Files
proton-bridge/pkg/keychain/helper_dbus_linux.go
Jakub f3c69faf8b GODT-1260: Renaming
* Renaming GUI, CLI, no-impact config.
* License header and documentation rebranding.
* Rename app title and vendor. Impact: manual install
* Migrating mac keychain and launch on startup.
* Fix linter and linter renaming
2022-05-18 11:23:38 +02:00

221 lines
5.0 KiB
Go

// Copyright (c) 2022 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail 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.
//
// Proton Mail 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 Proton Mail 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
}