Files
jira/vendor/github.com/tmc/keyring/keyring_linux.go
T
2017-09-06 11:35:00 -07:00

153 lines
4.2 KiB
Go

// +build !gnome_keyring
package keyring
import (
"fmt"
dbus "github.com/guelfey/go.dbus"
)
const (
ssServiceName = "org.freedesktop.secrets"
ssServicePath = "/org/freedesktop/secrets"
ssCollectionPath = "/org/freedesktop/secrets/aliases/default"
ssServiceIface = "org.freedesktop.Secret.Service."
ssSessionIface = "org.freedesktop.Secret.Session."
ssCollectionIface = "org.freedesktop.Secret.Collection."
ssItemIface = "org.freedesktop.Secret.Item."
ssPromptIface = "org.freedesktop.Secret.Prompt."
)
// ssSecret corresponds to org.freedesktop.Secret.Item
// Note: Order is important
type ssSecret struct {
Session dbus.ObjectPath
Parameters []byte
Value []byte
ContentType string `dbus:"content_type"`
}
// newSSSecret prepares an ssSecret for use
// Uses text/plain as the Content-type which may need to change in the future
func newSSSecret(session dbus.ObjectPath, secret string) (s ssSecret) {
s = ssSecret{
ContentType: "text/plain; charset=utf8",
Parameters: []byte{},
Session: session,
Value: []byte(secret),
}
return
}
// ssProvider implements the provider interface freedesktop SecretService
type ssProvider struct {
*dbus.Conn
srv *dbus.Object
}
// This is used to open a seassion for every get/set. Alternative might be to
// defer() the call to close when constructing the ssProvider
func (s *ssProvider) openSession() (*dbus.Object, error) {
var disregard dbus.Variant
var sessionPath dbus.ObjectPath
method := fmt.Sprint(ssServiceIface, "OpenSession")
err := s.srv.Call(method, 0, "plain", dbus.MakeVariant("")).Store(&disregard, &sessionPath)
if err != nil {
return nil, err
}
return s.Object(ssServiceName, sessionPath), nil
}
// Unsure how the .Prompt call surfaces, it hasn't come up.
func (s *ssProvider) unlock(p dbus.ObjectPath) error {
var unlocked []dbus.ObjectPath
var prompt dbus.ObjectPath
method := fmt.Sprint(ssServiceIface, "Unlock")
err := s.srv.Call(method, 0, []dbus.ObjectPath{p}).Store(&unlocked, &prompt)
if err != nil {
return fmt.Errorf("keyring/dbus: Unlock error: %s", err)
}
if prompt != dbus.ObjectPath("/") {
method = fmt.Sprint(ssPromptIface, "Prompt")
call := s.Object(ssServiceName, prompt).Call(method, 0, "unlock")
return call.Err
}
return nil
}
func (s *ssProvider) Get(c, u string) (string, error) {
results := []dbus.ObjectPath{}
var secret ssSecret
search := map[string]string{
"username": u,
"service": c,
}
session, err := s.openSession()
if err != nil {
return "", err
}
defer session.Call(fmt.Sprint(ssSessionIface, "Close"), 0)
s.unlock(ssCollectionPath)
collection := s.Object(ssServiceName, ssCollectionPath)
method := fmt.Sprint(ssCollectionIface, "SearchItems")
call := collection.Call(method, 0, search)
err = call.Store(&results)
if call.Err != nil {
return "", call.Err
}
// results is a slice. Just grab the first one.
if len(results) == 0 {
return "", ErrNotFound
}
method = fmt.Sprint(ssItemIface, "GetSecret")
err = s.Object(ssServiceName, results[0]).Call(method, 0, session.Path()).Store(&secret)
if err != nil {
return "", err
}
return string(secret.Value), nil
}
func (s *ssProvider) Set(c, u, p string) error {
var item, prompt dbus.ObjectPath
properties := map[string]dbus.Variant{
"org.freedesktop.Secret.Item.Label": dbus.MakeVariant(fmt.Sprintf("%s - %s", u, c)),
"org.freedesktop.Secret.Item.Attributes": dbus.MakeVariant(map[string]string{
"username": u,
"service": c,
}),
}
session, err := s.openSession()
if err != nil {
return err
}
defer session.Call(fmt.Sprint(ssSessionIface, "Close"), 0)
s.unlock(ssCollectionPath)
collection := s.Object(ssServiceName, ssCollectionPath)
secret := newSSSecret(session.Path(), p)
// the bool is "replace"
err = collection.Call(fmt.Sprint(ssCollectionIface, "CreateItem"), 0, properties, secret, true).Store(&item, &prompt)
if err != nil {
return fmt.Errorf("keyring/dbus: CreateItem error: %s", err)
}
if prompt != "/" {
s.Object(ssServiceName, prompt).Call(fmt.Sprint(ssPromptIface, "Prompt"), 0, "unlock")
}
return nil
}
func initializeProvider() (provider, error) {
conn, err := dbus.SessionBus()
if err != nil {
return nil, err
}
srv := conn.Object(ssServiceName, ssServicePath)
p := &ssProvider{conn, srv}
return p, nil
}