Compare commits

...

6 Commits

21 changed files with 431 additions and 28 deletions

View File

@ -147,8 +147,6 @@ build-linux-qa:
.build-darwin-base: .build-darwin-base:
extends: .build-base extends: .build-base
before_script: 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/bin:$PATH
- export PATH=/usr/local/opt/git/bin:$PATH - export PATH=/usr/local/opt/git/bin:$PATH
- export PATH=/usr/local/opt/make/libexec/gnubin:$PATH - export PATH=/usr/local/opt/make/libexec/gnubin:$PATH

0
.gitmodules vendored
View File

View File

@ -2,6 +2,18 @@
Changelog [format](http://keepachangelog.com/en/1.0.0/) 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 ## [Bridge 2.1.2] London
## Added ## Added

View File

@ -10,7 +10,7 @@ TARGET_OS?=${GOOS}
.PHONY: build build-nogui build-launcher versioner hasher .PHONY: build build-nogui build-launcher versioner hasher
# Keep version hardcoded so app build works also without Git repository. # 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} APP_VERSION:=${BRIDGE_APP_VERSION}
SRC_ICO:=logo.ico SRC_ICO:=logo.ico
SRC_ICNS:=Bridge.icns SRC_ICNS:=Bridge.icns
@ -85,7 +85,7 @@ hasher:
${TGZ_TARGET}: ${DEPLOY_DIR}/${TARGET_OS} ${TGZ_TARGET}: ${DEPLOY_DIR}/${TARGET_OS}
rm -f $@ rm -f $@
cd ${DEPLOY_DIR}/${TARGET_OS} && tar czf ../../../../$@ . cd ${DEPLOY_DIR}/${TARGET_OS} && tar -czvf ../../../../$@ .
${DEPLOY_DIR}/linux: ${EXE_TARGET} ${DEPLOY_DIR}/linux: ${EXE_TARGET}
cp -pf ./internal/frontend/share/${SRC_SVG} ${DEPLOY_DIR}/linux/logo.svg cp -pf ./internal/frontend/share/${SRC_SVG} ${DEPLOY_DIR}/linux/logo.svg

103
doc/updates.md Normal file
View 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
View File

@ -43,12 +43,13 @@ require (
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
github.com/getsentry/sentry-go v0.12.0 github.com/getsentry/sentry-go v0.12.0
github.com/go-resty/resty/v2 v2.6.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/golang/mock v1.4.4
github.com/google/go-cmp v0.5.5 github.com/google/go-cmp v0.5.5
github.com/google/uuid v1.1.1 github.com/google/uuid v1.1.1
github.com/hashicorp/go-multierror v1.1.0 github.com/hashicorp/go-multierror v1.1.0
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7 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/kr/text v0.2.0 // indirect
github.com/logrusorgru/aurora v2.0.3+incompatible github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/mattn/go-runewidth v0.0.9 // indirect 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-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/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/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
View File

@ -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.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 h1:fvkpwsLgnIm0qugftrw2YwNlio+ABe2Iu94Ap8GMYIY=
github.com/cucumber/messages-go/v16 v16.0.1/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g= 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 h1:3RNcEpBg4IhIChZdFRSdlQt1QjCp1sMAPIrOnm7Yf8g=
github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= 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= 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/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/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= 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 h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 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= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=

View File

@ -133,7 +133,7 @@ QtObject {
return Qt.point(_x, _y) return Qt.point(_x, _y)
} }
// fir to the right // fit to the right
_x = iconRect.right _x = iconRect.right
if (isInInterval(_x, screenRect.left, screenRect.right - width)) { if (isInInterval(_x, screenRect.left, screenRect.right - width)) {
// position preferebly in the vertical center but bound to the screen rect // position preferebly in the vertical center but bound to the screen rect

View File

@ -729,6 +729,9 @@ Window {
console.log("check updates") console.log("check updates")
} }
signal checkUpdatesFinished() signal checkUpdatesFinished()
function installUpdate() {
console.log("manuall install update triggered")
}
property bool isDiskCacheEnabled: true property bool isDiskCacheEnabled: true
@ -748,7 +751,19 @@ Window {
property bool isAutomaticUpdateOn : true property bool isAutomaticUpdateOn : true
function toggleAutomaticUpdate(makeItActive) { function toggleAutomaticUpdate(makeItActive) {
console.debug("-> silent updates", makeItActive, root.isAutomaticUpdateOn) 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 property bool isAutostartOn : true // Example of settings with loading state

View File

@ -127,19 +127,10 @@ SettingsView {
} }
TextEdit { TextEdit {
text: { text: qsTr("Reports are not end-to-end encrypted, please do not send any sensitive information.")
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
readOnly: true readOnly: true
Layout.fillWidth: true Layout.fillWidth: true
color: root.colorScheme.text_weak color: root.colorScheme.text_weak
font.family: ProtonStyle.font_family font.family: ProtonStyle.font_family

View File

@ -182,6 +182,7 @@ ApplicationWindow {
colorScheme: root.colorScheme colorScheme: root.colorScheme
notifications: root.notifications notifications: root.notifications
mainWindow: root mainWindow: root
backend: root.backend
} }
function showLocalCacheSettings() { contentWrapper.showLocalCacheSettings() } function showLocalCacheSettings() { contentWrapper.showLocalCacheSettings() }

View File

@ -25,6 +25,7 @@ import Notifications 1.0
Item { Item {
id: root id: root
property var backend
property ColorScheme colorScheme property ColorScheme colorScheme
property var notifications property var notifications
@ -51,8 +52,11 @@ Item {
notification: root.notifications.updateManualReady notification: root.notifications.updateManualReady
Switch { Switch {
id:autoUpdate
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: qsTr("Update automatically in the future") text: qsTr("Update automatically in the future")
checked: root.backend.isAutomaticUpdateOn
onClicked: root.backend.toggleAutomaticUpdate(autoUpdate.checked)
} }
} }

View File

@ -166,8 +166,9 @@ QtObject {
} }
property Notification updateManualError: Notification { property Notification updateManualError: Notification {
description: qsTr("Bridge couldnt update") title: qsTr("Bridge couldnt update")
brief: description brief: title
description: qsTr("Please follow manual installation in order to update Bridge.")
icon: "./icons/ic-exclamation-circle-filled.svg" icon: "./icons/ic-exclamation-circle-filled.svg"
type: Notification.NotificationType.Warning type: Notification.NotificationType.Warning
group: Notifications.Group.Update group: Notifications.Group.Update
@ -192,7 +193,7 @@ QtObject {
text: qsTr("Remind me later") text: qsTr("Remind me later")
onTriggered: { onTriggered: {
root.updateManualReady.active = false root.updateManualError.active = false
} }
} }
] ]
@ -273,7 +274,7 @@ QtObject {
onTriggered: { onTriggered: {
root.backend.quit() root.backend.quit()
root.updateForce.active = false root.updateForceError.active = false
} }
} }
] ]

View File

@ -153,7 +153,7 @@ func (f *FrontendQt) NotifySilentUpdateInstalled() {
} }
func (f *FrontendQt) NotifySilentUpdateError(err error) { 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() f.qml.UpdateManualError()
} }

View File

@ -25,6 +25,7 @@ import (
"github.com/ProtonMail/proton-bridge/internal/config/settings" "github.com/ProtonMail/proton-bridge/internal/config/settings"
"github.com/ProtonMail/proton-bridge/internal/updater" "github.com/ProtonMail/proton-bridge/internal/updater"
"github.com/pkg/errors"
) )
var checkingUpdates = sync.Mutex{} var checkingUpdates = sync.Mutex{}
@ -62,9 +63,18 @@ func (f *FrontendQt) checkUpdatesAndNotify(isRequestFromUser bool) {
if !f.updater.CanInstall(f.newVersionInfo) { if !f.updater.CanInstall(f.newVersionInfo) {
f.log.Debug("A manual update is required") f.log.Debug("A manual update is required")
f.qml.UpdateManualReady(f.newVersionInfo.Version.String()) f.qml.UpdateManualError()
return 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() { 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. // Immediately check the updates to set the correct landing page link.
f.checkUpdates() 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()
}

View File

@ -81,6 +81,7 @@ type QMLBackend struct {
_ func() `signal:"updateIsLatestVersion"` _ func() `signal:"updateIsLatestVersion"`
_ func() `slot:"checkUpdates"` _ func() `slot:"checkUpdates"`
_ func() `signal:"checkUpdatesFinished"` _ func() `signal:"checkUpdatesFinished"`
_ func() `slot:"installUpdate"`
_ bool `property:"isDiskCacheEnabled"` _ bool `property:"isDiskCacheEnabled"`
_ core.QUrl `property:"diskCachePath"` _ 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.setIsDiskCacheEnabled()
f.setDiskCachePath() f.setDiskCachePath()
q.ConnectChangeLocalCache(func(e bool, d *core.QUrl) { q.ConnectChangeLocalCache(func(e bool, d *core.QUrl) {

View File

@ -127,6 +127,6 @@ func TestCooldownNotSooner(t *testing.T) {
assert.True(t, testCooldown.isTooSoon()) assert.True(t, testCooldown.isTooSoon())
// After given wait time it shouldn't be soon anymore. // 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()) assert.False(t, testCooldown.isTooSoon())
} }

View 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
}

View File

@ -28,13 +28,18 @@ import (
) )
const ( const (
Pass = "pass-app" Pass = "pass-app"
SecretService = "secret-service" SecretService = "secret-service"
SecretServiceDBus = "secret-service-dbus"
) )
func init() { // nolint[noinit] func init() { // nolint[noinit]
Helpers = make(map[string]helperConstructor) Helpers = make(map[string]helperConstructor)
if isUsable(newDBusHelper("")) {
Helpers[SecretServiceDBus] = newDBusHelper
}
if _, err := exec.LookPath("gnome-keyring"); err == nil && isUsable(newSecretServiceHelper("")) { if _, err := exec.LookPath("gnome-keyring"); err == nil && isUsable(newSecretServiceHelper("")) {
Helpers[SecretService] = newSecretServiceHelper Helpers[SecretService] = newSecretServiceHelper
} }
@ -43,6 +48,8 @@ func init() { // nolint[noinit]
Helpers[Pass] = newPassHelper Helpers[Pass] = newPassHelper
} }
defaultHelper = SecretServiceDBus
// If Pass is available, use it by default. // If Pass is available, use it by default.
// Otherwise, if SecretService is available, use it by default. // Otherwise, if SecretService is available, use it by default.
if _, ok := Helpers[Pass]; ok { 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) { func newPassHelper(string) (credentials.Helper, error) {
return &pass.Pass{}, nil return &pass.Pass{}, nil
} }

View File

@ -34,7 +34,7 @@ var (
wantOutput = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} wantOutput = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
testProcessSleep = 100 // ms testProcessSleep = 100 // ms
runParallelTimeOverhead = 150 // ms runParallelTimeOverhead = 150 // ms
windowsCIExtra = 250 // ms - estimated experimentally windowsCIExtra = 500 // ms - estimated experimentally
) )
func TestParallel(t *testing.T) { func TestParallel(t *testing.T) {

View File

@ -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 .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 GO111MODULE=on
export BRIDGE_VERSION:=2.1.2+integrationtests export BRIDGE_VERSION:=2.1.3+integrationtests
export VERBOSITY?=fatal export VERBOSITY?=fatal
export TEST_DATA=testdata export TEST_DATA=testdata