mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-10 04:36:43 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 51eb2c42cd | |||
| c94d839fbb | |||
| de586e5f12 | |||
| c32a106898 | |||
| 5b20b6a3d0 | |||
| 3b07121f08 |
@ -147,8 +147,6 @@ build-linux-qa:
|
||||
.build-darwin-base:
|
||||
extends: .build-base
|
||||
before_script:
|
||||
- eval $(ssh-agent -s)
|
||||
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
|
||||
- export PATH=/usr/local/bin:$PATH
|
||||
- export PATH=/usr/local/opt/git/bin:$PATH
|
||||
- export PATH=/usr/local/opt/make/libexec/gnubin:$PATH
|
||||
|
||||
0
.gitmodules
vendored
0
.gitmodules
vendored
12
Changelog.md
12
Changelog.md
@ -2,6 +2,18 @@
|
||||
|
||||
Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
||||
|
||||
## [Bridge 2.1.3] London
|
||||
|
||||
## Added
|
||||
GODT-1525: Add keybase/go-keychain/secretservice as new keychain helper.
|
||||
|
||||
## Changed
|
||||
GODT-1527: Change bug report description.
|
||||
|
||||
## Fixed
|
||||
GODT-1537: Manual in-app update mechanism.
|
||||
|
||||
|
||||
## [Bridge 2.1.2] London
|
||||
|
||||
## Added
|
||||
|
||||
4
Makefile
4
Makefile
@ -10,7 +10,7 @@ TARGET_OS?=${GOOS}
|
||||
.PHONY: build build-nogui build-launcher versioner hasher
|
||||
|
||||
# Keep version hardcoded so app build works also without Git repository.
|
||||
BRIDGE_APP_VERSION?=2.1.2+git
|
||||
BRIDGE_APP_VERSION?=2.1.3+git
|
||||
APP_VERSION:=${BRIDGE_APP_VERSION}
|
||||
SRC_ICO:=logo.ico
|
||||
SRC_ICNS:=Bridge.icns
|
||||
@ -85,7 +85,7 @@ hasher:
|
||||
|
||||
${TGZ_TARGET}: ${DEPLOY_DIR}/${TARGET_OS}
|
||||
rm -f $@
|
||||
cd ${DEPLOY_DIR}/${TARGET_OS} && tar czf ../../../../$@ .
|
||||
cd ${DEPLOY_DIR}/${TARGET_OS} && tar -czvf ../../../../$@ .
|
||||
|
||||
${DEPLOY_DIR}/linux: ${EXE_TARGET}
|
||||
cp -pf ./internal/frontend/share/${SRC_SVG} ${DEPLOY_DIR}/linux/logo.svg
|
||||
|
||||
103
doc/updates.md
Normal file
103
doc/updates.md
Normal file
@ -0,0 +1,103 @@
|
||||
# Update mechanism of Bridge
|
||||
|
||||
There are mulitple options how to change version of application:
|
||||
* Automatic in-app update
|
||||
* Manual in-app update
|
||||
* Manual install
|
||||
|
||||
In-app update ends with restarting bridge into new version. Automatic in-app
|
||||
update is downloading, verifying and installing the new version immediatelly
|
||||
without user confirmation. For manual in-app update user needs to confirm first.
|
||||
Update is done from special update file published on website.
|
||||
|
||||
The manual installation requires user to download, verify and install manually
|
||||
using installer for given OS.
|
||||
|
||||
The bridge is installed and executed differently for given OS:
|
||||
|
||||
* Windows and Linux apps are using launcher mechanism:
|
||||
* There is system protected installation path which is created on first
|
||||
install. It contains bridge exe and launcher exe. When users starts
|
||||
bridge the launcher is executed first. It will check update path compare
|
||||
version with installed one. The newer version then is then executed.
|
||||
* Update mechanism means to replace files in update folder which is located
|
||||
in user space.
|
||||
|
||||
* macOS app does not use launcher
|
||||
* No launcher, only one executable
|
||||
* In-App udpate replaces the bridge files in installation path directly
|
||||
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Frontend
|
||||
U[User requests<br>version check]
|
||||
ManIns((Notify user about<br>manual install<br>is needed))
|
||||
R((Notify user<br>about restart))
|
||||
ManUp((Notify user about<br>manual update))
|
||||
NF((Notify user about<br>force update))
|
||||
|
||||
ManUp -->|Install| InstFront[Install]
|
||||
InstFront -->|Ok| R
|
||||
InstFront -->|Error| ManIns
|
||||
|
||||
U --> CheckFront[Check online]
|
||||
CheckFront -->|Ok| IAFront{Is new version<br>and applicable?}
|
||||
CheckFront -->|Error| ManIns
|
||||
|
||||
IAFront -->|No| Latest((Notify user<br>has latest version))
|
||||
IAFront -->|Yes| CanInstall{Can update?}
|
||||
CanInstall -->|No| ManIns
|
||||
CanInstall -->|Yes| NotifOrInstall{Is automatic<br>update enabled?}
|
||||
NotifOrInstall -->|Manual| ManUp
|
||||
end
|
||||
|
||||
|
||||
subgraph Backend
|
||||
W[Wait for next check]
|
||||
|
||||
W --> Check[Check online]
|
||||
|
||||
Check --> NV{Has new<br>version?}
|
||||
Check -->|Error| W
|
||||
NV -->|No new version| W
|
||||
IA{Is install<br>applicable?}
|
||||
NV -->|New version<br>available| IA
|
||||
IA -->|Local rollout<br>not enough| W
|
||||
IA -->|Yes| AU{Is automatic\nupdate enabled?}
|
||||
|
||||
AU -->|Yes| CanUp{Can update?}
|
||||
CanUp -->|No| ManIns
|
||||
|
||||
CanUp -->|Yes| Ins[Install]
|
||||
Ins -->|Error| ManIns
|
||||
Ins -->|Ok| R
|
||||
|
||||
AU -->|No| ManUp
|
||||
ManUp -->|Ignore| W
|
||||
|
||||
|
||||
F[Force update]
|
||||
F --> NF
|
||||
end
|
||||
|
||||
ManIns --> Web[Open web page]
|
||||
NF --> Web
|
||||
ManUp --> Web
|
||||
R --> Re[Restart]
|
||||
NF --> Q[Quit bridge]
|
||||
NotifOrInstall -->|Automatic| W
|
||||
```
|
||||
|
||||
|
||||
The non-trivial is to combine the update with setting change:
|
||||
* turn off/on automatic in-app updates
|
||||
* change from stable to beta or back
|
||||
|
||||
_TODO fill flow chart details_
|
||||
|
||||
|
||||
We are not support downgrade functionality. Only some circumstances can lead to
|
||||
downgrading the app version.
|
||||
|
||||
_TODO fill flow chart details_
|
||||
4
go.mod
4
go.mod
@ -43,12 +43,13 @@ require (
|
||||
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
||||
github.com/getsentry/sentry-go v0.12.0
|
||||
github.com/go-resty/resty/v2 v2.6.0
|
||||
github.com/godbus/dbus v4.1.0+incompatible
|
||||
github.com/golang/mock v1.4.4
|
||||
github.com/google/go-cmp v0.5.5
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/hashicorp/go-multierror v1.1.0
|
||||
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7
|
||||
github.com/keybase/go-keychain v0.0.0-20211119201326-e02f34051621
|
||||
github.com/keybase/go-keychain v0.0.0
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
@ -79,4 +80,5 @@ replace (
|
||||
github.com/emersion/go-imap => github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac
|
||||
github.com/emersion/go-message => github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753
|
||||
github.com/jameskeane/bcrypt => github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57
|
||||
github.com/keybase/go-keychain => github.com/cuthix/go-keychain v0.0.0-20220405075754-31e7cee908fe
|
||||
)
|
||||
|
||||
4
go.sum
4
go.sum
@ -102,6 +102,8 @@ github.com/cucumber/godog v0.12.1/go.mod h1:u6SD7IXC49dLpPN35kal0oYEjsXZWee4pW6T
|
||||
github.com/cucumber/messages-go/v16 v16.0.0/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g=
|
||||
github.com/cucumber/messages-go/v16 v16.0.1 h1:fvkpwsLgnIm0qugftrw2YwNlio+ABe2Iu94Ap8GMYIY=
|
||||
github.com/cucumber/messages-go/v16 v16.0.1/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g=
|
||||
github.com/cuthix/go-keychain v0.0.0-20220405075754-31e7cee908fe h1:KRj3wdvA9yE92prNmOjS7x5DOqoyjxqdE30qnrmTasc=
|
||||
github.com/cuthix/go-keychain v0.0.0-20220405075754-31e7cee908fe/go.mod h1:ZoZU1fnBy3mOLWr3Pg+Y2+nTKtu6ypDte2kZg9HvSwY=
|
||||
github.com/danieljoos/wincred v1.1.0 h1:3RNcEpBg4IhIChZdFRSdlQt1QjCp1sMAPIrOnm7Yf8g=
|
||||
github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -164,6 +166,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
|
||||
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
|
||||
@ -133,7 +133,7 @@ QtObject {
|
||||
return Qt.point(_x, _y)
|
||||
}
|
||||
|
||||
// fir to the right
|
||||
// fit to the right
|
||||
_x = iconRect.right
|
||||
if (isInInterval(_x, screenRect.left, screenRect.right - width)) {
|
||||
// position preferebly in the vertical center but bound to the screen rect
|
||||
|
||||
@ -729,6 +729,9 @@ Window {
|
||||
console.log("check updates")
|
||||
}
|
||||
signal checkUpdatesFinished()
|
||||
function installUpdate() {
|
||||
console.log("manuall install update triggered")
|
||||
}
|
||||
|
||||
|
||||
property bool isDiskCacheEnabled: true
|
||||
@ -748,7 +751,19 @@ Window {
|
||||
property bool isAutomaticUpdateOn : true
|
||||
function toggleAutomaticUpdate(makeItActive) {
|
||||
console.debug("-> silent updates", makeItActive, root.isAutomaticUpdateOn)
|
||||
root.isAutomaticUpdateOn = makeItActive
|
||||
var callback = function () {
|
||||
root.isAutomaticUpdateOn = makeItActive;
|
||||
console.debug("-> CHANGED silent updates", makeItActive, root.isAutomaticUpdateOn)
|
||||
}
|
||||
atimer.onTriggered.connect(callback)
|
||||
atimer.restart()
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: atimer
|
||||
interval: 2000
|
||||
running: false
|
||||
repeat: false
|
||||
}
|
||||
|
||||
property bool isAutostartOn : true // Example of settings with loading state
|
||||
|
||||
@ -127,19 +127,10 @@ SettingsView {
|
||||
}
|
||||
|
||||
TextEdit {
|
||||
text: {
|
||||
var address = "bridge@protonmail.com"
|
||||
var mailTo = `<a href="mailto://${address}">${address}</a>`
|
||||
return "<style>a:link { color: " + root.colorScheme.interaction_norm + "; }</style>" +qsTr(
|
||||
"These reports are not end-to-end encrypted. In case of sensitive information, contact us at %1."
|
||||
).arg(mailTo)
|
||||
}
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
textFormat: Text.RichText
|
||||
text: qsTr("Reports are not end-to-end encrypted, please do not send any sensitive information.")
|
||||
|
||||
readOnly: true
|
||||
|
||||
|
||||
Layout.fillWidth: true
|
||||
color: root.colorScheme.text_weak
|
||||
font.family: ProtonStyle.font_family
|
||||
|
||||
@ -182,6 +182,7 @@ ApplicationWindow {
|
||||
colorScheme: root.colorScheme
|
||||
notifications: root.notifications
|
||||
mainWindow: root
|
||||
backend: root.backend
|
||||
}
|
||||
|
||||
function showLocalCacheSettings() { contentWrapper.showLocalCacheSettings() }
|
||||
|
||||
@ -25,6 +25,7 @@ import Notifications 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property var backend
|
||||
|
||||
property ColorScheme colorScheme
|
||||
property var notifications
|
||||
@ -51,8 +52,11 @@ Item {
|
||||
notification: root.notifications.updateManualReady
|
||||
|
||||
Switch {
|
||||
id:autoUpdate
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Update automatically in the future")
|
||||
checked: root.backend.isAutomaticUpdateOn
|
||||
onClicked: root.backend.toggleAutomaticUpdate(autoUpdate.checked)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -166,8 +166,9 @@ QtObject {
|
||||
}
|
||||
|
||||
property Notification updateManualError: Notification {
|
||||
description: qsTr("Bridge couldn’t update")
|
||||
brief: description
|
||||
title: qsTr("Bridge couldn’t update")
|
||||
brief: title
|
||||
description: qsTr("Please follow manual installation in order to update Bridge.")
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||
type: Notification.NotificationType.Warning
|
||||
group: Notifications.Group.Update
|
||||
@ -192,7 +193,7 @@ QtObject {
|
||||
text: qsTr("Remind me later")
|
||||
|
||||
onTriggered: {
|
||||
root.updateManualReady.active = false
|
||||
root.updateManualError.active = false
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -273,7 +274,7 @@ QtObject {
|
||||
|
||||
onTriggered: {
|
||||
root.backend.quit()
|
||||
root.updateForce.active = false
|
||||
root.updateForceError.active = false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@ -153,7 +153,7 @@ func (f *FrontendQt) NotifySilentUpdateInstalled() {
|
||||
}
|
||||
|
||||
func (f *FrontendQt) NotifySilentUpdateError(err error) {
|
||||
f.log.WithError(err).Warn("Update failed, asking for manual.")
|
||||
f.log.WithError(err).Warn("In-app update failed, asking for manual.")
|
||||
f.qml.UpdateManualError()
|
||||
}
|
||||
|
||||
|
||||
@ -25,6 +25,7 @@ import (
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var checkingUpdates = sync.Mutex{}
|
||||
@ -62,9 +63,18 @@ func (f *FrontendQt) checkUpdatesAndNotify(isRequestFromUser bool) {
|
||||
|
||||
if !f.updater.CanInstall(f.newVersionInfo) {
|
||||
f.log.Debug("A manual update is required")
|
||||
f.qml.UpdateManualReady(f.newVersionInfo.Version.String())
|
||||
f.qml.UpdateManualError()
|
||||
return
|
||||
}
|
||||
|
||||
if f.settings.GetBool(settings.AutoUpdateKey) {
|
||||
// NOOP will update eventually
|
||||
return
|
||||
}
|
||||
|
||||
if isRequestFromUser {
|
||||
f.qml.UpdateManualReady(f.newVersionInfo.Version.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FrontendQt) updateForce() {
|
||||
@ -113,3 +123,26 @@ func (f *FrontendQt) toggleBeta(makeItEnabled bool) {
|
||||
// Immediately check the updates to set the correct landing page link.
|
||||
f.checkUpdates()
|
||||
}
|
||||
|
||||
func (f *FrontendQt) installUpdate() {
|
||||
checkingUpdates.Lock()
|
||||
defer checkingUpdates.Unlock()
|
||||
|
||||
if !f.updater.CanInstall(f.newVersionInfo) {
|
||||
f.log.Warning("Skipping update installation, current version too old")
|
||||
f.qml.UpdateManualError()
|
||||
return
|
||||
}
|
||||
|
||||
if err := f.updater.InstallUpdate(f.newVersionInfo); err != nil {
|
||||
if errors.Cause(err) == updater.ErrDownloadVerify {
|
||||
f.log.WithError(err).Warning("Skipping update installation due to temporary error")
|
||||
} else {
|
||||
f.log.WithError(err).Error("The update couldn't be installed")
|
||||
f.qml.UpdateManualError()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
f.qml.UpdateSilentRestartNeeded()
|
||||
}
|
||||
|
||||
@ -81,6 +81,7 @@ type QMLBackend struct {
|
||||
_ func() `signal:"updateIsLatestVersion"`
|
||||
_ func() `slot:"checkUpdates"`
|
||||
_ func() `signal:"checkUpdatesFinished"`
|
||||
_ func() `slot:"installUpdate"`
|
||||
|
||||
_ bool `property:"isDiskCacheEnabled"`
|
||||
_ core.QUrl `property:"diskCachePath"`
|
||||
@ -213,6 +214,13 @@ func (q *QMLBackend) setup(f *FrontendQt) {
|
||||
}()
|
||||
})
|
||||
|
||||
q.ConnectInstallUpdate(func() {
|
||||
go func() {
|
||||
defer f.panicHandler.HandlePanic()
|
||||
f.installUpdate()
|
||||
}()
|
||||
})
|
||||
|
||||
f.setIsDiskCacheEnabled()
|
||||
f.setDiskCachePath()
|
||||
q.ConnectChangeLocalCache(func(e bool, d *core.QUrl) {
|
||||
|
||||
@ -127,6 +127,6 @@ func TestCooldownNotSooner(t *testing.T) {
|
||||
assert.True(t, testCooldown.isTooSoon())
|
||||
|
||||
// After given wait time it shouldn't be soon anymore.
|
||||
time.Sleep(waitTime / 2)
|
||||
time.Sleep(waitTime/2 + time.Millisecond)
|
||||
assert.False(t, testCooldown.isTooSoon())
|
||||
}
|
||||
|
||||
220
pkg/keychain/helper_dbus_linux.go
Normal file
220
pkg/keychain/helper_dbus_linux.go
Normal file
@ -0,0 +1,220 @@
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package keychain
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
"github.com/godbus/dbus"
|
||||
"github.com/keybase/go-keychain/secretservice"
|
||||
)
|
||||
|
||||
const (
|
||||
serverAtt = "server"
|
||||
labelAtt = "label"
|
||||
usernameAtt = "username"
|
||||
|
||||
defaulDomain = "protonmail/bridge/users/"
|
||||
defaultLabel = "Docker Credentials"
|
||||
)
|
||||
|
||||
func getSession() (*secretservice.SecretService, *secretservice.Session, error) {
|
||||
service, err := secretservice.NewService()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
session, err := service.OpenSession(secretservice.AuthenticationDHAES)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return service, session, nil
|
||||
}
|
||||
|
||||
func handleTimeout(f func() error) error {
|
||||
err := f()
|
||||
if err == secretservice.ErrPromptTimedOut {
|
||||
return f()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func getItems(service *secretservice.SecretService, attributes map[string]string) ([]dbus.ObjectPath, error) {
|
||||
if err := unlock(service); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var items []dbus.ObjectPath
|
||||
err := handleTimeout(func() error {
|
||||
var err error
|
||||
items, err = service.SearchCollection(
|
||||
secretservice.DefaultCollection,
|
||||
attributes,
|
||||
)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return items, err
|
||||
}
|
||||
|
||||
func unlock(service *secretservice.SecretService) error {
|
||||
return handleTimeout(func() error {
|
||||
return service.Unlock([]dbus.ObjectPath{secretservice.DefaultCollection})
|
||||
})
|
||||
}
|
||||
|
||||
// SecretServiceDBusHelper is wrapper around keybase/go-keychain/secretservice
|
||||
// library.
|
||||
type SecretServiceDBusHelper struct{}
|
||||
|
||||
// Add appends credentials to the store.
|
||||
func (s *SecretServiceDBusHelper) Add(creds *credentials.Credentials) error {
|
||||
service, session, err := getSession()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer service.CloseSession(session)
|
||||
|
||||
if err := unlock(service); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
secret, err := session.NewSecret([]byte(creds.Secret))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
attributes := map[string]string{
|
||||
usernameAtt: creds.Username,
|
||||
serverAtt: creds.ServerURL,
|
||||
labelAtt: defaultLabel,
|
||||
"xdg:schema": "io.docker.Credentials",
|
||||
"docker_cli": "1",
|
||||
}
|
||||
|
||||
return handleTimeout(func() error {
|
||||
_, err = service.CreateItem(
|
||||
secretservice.DefaultCollection,
|
||||
secretservice.NewSecretProperties(creds.ServerURL, attributes),
|
||||
secret,
|
||||
secretservice.ReplaceBehaviorReplace,
|
||||
)
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// Delete removes credentials from the store.
|
||||
func (s *SecretServiceDBusHelper) Delete(serverURL string) error {
|
||||
service, session, err := getSession()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer service.CloseSession(session)
|
||||
|
||||
items, err := getItems(service, map[string]string{
|
||||
labelAtt: defaultLabel,
|
||||
serverAtt: serverURL,
|
||||
})
|
||||
|
||||
if len(items) == 0 || err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return handleTimeout(func() error {
|
||||
return service.DeleteItem(items[0])
|
||||
})
|
||||
}
|
||||
|
||||
// Get retrieves credentials from the store.
|
||||
// It returns username and secret as strings.
|
||||
func (s *SecretServiceDBusHelper) Get(serverURL string) (string, string, error) {
|
||||
service, session, err := getSession()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
defer service.CloseSession(session)
|
||||
|
||||
if err := unlock(service); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
items, err := getItems(service, map[string]string{
|
||||
labelAtt: defaultLabel,
|
||||
serverAtt: serverURL,
|
||||
})
|
||||
|
||||
if len(items) == 0 || err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
item := items[0]
|
||||
|
||||
attributes, err := service.GetAttributes(item)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
var secretPlaintext []byte
|
||||
err = handleTimeout(func() error {
|
||||
var err error
|
||||
secretPlaintext, err = service.GetSecret(item, *session)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return attributes[usernameAtt], string(secretPlaintext), nil
|
||||
}
|
||||
|
||||
// List returns the stored serverURLs and their associated usernames.
|
||||
func (s *SecretServiceDBusHelper) List() (map[string]string, error) {
|
||||
userIDByURL := make(map[string]string)
|
||||
|
||||
service, session, err := getSession()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer service.CloseSession(session)
|
||||
|
||||
items, err := getItems(service, map[string]string{labelAtt: defaultLabel})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, it := range items {
|
||||
attributes, err := service.GetAttributes(it)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(attributes[serverAtt], defaulDomain) {
|
||||
continue
|
||||
}
|
||||
|
||||
userIDByURL[attributes[serverAtt]] = attributes[usernameAtt]
|
||||
}
|
||||
|
||||
return userIDByURL, nil
|
||||
}
|
||||
@ -28,13 +28,18 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
Pass = "pass-app"
|
||||
SecretService = "secret-service"
|
||||
Pass = "pass-app"
|
||||
SecretService = "secret-service"
|
||||
SecretServiceDBus = "secret-service-dbus"
|
||||
)
|
||||
|
||||
func init() { // nolint[noinit]
|
||||
Helpers = make(map[string]helperConstructor)
|
||||
|
||||
if isUsable(newDBusHelper("")) {
|
||||
Helpers[SecretServiceDBus] = newDBusHelper
|
||||
}
|
||||
|
||||
if _, err := exec.LookPath("gnome-keyring"); err == nil && isUsable(newSecretServiceHelper("")) {
|
||||
Helpers[SecretService] = newSecretServiceHelper
|
||||
}
|
||||
@ -43,6 +48,8 @@ func init() { // nolint[noinit]
|
||||
Helpers[Pass] = newPassHelper
|
||||
}
|
||||
|
||||
defaultHelper = SecretServiceDBus
|
||||
|
||||
// If Pass is available, use it by default.
|
||||
// Otherwise, if SecretService is available, use it by default.
|
||||
if _, ok := Helpers[Pass]; ok {
|
||||
@ -52,6 +59,10 @@ func init() { // nolint[noinit]
|
||||
}
|
||||
}
|
||||
|
||||
func newDBusHelper(string) (credentials.Helper, error) {
|
||||
return &SecretServiceDBusHelper{}, nil
|
||||
}
|
||||
|
||||
func newPassHelper(string) (credentials.Helper, error) {
|
||||
return &pass.Pass{}, nil
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@ var (
|
||||
wantOutput = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
|
||||
testProcessSleep = 100 // ms
|
||||
runParallelTimeOverhead = 150 // ms
|
||||
windowsCIExtra = 250 // ms - estimated experimentally
|
||||
windowsCIExtra = 500 // ms - estimated experimentally
|
||||
)
|
||||
|
||||
func TestParallel(t *testing.T) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
.PHONY: check-go check-godog install-godog test test-bridge test-live test-live-bridge test-stage test-debug test-live-debug bench
|
||||
|
||||
export GO111MODULE=on
|
||||
export BRIDGE_VERSION:=2.1.2+integrationtests
|
||||
export BRIDGE_VERSION:=2.1.3+integrationtests
|
||||
export VERBOSITY?=fatal
|
||||
export TEST_DATA=testdata
|
||||
|
||||
|
||||
Reference in New Issue
Block a user