2021-11-21 23:05:13 -05:00
|
|
|
package gosecret
|
2021-11-21 18:07:52 -05:00
|
|
|
|
|
|
|
import (
|
2021-12-08 02:34:27 -05:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
2021-12-25 01:51:49 -05:00
|
|
|
"time"
|
2021-12-06 03:24:55 -05:00
|
|
|
|
|
|
|
"github.com/godbus/dbus/v5"
|
2022-01-09 15:39:37 -05:00
|
|
|
`r00t2.io/goutils/multierr`
|
2021-11-21 18:07:52 -05:00
|
|
|
)
|
|
|
|
|
2021-11-28 21:43:30 -05:00
|
|
|
// NewService returns a pointer to a new Service connection.
|
2021-11-21 18:07:52 -05:00
|
|
|
func NewService() (service *Service, err error) {
|
|
|
|
|
2021-12-07 02:56:15 -05:00
|
|
|
var svc Service = Service{
|
|
|
|
DbusObject: &DbusObject{
|
|
|
|
Conn: nil,
|
|
|
|
Dbus: nil,
|
|
|
|
},
|
|
|
|
Session: nil,
|
|
|
|
}
|
2021-11-21 18:07:52 -05:00
|
|
|
|
2021-11-28 21:43:30 -05:00
|
|
|
if svc.Conn, err = dbus.SessionBus(); err != nil {
|
2021-11-21 18:07:52 -05:00
|
|
|
return
|
|
|
|
}
|
2021-12-07 02:56:15 -05:00
|
|
|
svc.Dbus = svc.Conn.Object(DbusService, dbus.ObjectPath(DbusPath))
|
2021-11-21 18:07:52 -05:00
|
|
|
|
2021-12-06 03:24:55 -05:00
|
|
|
if svc.Session, err = svc.GetSession(); err != nil {
|
2021-11-28 21:43:30 -05:00
|
|
|
return
|
|
|
|
}
|
2021-11-21 18:07:52 -05:00
|
|
|
|
2021-11-28 21:43:30 -05:00
|
|
|
service = &svc
|
2021-11-21 18:07:52 -05:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-12-04 02:34:45 -05:00
|
|
|
// Close cleanly closes a Service and all its underlying connections (e.g. Service.Session).
|
|
|
|
func (s *Service) Close() (err error) {
|
|
|
|
|
2021-12-25 01:51:49 -05:00
|
|
|
if err = s.Session.Close(); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = s.Conn.Close(); err != nil {
|
|
|
|
return
|
|
|
|
}
|
2021-12-04 02:34:45 -05:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-01-09 15:39:37 -05:00
|
|
|
/*
|
|
|
|
Collections returns a slice of Collection items accessible to this Service.
|
|
|
|
|
|
|
|
err MAY be a *multierr.MultiError.
|
|
|
|
*/
|
2021-12-04 19:38:26 -05:00
|
|
|
func (s *Service) Collections() (collections []*Collection, err error) {
|
2021-11-21 18:07:52 -05:00
|
|
|
|
|
|
|
var paths []dbus.ObjectPath
|
|
|
|
var variant dbus.Variant
|
2021-11-30 02:33:07 -05:00
|
|
|
var coll *Collection
|
2022-01-09 15:39:37 -05:00
|
|
|
var errs *multierr.MultiError = multierr.NewMultiError()
|
2021-11-21 18:07:52 -05:00
|
|
|
|
2021-11-30 02:33:07 -05:00
|
|
|
if variant, err = s.Dbus.GetProperty(DbusServiceCollections); err != nil {
|
2021-11-21 18:07:52 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
paths = variant.Value().([]dbus.ObjectPath)
|
|
|
|
|
2021-12-18 00:57:41 -05:00
|
|
|
collections = make([]*Collection, 0)
|
2021-11-21 18:07:52 -05:00
|
|
|
|
2021-12-18 00:57:41 -05:00
|
|
|
for _, path := range paths {
|
|
|
|
coll = nil
|
2021-12-04 19:38:26 -05:00
|
|
|
if coll, err = NewCollection(s, path); err != nil {
|
2022-01-09 15:39:37 -05:00
|
|
|
errs.AddError(err)
|
2021-11-30 02:33:07 -05:00
|
|
|
err = nil
|
2021-12-04 19:38:26 -05:00
|
|
|
continue
|
2021-11-30 02:33:07 -05:00
|
|
|
}
|
2021-12-18 00:57:41 -05:00
|
|
|
collections = append(collections, coll)
|
2021-11-21 18:07:52 -05:00
|
|
|
}
|
2022-01-09 15:39:37 -05:00
|
|
|
|
|
|
|
if !errs.IsEmpty() {
|
|
|
|
err = errs
|
|
|
|
}
|
2021-11-21 18:07:52 -05:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-11-30 02:33:07 -05:00
|
|
|
/*
|
|
|
|
CreateAliasedCollection creates a new Collection (keyring) via a Service with the name specified by label,
|
|
|
|
aliased to the name specified by alias, and returns the new Collection.
|
|
|
|
*/
|
|
|
|
func (s *Service) CreateAliasedCollection(label, alias string) (collection *Collection, err error) {
|
2021-11-21 18:07:52 -05:00
|
|
|
|
2021-12-25 01:51:49 -05:00
|
|
|
var call *dbus.Call
|
2021-11-21 18:07:52 -05:00
|
|
|
var variant *dbus.Variant
|
|
|
|
var path dbus.ObjectPath
|
|
|
|
var promptPath dbus.ObjectPath
|
|
|
|
var prompt *Prompt
|
|
|
|
var props map[string]dbus.Variant = make(map[string]dbus.Variant)
|
|
|
|
|
2021-11-30 02:33:07 -05:00
|
|
|
props[DbusCollectionLabel] = dbus.MakeVariant(label)
|
2021-12-13 05:34:53 -05:00
|
|
|
props[DbusCollectionCreated] = dbus.MakeVariant(uint64(time.Now().Unix()))
|
|
|
|
props[DbusCollectionModified] = dbus.MakeVariant(uint64(time.Now().Unix()))
|
2021-11-21 18:07:52 -05:00
|
|
|
|
2021-12-25 01:51:49 -05:00
|
|
|
if call = s.Dbus.Call(
|
2021-11-30 02:33:07 -05:00
|
|
|
DbusServiceCreateCollection, 0, props, alias,
|
2021-12-25 01:51:49 -05:00
|
|
|
); call.Err != nil {
|
|
|
|
err = call.Err
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err = call.Store(&path, &promptPath); err != nil {
|
2021-11-21 18:07:52 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if isPrompt(promptPath) {
|
|
|
|
|
|
|
|
prompt = NewPrompt(s.Conn, promptPath)
|
|
|
|
if variant, err = prompt.Prompt(); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
path = variant.Value().(dbus.ObjectPath)
|
|
|
|
}
|
|
|
|
|
2021-12-04 19:38:26 -05:00
|
|
|
collection, err = NewCollection(s, path)
|
2021-11-21 18:07:52 -05:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-11-30 02:33:07 -05:00
|
|
|
/*
|
|
|
|
CreateCollection creates a new Collection (keyring) via a Service with the name specified by label and returns the new Collection.
|
|
|
|
It is a *very* thin wrapper around Service.CreateAliasedCollection, but with a blank alias.
|
|
|
|
*/
|
|
|
|
func (s *Service) CreateCollection(label string) (collection *Collection, err error) {
|
2021-11-21 18:07:52 -05:00
|
|
|
|
2021-11-30 02:33:07 -05:00
|
|
|
collection, err = s.CreateAliasedCollection(label, "")
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
2021-11-21 18:07:52 -05:00
|
|
|
|
2021-12-04 19:38:26 -05:00
|
|
|
/*
|
|
|
|
GetCollection returns a single Collection based on the name (name can also be an alias).
|
|
|
|
It's a helper function that avoids needing to make multiple calls in user code.
|
2022-01-09 15:39:37 -05:00
|
|
|
|
|
|
|
err MAY be a *multierr.MultiError.
|
2021-12-04 19:38:26 -05:00
|
|
|
*/
|
|
|
|
func (s *Service) GetCollection(name string) (c *Collection, err error) {
|
|
|
|
|
2022-01-09 15:39:37 -05:00
|
|
|
var errs *multierr.MultiError = multierr.NewMultiError()
|
2021-12-04 19:38:26 -05:00
|
|
|
var colls []*Collection
|
2021-12-13 04:04:03 -05:00
|
|
|
var pathName string
|
2021-12-04 19:38:26 -05:00
|
|
|
|
|
|
|
// First check for an alias.
|
2021-12-06 03:24:55 -05:00
|
|
|
if c, err = s.ReadAlias(name); err != nil && err != ErrDoesNotExist {
|
2021-12-13 04:04:03 -05:00
|
|
|
c = nil
|
2021-12-04 19:38:26 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if c != nil {
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
c = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// We didn't get it by alias, so let's try by name...
|
|
|
|
if colls, err = s.Collections(); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for _, i := range colls {
|
2021-12-13 04:04:03 -05:00
|
|
|
if pathName, err = NameFromPath(i.Dbus.Path()); err != nil {
|
2022-01-09 15:39:37 -05:00
|
|
|
errs.AddError(err)
|
2021-12-13 04:04:03 -05:00
|
|
|
err = nil
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if pathName == name {
|
2021-12-04 19:38:26 -05:00
|
|
|
c = i
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-12 02:29:29 -05:00
|
|
|
// Still nothing? Try by label.
|
|
|
|
for _, i := range colls {
|
2021-12-13 04:04:03 -05:00
|
|
|
if i.LabelName == name {
|
2021-12-12 02:29:29 -05:00
|
|
|
c = i
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-04 19:38:26 -05:00
|
|
|
// Couldn't find it by the given name.
|
2022-01-09 15:39:37 -05:00
|
|
|
if !errs.IsEmpty() {
|
|
|
|
err = errs
|
2021-12-12 02:29:29 -05:00
|
|
|
} else {
|
|
|
|
err = ErrDoesNotExist
|
|
|
|
}
|
2021-12-04 19:38:26 -05:00
|
|
|
|
2021-12-04 02:34:45 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-11-30 02:33:07 -05:00
|
|
|
/*
|
|
|
|
GetSecrets allows you to fetch values (Secret) from multiple Item object paths using this Service's Session.
|
|
|
|
An ErrMissingPaths will be returned for err if itemPaths is nil or empty.
|
|
|
|
The returned secrets is a map with itemPaths as the keys and their corresponding Secret as the value.
|
2021-12-13 04:04:03 -05:00
|
|
|
If you know which Collection your desired Secret is in, it is recommended to iterate through Collection.Items instead
|
|
|
|
(as Secrets returned here may have missing functionality).
|
2021-11-30 02:33:07 -05:00
|
|
|
*/
|
|
|
|
func (s *Service) GetSecrets(itemPaths ...dbus.ObjectPath) (secrets map[dbus.ObjectPath]*Secret, err error) {
|
|
|
|
|
2021-12-10 02:50:30 -05:00
|
|
|
/*
|
|
|
|
Results are in the form of a map with the value consisting of:
|
|
|
|
[]interface {}{
|
|
|
|
"/org/freedesktop/secrets/session/sNNN", // 0, session path
|
|
|
|
[]uint8{}, // 1, "params"
|
|
|
|
[]uint8{0x0}, // 2, value
|
|
|
|
"text/plain", // 3, content type
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
var results map[dbus.ObjectPath][]interface{}
|
2021-12-25 01:51:49 -05:00
|
|
|
var call *dbus.Call
|
2021-12-10 02:50:30 -05:00
|
|
|
|
2021-11-30 02:33:07 -05:00
|
|
|
if itemPaths == nil || len(itemPaths) == 0 {
|
|
|
|
err = ErrMissingPaths
|
2021-11-21 18:07:52 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-11-30 02:33:07 -05:00
|
|
|
secrets = make(map[dbus.ObjectPath]*Secret, len(itemPaths))
|
2021-12-10 02:50:30 -05:00
|
|
|
results = make(map[dbus.ObjectPath][]interface{}, len(itemPaths))
|
2021-11-30 02:33:07 -05:00
|
|
|
|
|
|
|
// TODO: trigger a Service.Unlock for any locked items?
|
2021-12-25 01:51:49 -05:00
|
|
|
if call = s.Dbus.Call(
|
2021-12-10 02:50:30 -05:00
|
|
|
DbusServiceGetSecrets, 0, itemPaths, s.Session.Dbus.Path(),
|
2021-12-25 01:51:49 -05:00
|
|
|
); call.Err != nil {
|
|
|
|
err = call.Err
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err = call.Store(&results); err != nil {
|
2021-11-30 02:33:07 -05:00
|
|
|
return
|
|
|
|
}
|
2021-11-21 18:07:52 -05:00
|
|
|
|
2021-12-10 02:50:30 -05:00
|
|
|
for p, r := range results {
|
|
|
|
secrets[p] = NewSecret(
|
|
|
|
s.Session, r[1].([]byte), r[2].([]byte), r[3].(string),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-11-30 02:33:07 -05:00
|
|
|
return
|
|
|
|
}
|
2021-11-21 18:07:52 -05:00
|
|
|
|
2021-12-06 03:24:55 -05:00
|
|
|
/*
|
|
|
|
GetSession returns a single Session.
|
|
|
|
It's a helper function that wraps Service.OpenSession.
|
|
|
|
*/
|
|
|
|
func (s *Service) GetSession() (ssn *Session, err error) {
|
|
|
|
|
|
|
|
ssn, _, err = s.OpenSession("", "")
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-01-09 17:11:50 -05:00
|
|
|
// Scrapping this idea for now; it would require introspection on a known Item path.
|
|
|
|
/*
|
|
|
|
IsLegacy indicates with a decent likelihood of accuracy if this Service is
|
|
|
|
connected to a legacy spec Secret Service (true) or if the spec is current (false).
|
|
|
|
|
|
|
|
It also returns a confidence indicator as a float, which indicates how accurate
|
|
|
|
the guess (because it is a guess) may/is likely to be (as a percentage). For example,
|
|
|
|
if confidence is expressed as 0.25, the result of legacyAPI has a 25% of being accurate.
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
func (s *Service) IsLegacy() (legacyAPI bool, confidence int) {
|
|
|
|
|
|
|
|
var maxCon int
|
|
|
|
|
|
|
|
// Test 1, property introspection on Item. We're looking for a Type property.
|
|
|
|
DbusInterfaceItem
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
2021-12-13 04:04:03 -05:00
|
|
|
// Lock locks an Unlocked Collection or Item (LockableObject).
|
|
|
|
func (s *Service) Lock(objects ...LockableObject) (err error) {
|
2021-11-30 02:33:07 -05:00
|
|
|
|
2021-12-25 01:51:49 -05:00
|
|
|
var call *dbus.Call
|
2021-12-13 04:04:03 -05:00
|
|
|
var toLock []dbus.ObjectPath
|
2021-12-06 03:24:55 -05:00
|
|
|
// We only use these as destinations.
|
2021-11-30 02:33:07 -05:00
|
|
|
var locked []dbus.ObjectPath
|
|
|
|
var prompt *Prompt
|
2021-12-10 02:50:30 -05:00
|
|
|
var promptPath dbus.ObjectPath
|
2021-11-30 02:33:07 -05:00
|
|
|
|
2021-12-13 04:04:03 -05:00
|
|
|
if objects == nil || len(objects) == 0 {
|
|
|
|
err = ErrMissingObj
|
|
|
|
return
|
2021-11-30 02:33:07 -05:00
|
|
|
}
|
|
|
|
|
2021-12-13 04:04:03 -05:00
|
|
|
toLock = make([]dbus.ObjectPath, len(objects))
|
|
|
|
|
|
|
|
for idx, o := range objects {
|
|
|
|
toLock[idx] = o.path()
|
|
|
|
}
|
|
|
|
|
2021-12-25 01:51:49 -05:00
|
|
|
if call = s.Dbus.Call(
|
2021-12-13 05:34:53 -05:00
|
|
|
DbusServiceLock, 0, toLock,
|
2021-12-25 01:51:49 -05:00
|
|
|
); call.Err != nil {
|
|
|
|
err = call.Err
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err = call.Store(&locked, &promptPath); err != nil {
|
2021-12-10 02:50:30 -05:00
|
|
|
return
|
|
|
|
}
|
2021-11-30 02:33:07 -05:00
|
|
|
|
2021-12-10 02:50:30 -05:00
|
|
|
if isPrompt(promptPath) {
|
2021-11-30 02:33:07 -05:00
|
|
|
|
2021-12-10 02:50:30 -05:00
|
|
|
prompt = NewPrompt(s.Conn, promptPath)
|
2021-11-30 02:33:07 -05:00
|
|
|
|
2021-12-10 02:50:30 -05:00
|
|
|
if _, err = prompt.Prompt(); err != nil {
|
|
|
|
return
|
2021-11-30 02:33:07 -05:00
|
|
|
}
|
2021-11-21 18:07:52 -05:00
|
|
|
}
|
|
|
|
|
2021-12-13 04:33:43 -05:00
|
|
|
// TODO: use channels and goroutines here.
|
2021-12-13 04:04:03 -05:00
|
|
|
for _, o := range objects {
|
2021-12-13 04:33:43 -05:00
|
|
|
if _, err = o.Locked(); err != nil {
|
|
|
|
return
|
|
|
|
}
|
2021-12-13 04:04:03 -05:00
|
|
|
}
|
|
|
|
|
2021-11-21 18:07:52 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-12-04 02:34:45 -05:00
|
|
|
/*
|
2021-12-06 03:24:55 -05:00
|
|
|
OpenSession returns a pointer to a Session from the Service.
|
2021-12-04 02:34:45 -05:00
|
|
|
It's a convenience function around NewSession.
|
2021-12-13 04:04:03 -05:00
|
|
|
However, NewService attaches a Session by default at Service.Session so this is likely unnecessary.
|
2021-12-04 02:34:45 -05:00
|
|
|
*/
|
2021-12-06 03:24:55 -05:00
|
|
|
func (s *Service) OpenSession(algo, input string) (session *Session, output dbus.Variant, err error) {
|
2021-11-21 18:07:52 -05:00
|
|
|
|
2021-12-25 01:51:49 -05:00
|
|
|
var call *dbus.Call
|
2021-11-30 02:33:07 -05:00
|
|
|
var path dbus.ObjectPath
|
2021-12-06 03:24:55 -05:00
|
|
|
var inputVariant dbus.Variant
|
|
|
|
|
|
|
|
if strings.TrimSpace(algo) == "" {
|
2021-12-07 02:56:15 -05:00
|
|
|
algo = "plain"
|
2021-12-06 03:24:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
inputVariant = dbus.MakeVariant(input)
|
2021-11-21 18:07:52 -05:00
|
|
|
|
2021-11-30 02:33:07 -05:00
|
|
|
// In *theory*, SecretService supports multiple "algorithms" for encryption in-transit, but I don't think it's implemented (yet)?
|
|
|
|
// TODO: confirm this.
|
|
|
|
// Possible flags are dbus.Flags consts: https://pkg.go.dev/github.com/godbus/dbus#Flags
|
|
|
|
// Oddly, there is no "None" flag. So it's explicitly specified as a null byte.
|
2021-12-25 01:51:49 -05:00
|
|
|
if call = s.Dbus.Call(
|
2021-12-07 02:56:15 -05:00
|
|
|
DbusServiceOpenSession, 0, algo, inputVariant,
|
2021-12-25 01:51:49 -05:00
|
|
|
); call.Err != nil {
|
|
|
|
err = call.Err
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err = call.Store(&output, &path); err != nil {
|
2021-11-21 18:07:52 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-12-08 02:34:27 -05:00
|
|
|
session, err = NewSession(s, path)
|
2021-12-04 02:34:45 -05:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-12-06 03:24:55 -05:00
|
|
|
/*
|
|
|
|
ReadAlias allows one to fetch a Collection based on an alias name.
|
|
|
|
An ErrDoesNotExist will be raised if the alias does not exist.
|
|
|
|
You will almost assuredly want to use Service.GetCollection instead; it works for both alias names and real names.
|
|
|
|
*/
|
|
|
|
func (s *Service) ReadAlias(alias string) (collection *Collection, err error) {
|
|
|
|
|
2021-12-25 01:51:49 -05:00
|
|
|
var call *dbus.Call
|
2021-12-06 03:24:55 -05:00
|
|
|
var objectPath dbus.ObjectPath
|
|
|
|
|
2021-12-25 01:51:49 -05:00
|
|
|
if call = s.Dbus.Call(
|
2021-12-06 03:24:55 -05:00
|
|
|
DbusServiceReadAlias, 0, alias,
|
2021-12-25 01:51:49 -05:00
|
|
|
); call.Err != nil {
|
|
|
|
err = call.Err
|
|
|
|
return
|
|
|
|
}
|
2021-12-06 03:24:55 -05:00
|
|
|
/*
|
|
|
|
TODO: Confirm that a nonexistent alias will NOT cause an error to return.
|
|
|
|
If it does, alter the below logic.
|
|
|
|
*/
|
2021-12-25 01:51:49 -05:00
|
|
|
if err = call.Store(&objectPath); err != nil {
|
2021-12-06 03:24:55 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the alias does not exist, objectPath will be dbus.ObjectPath("/").
|
|
|
|
if objectPath == dbus.ObjectPath("/") {
|
|
|
|
err = ErrDoesNotExist
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if collection, err = NewCollection(s, objectPath); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-12-13 04:04:03 -05:00
|
|
|
// RemoveAlias is a thin wrapper around Service.SetAlias using the removal method specified there.
|
|
|
|
func (s *Service) RemoveAlias(alias string) (err error) {
|
|
|
|
|
2021-12-13 05:34:53 -05:00
|
|
|
if err = s.SetAlias(alias, DbusRemoveAliasPath); err != nil {
|
2021-12-13 04:04:03 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-12-04 02:34:45 -05:00
|
|
|
/*
|
|
|
|
SearchItems searches all Collection objects and returns all matches based on the map of attributes.
|
2022-01-09 15:39:37 -05:00
|
|
|
|
|
|
|
err MAY be a *multierr.MultiError.
|
2021-12-04 02:34:45 -05:00
|
|
|
*/
|
2021-12-06 03:24:55 -05:00
|
|
|
func (s *Service) SearchItems(attributes map[string]string) (unlockedItems []*Item, lockedItems []*Item, err error) {
|
|
|
|
|
2021-12-25 01:51:49 -05:00
|
|
|
var call *dbus.Call
|
2021-12-06 03:24:55 -05:00
|
|
|
var locked []dbus.ObjectPath
|
|
|
|
var unlocked []dbus.ObjectPath
|
|
|
|
var collectionObjs []*Collection
|
|
|
|
var collections map[dbus.ObjectPath]*Collection = make(map[dbus.ObjectPath]*Collection, 0)
|
|
|
|
var ok bool
|
|
|
|
var c *Collection
|
|
|
|
var cPath dbus.ObjectPath
|
2021-12-18 00:57:41 -05:00
|
|
|
var item *Item
|
2022-01-09 15:39:37 -05:00
|
|
|
var errs *multierr.MultiError = multierr.NewMultiError()
|
2021-12-04 02:34:45 -05:00
|
|
|
|
2021-12-04 19:38:26 -05:00
|
|
|
if attributes == nil || len(attributes) == 0 {
|
|
|
|
err = ErrMissingAttrs
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-12-25 01:51:49 -05:00
|
|
|
if call = s.Dbus.Call(
|
2021-12-04 02:34:45 -05:00
|
|
|
DbusServiceSearchItems, 0, attributes,
|
2021-12-25 01:51:49 -05:00
|
|
|
); call.Err != nil {
|
|
|
|
}
|
|
|
|
if err = call.Store(&unlocked, &locked); err != nil {
|
|
|
|
return
|
|
|
|
}
|
2021-12-06 03:24:55 -05:00
|
|
|
|
2021-12-18 00:57:41 -05:00
|
|
|
lockedItems = make([]*Item, 0)
|
|
|
|
unlockedItems = make([]*Item, 0)
|
2021-12-06 03:24:55 -05:00
|
|
|
|
|
|
|
if collectionObjs, err = s.Collections(); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, c = range collectionObjs {
|
|
|
|
if _, ok = collections[c.Dbus.Path()]; !ok {
|
|
|
|
collections[c.Dbus.Path()] = c
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Locked items
|
2021-12-18 00:57:41 -05:00
|
|
|
for _, i := range locked {
|
2021-12-06 03:24:55 -05:00
|
|
|
|
2021-12-18 00:57:41 -05:00
|
|
|
item = nil
|
2021-12-06 03:24:55 -05:00
|
|
|
cPath = dbus.ObjectPath(filepath.Dir(string(i)))
|
|
|
|
|
|
|
|
if c, ok = collections[cPath]; !ok {
|
2022-01-09 15:39:37 -05:00
|
|
|
errs.AddError(errors.New(fmt.Sprintf(
|
2021-12-06 03:24:55 -05:00
|
|
|
"could not find matching Collection for locked item %v", string(i),
|
|
|
|
)))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2021-12-18 00:57:41 -05:00
|
|
|
if item, err = NewItem(c, i); err != nil {
|
2022-01-09 15:39:37 -05:00
|
|
|
errs.AddError(errors.New(fmt.Sprintf(
|
|
|
|
"could not create Item for locked item %v; error follows", string(i),
|
2021-12-06 03:24:55 -05:00
|
|
|
)))
|
2022-01-09 15:39:37 -05:00
|
|
|
errs.AddError(err)
|
2021-12-06 03:24:55 -05:00
|
|
|
err = nil
|
|
|
|
continue
|
|
|
|
}
|
2021-12-18 00:57:41 -05:00
|
|
|
lockedItems = append(lockedItems, item)
|
2021-12-06 03:24:55 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Unlocked items
|
2021-12-18 00:57:41 -05:00
|
|
|
for _, i := range unlocked {
|
2021-12-06 03:24:55 -05:00
|
|
|
|
2021-12-18 00:57:41 -05:00
|
|
|
item = nil
|
2021-12-06 03:24:55 -05:00
|
|
|
cPath = dbus.ObjectPath(filepath.Dir(string(i)))
|
|
|
|
|
|
|
|
if c, ok = collections[cPath]; !ok {
|
2022-01-09 15:39:37 -05:00
|
|
|
errs.AddError(errors.New(fmt.Sprintf(
|
2021-12-06 03:24:55 -05:00
|
|
|
"could not find matching Collection for unlocked item %v", string(i),
|
|
|
|
)))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2021-12-18 00:57:41 -05:00
|
|
|
if item, err = NewItem(c, i); err != nil {
|
2022-01-09 15:39:37 -05:00
|
|
|
errs.AddError(errors.New(fmt.Sprintf(
|
|
|
|
"could not create Item for unlocked item %v; error follows", string(i),
|
2021-12-06 03:24:55 -05:00
|
|
|
)))
|
2022-01-09 15:39:37 -05:00
|
|
|
errs.AddError(err)
|
2021-12-06 03:24:55 -05:00
|
|
|
err = nil
|
|
|
|
continue
|
|
|
|
}
|
2021-12-18 00:57:41 -05:00
|
|
|
unlockedItems = append(unlockedItems, item)
|
2021-12-06 03:24:55 -05:00
|
|
|
}
|
|
|
|
|
2022-01-09 15:39:37 -05:00
|
|
|
if !errs.IsEmpty() {
|
|
|
|
err = errs
|
2021-12-06 03:24:55 -05:00
|
|
|
}
|
2021-12-04 02:34:45 -05:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
SetAlias sets an alias for an existing Collection.
|
2021-12-13 04:04:03 -05:00
|
|
|
(You can get its path via <Collection>.Dbus.Path().)
|
2021-12-04 02:34:45 -05:00
|
|
|
To remove an alias, set objectPath to dbus.ObjectPath("/").
|
|
|
|
*/
|
|
|
|
func (s *Service) SetAlias(alias string, objectPath dbus.ObjectPath) (err error) {
|
|
|
|
|
2021-12-25 01:51:49 -05:00
|
|
|
var call *dbus.Call
|
2021-12-13 05:34:53 -05:00
|
|
|
var collection *Collection
|
|
|
|
|
|
|
|
if collection, err = s.GetCollection(alias); err != nil {
|
|
|
|
return
|
|
|
|
}
|
2021-12-04 02:34:45 -05:00
|
|
|
|
2021-12-25 01:51:49 -05:00
|
|
|
if call = s.Dbus.Call(
|
2021-12-04 02:34:45 -05:00
|
|
|
DbusServiceSetAlias, 0, alias, objectPath,
|
2021-12-25 01:51:49 -05:00
|
|
|
); call.Err != nil {
|
|
|
|
err = call.Err
|
2021-12-13 05:34:53 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if objectPath == DbusRemoveAliasPath {
|
|
|
|
collection.Alias = ""
|
|
|
|
} else {
|
|
|
|
collection.Alias = alias
|
|
|
|
}
|
2021-11-21 18:07:52 -05:00
|
|
|
|
2021-11-30 02:33:07 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-12-13 04:04:03 -05:00
|
|
|
// Unlock unlocks a locked Collection or Item (LockableObject).
|
|
|
|
func (s *Service) Unlock(objects ...LockableObject) (err error) {
|
2021-11-21 18:07:52 -05:00
|
|
|
|
2021-12-25 01:51:49 -05:00
|
|
|
var call *dbus.Call
|
2021-12-13 04:04:03 -05:00
|
|
|
var toUnlock []dbus.ObjectPath
|
|
|
|
// We only use these as destinations.
|
2021-11-30 02:33:07 -05:00
|
|
|
var unlocked []dbus.ObjectPath
|
|
|
|
var prompt *Prompt
|
|
|
|
var resultPath dbus.ObjectPath
|
|
|
|
|
2021-12-13 04:04:03 -05:00
|
|
|
if objects == nil || len(objects) == 0 {
|
|
|
|
err = ErrMissingObj
|
|
|
|
return
|
2021-11-30 02:33:07 -05:00
|
|
|
}
|
|
|
|
|
2021-12-13 04:04:03 -05:00
|
|
|
toUnlock = make([]dbus.ObjectPath, len(objects))
|
|
|
|
|
|
|
|
for idx, o := range objects {
|
|
|
|
toUnlock[idx] = o.path()
|
|
|
|
}
|
|
|
|
|
2021-12-25 01:51:49 -05:00
|
|
|
if call = s.Dbus.Call(
|
2021-12-13 05:34:53 -05:00
|
|
|
DbusServiceUnlock, 0, toUnlock,
|
2021-12-25 01:51:49 -05:00
|
|
|
); call.Err != nil {
|
|
|
|
err = call.Err
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err = call.Store(&unlocked, &resultPath); err != nil {
|
2021-12-10 02:50:30 -05:00
|
|
|
return
|
|
|
|
}
|
2021-11-30 02:33:07 -05:00
|
|
|
|
2021-12-10 02:50:30 -05:00
|
|
|
if isPrompt(resultPath) {
|
2021-11-30 02:33:07 -05:00
|
|
|
|
2021-12-10 02:50:30 -05:00
|
|
|
prompt = NewPrompt(s.Conn, resultPath)
|
2021-11-30 02:33:07 -05:00
|
|
|
|
2021-12-10 02:50:30 -05:00
|
|
|
if _, err = prompt.Prompt(); err != nil {
|
|
|
|
return
|
2021-11-30 02:33:07 -05:00
|
|
|
}
|
2021-11-21 18:07:52 -05:00
|
|
|
}
|
|
|
|
|
2021-12-13 04:33:43 -05:00
|
|
|
// TODO: use channels and goroutines here.
|
2021-12-13 04:04:03 -05:00
|
|
|
for _, o := range objects {
|
2021-12-13 04:33:43 -05:00
|
|
|
if _, err = o.Locked(); err != nil {
|
|
|
|
return
|
|
|
|
}
|
2021-12-13 04:04:03 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// path is a *very* thin wrapper around Service.Dbus.Path().
|
|
|
|
func (s *Service) path() (dbusPath dbus.ObjectPath) {
|
|
|
|
|
|
|
|
dbusPath = s.Dbus.Path()
|
|
|
|
|
2021-11-21 18:07:52 -05:00
|
|
|
return
|
|
|
|
}
|