Compare commits

...

5 Commits

Author SHA1 Message Date
brent s. aa8aef4ccf
adding convenience function to check (in a very basic manner) if an API spec is legacy. 2022-01-09 17:11:50 -05:00
brent s. d13b263222
wrap errors
Implement error wrapping so we catch all errors into a single error.
2022-01-09 15:53:50 -05:00
brent s. b4419a6f8c
Workaround for missing Type property
Some poor decisions that have been made made by KeePassXC lead to the case where they diverge from libsecret's implementation and implement their own incompatible API in the same Dbus namespace.

But they still call their API "Secret Service".

 Well then.
2022-01-09 00:56:56 -05:00
brent s. bb85cb8b52
cleanly close, catch Dbus errors 2021-12-25 01:51:49 -05:00
brent s. 9d3299c9dc
multierror update, fix output 2021-12-24 05:50:31 -05:00
13 changed files with 322 additions and 153 deletions

View File

@ -224,6 +224,8 @@ func main() {
}
----

Note that many functions/methods may return a https://pkg.go.dev/r00t2.io/goutils/multierr#MultiError[`(r00t2.io/goutils/)multierr.MultiError`^], which you may attempt to typeswitch to receive the original errors in their native error format. The functions/methods which may return a MultiError are noted as such in their individual documentation.

== Library Hacking

=== Reference

2
TODO
View File

@ -1 +1,3 @@
- Benchmarking?

- call .Close() on dbus.Conns

View File

@ -4,6 +4,7 @@ import (
"time"

"github.com/godbus/dbus/v5"
"r00t2.io/goutils/multierr"
)

/*
@ -65,6 +66,7 @@ func NewCollection(service *Service, path dbus.ObjectPath) (coll *Collection, er
*/
func (c *Collection) CreateItem(label string, attrs map[string]string, secret *Secret, replace bool, itemType ...string) (item *Item, err error) {

var call *dbus.Call
var prompt *Prompt
var path dbus.ObjectPath
var promptPath dbus.ObjectPath
@ -79,14 +81,20 @@ func (c *Collection) CreateItem(label string, attrs map[string]string, secret *S
}

props[DbusItemLabel] = dbus.MakeVariant(label)
props[DbusItemType] = dbus.MakeVariant(typeString)
if !c.service.Legacy {
props[DbusItemType] = dbus.MakeVariant(typeString)
}
props[DbusItemAttributes] = dbus.MakeVariant(attrs)
props[DbusItemCreated] = dbus.MakeVariant(uint64(time.Now().Unix()))
// props[DbusItemModified] = dbus.MakeVariant(uint64(time.Now().Unix()))

if err = c.Dbus.Call(
if call = c.Dbus.Call(
DbusCollectionCreateItem, 0, props, secret, replace,
).Store(&path, &promptPath); err != nil {
); call.Err != nil {
err = call.Err
return
}
if err = call.Store(&path, &promptPath); err != nil {
return
}

@ -114,10 +122,17 @@ func (c *Collection) CreateItem(label string, attrs map[string]string, secret *S
*/
func (c *Collection) Delete() (err error) {

var call *dbus.Call
var promptPath dbus.ObjectPath
var prompt *Prompt

if err = c.Dbus.Call(DbusCollectionDelete, 0).Store(&promptPath); err != nil {
if call = c.Dbus.Call(
DbusCollectionDelete, 0,
); call.Err != nil {
err = call.Err
return
}
if err = call.Store(&promptPath); err != nil {
return
}

@ -132,13 +147,16 @@ func (c *Collection) Delete() (err error) {
return
}

// Items returns a slice of Item pointers in the Collection.
/*
Items returns a slice of Item pointers in the Collection.
err MAY be a *multierr.MultiError.
*/
func (c *Collection) Items() (items []*Item, err error) {

var paths []dbus.ObjectPath
var item *Item
var variant dbus.Variant
var errs []error = make([]error, 0)
var errs *multierr.MultiError = multierr.NewMultiError()

if variant, err = c.Dbus.GetProperty(DbusCollectionItems); err != nil {
return
@ -151,13 +169,16 @@ func (c *Collection) Items() (items []*Item, err error) {
for _, path := range paths {
item = nil
if item, err = NewItem(c, path); err != nil {
errs = append(errs, err)
errs.AddError(err)
err = nil
continue
}
items = append(items, item)
}
err = NewErrors(err)

if !errs.IsEmpty() {
err = errs
}

return
}
@ -234,25 +255,32 @@ func (c *Collection) Relabel(newLabel string) (err error) {
}

/*
SearchItems searches a Collection for a matching profile string.
SearchItems searches a Collection for a matching "profile" string.
It's mostly a carry-over from go-libsecret, and is here for convenience. IT MAY BE REMOVED IN THE FUTURE.

I promise it's not useful for any other implementation/storage of SecretService whatsoever.

err MAY be a *multierr.MultiError.

Deprecated: Use Service.SearchItems instead.
*/
func (c *Collection) SearchItems(profile string) (items []*Item, err error) {

var call *dbus.Call
var paths []dbus.ObjectPath
var errs []error = make([]error, 0)
var errs *multierr.MultiError = multierr.NewMultiError()
var attrs map[string]string = make(map[string]string, 0)
var item *Item

attrs["profile"] = profile

if err = c.Dbus.Call(
if call = c.Dbus.Call(
DbusCollectionSearchItems, 0, attrs,
).Store(&paths); err != nil {
); call.Err != nil {
err = call.Err
return
}
if err = call.Store(&paths); err != nil {
return
}

@ -261,13 +289,16 @@ func (c *Collection) SearchItems(profile string) (items []*Item, err error) {
for _, path := range paths {
item = nil
if item, err = NewItem(c, path); err != nil {
errs = append(errs, err)
errs.AddError(err)
err = nil
continue
}
items = append(items, item)
}
err = NewErrors(err)

if !errs.IsEmpty() {
err = errs
}

return
}
@ -277,11 +308,10 @@ func (c *Collection) SetAlias(alias string) (err error) {

var call *dbus.Call

call = c.service.Dbus.Call(
if call = c.service.Dbus.Call(
DbusServiceSetAlias, 0, alias, c.Dbus.Path(),
)

if err = call.Err; err != nil {
); call.Err != nil {
err = call.Err
return
}


View File

@ -1,9 +1,9 @@
package gosecret

import (
`testing`
"testing"

`github.com/godbus/dbus/v5`
"github.com/godbus/dbus/v5"
)

// Some functions are covered in the Service tests.
@ -57,7 +57,8 @@ func TestCollection_Items(t *testing.T) {
var collection *Collection
var items []*Item
var item *Item
var searchItemResults []*Item
var searchResultsUnlocked []*Item
var searchResultsLocked []*Item
var secret *Secret
var err error

@ -109,12 +110,12 @@ func TestCollection_Items(t *testing.T) {
)
} else {

if searchItemResults, err = collection.SearchItems(testItemLabel); err != nil {
if searchResultsUnlocked, searchResultsLocked, err = collection.service.SearchItems(itemAttrs); err != nil {
t.Errorf("failed to find item '%v' via Collection.SearchItems: %v", string(item.Dbus.Path()), err.Error())
} else if len(searchItemResults) == 0 {
} else if (len(searchResultsLocked) + len(searchResultsUnlocked)) == 0 {
t.Errorf("failed to find item '%v' via Collection.SearchItems, returned 0 results (should be at least 1)", testItemLabel)
} else {
t.Logf("found %v results for Collection.SearchItems", len(searchItemResults))
t.Logf("found %v results for Collection.SearchItems", len(searchResultsUnlocked)+len(searchResultsLocked))
}

if err = item.Delete(); err != nil {

4
doc.go
View File

@ -84,5 +84,9 @@ Usage

Full documentation can be found via inline documentation.
Additionally, use either https://pkg.go.dev/r00t2.io/gosecret or https://pkg.go.dev/golang.org/x/tools/cmd/godoc (or `go doc`) in the source root.

Note that many functions/methods may return a (r00t2.io/goutils/)multierr.MultiError (https://pkg.go.dev/r00t2.io/goutils/multierr#MultiError),
which you may attempt to typeswitch back to a *multierr.MultiErr to receive the original errors in their native error format (MultiError.Errors).
The functions/methods which may return a MultiError are noted as such in their individual documentation.
*/
package gosecret

View File

@ -4,6 +4,7 @@ import (
`strings`

`github.com/godbus/dbus/v5`
`r00t2.io/goutils/multierr`
)

// isPrompt returns a boolean that is true if path is/requires a prompt(ed path) and false if it is/does not.
@ -63,19 +64,27 @@ func pathIsValid(path interface{}) (ok bool, err error) {

/*
validConnPath condenses the checks for connIsValid and pathIsValid into one func due to how frequently this check is done.
err is a MultiError, which can be treated as an error.error. (See https://pkg.go.dev/builtin#error)

If err is not nil, it IS a *multierr.MultiError.
*/
func validConnPath(conn *dbus.Conn, path interface{}) (cr *ConnPathCheckResult, err error) {

var connErr error
var pathErr error
var errs *multierr.MultiError = multierr.NewMultiError()

cr = new(ConnPathCheckResult)

cr.ConnOK, connErr = connIsValid(conn)
cr.PathOK, pathErr = pathIsValid(path)
if cr.ConnOK, err = connIsValid(conn); err != nil {
errs.AddError(err)
err = nil
}
if cr.PathOK, err = pathIsValid(path); err != nil {
errs.AddError(err)
err = nil
}

err = NewErrors(connErr, pathErr)
if !errs.IsEmpty() {
err = errs
}

return
}
@ -144,3 +153,56 @@ func NameFromPath(path dbus.ObjectPath) (name string, err error) {

return
}

/*
CheckErrIsFromLegacy takes an error.Error from e.g.:

Service.SearchItems
Collection.CreateItem
NewItem
Item.ChangeItemType
Item.Type

and (in order) attempt to typeswitch to a *multierr.MultiError, then iterate through
the *multierr.MultiError.Errors, attempt to typeswitch each of them to a Dbus.Error, and then finally
check if it is regarding a missing Type property.

This is *very explicitly* only useful for the above functions/methods. If used anywhere else,
it's liable to return an incorrect isLegacy even if parsed == true.

It is admittedly convoluted and obtuse, but this saves a lot of boilerplate for users.
It wouldn't be necessary if projects didn't insist on using the legacy draft SecretService specification.
But here we are.

isLegacy is true if this Service's API destination is legacy spec. Note that this is checking for
very explicit conditions; isLegacy may return false but it is in fact running on a legacy API.
Don't rely on this too much.

parsed is true if we found an error type we were able to perform logic of determination on.
*/
func CheckErrIsFromLegacy(err error) (isLegacy, parsed bool) {

switch e := err.(type) {
case *multierr.MultiError:
parsed = true
for _, i := range e.Errors {
switch e2 := i.(type) {
case dbus.Error:
if e2.Name == "org.freedesktop.DBus.Error.UnknownProperty" {
isLegacy = true
return
}
default:
continue
}
}
case dbus.Error:
parsed = true
if e.Name == "org.freedesktop.DBus.Error.UnknownProperty" {
isLegacy = true
return
}
}

return
}

1
go.mod
View File

@ -5,4 +5,5 @@ go 1.17
require (
github.com/godbus/dbus/v5 v5.0.6
github.com/google/uuid v1.3.0
r00t2.io/goutils v1.1.2
)

5
go.sum
View File

@ -1,4 +1,9 @@
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
r00t2.io/goutils v1.1.2 h1:zOOqNHQ/HpJVggV5NTXBcd7FQtBP2C/sMLkHw3YvBzU=
r00t2.io/goutils v1.1.2/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk=
r00t2.io/sysutils v1.1.1/go.mod h1:Wlfi1rrJpoKBOjWiYM9rw2FaiZqraD6VpXyiHgoDo/o=

View File

@ -85,6 +85,11 @@ func (i *Item) ChangeItemType(newItemType string) (err error) {

var variant dbus.Variant

// Legacy spec.
if i.collection.service.Legacy {
return
}

if strings.TrimSpace(newItemType) == "" {
newItemType = DbusDefaultItemType
}
@ -106,10 +111,17 @@ func (i *Item) ChangeItemType(newItemType string) (err error) {
// Delete removes an Item from a Collection.
func (i *Item) Delete() (err error) {

var call *dbus.Call
var promptPath dbus.ObjectPath
var prompt *Prompt

if err = i.Dbus.Call(DbusItemDelete, 0).Store(&promptPath); err != nil {
if call = i.Dbus.Call(
DbusItemDelete, 0,
); call.Err != nil {
err = call.Err
return
}
if err = call.Store(&promptPath); err != nil {
return
}

@ -127,6 +139,8 @@ func (i *Item) Delete() (err error) {
// GetSecret returns the Secret in an Item using a Session.
func (i *Item) GetSecret(session *Session) (secret *Secret, err error) {

var call *dbus.Call

if session == nil {
err = ErrNoDbusConn
}
@ -135,9 +149,13 @@ func (i *Item) GetSecret(session *Session) (secret *Secret, err error) {
return
}

if err = i.Dbus.Call(
if call = i.Dbus.Call(
DbusItemGetSecret, 0, session.Dbus.Path(),
).Store(&secret); err != nil {
); call.Err != nil {
err = call.Err
return
}
if err = call.Store(&secret); err != nil {
return
}

@ -246,15 +264,15 @@ func (i *Item) ReplaceAttributes(newAttrs map[string]string) (err error) {
// SetSecret sets the Secret for an Item.
func (i *Item) SetSecret(secret *Secret) (err error) {

var c *dbus.Call
var call *dbus.Call

c = i.Dbus.Call(
if call = i.Dbus.Call(
DbusItemSetSecret, 0,
)
if c.Err != nil {
err = c.Err
); call.Err != nil {
err = call.Err
return
}

i.Secret = secret

if _, _, err = i.Modified(); err != nil {
@ -269,6 +287,11 @@ func (i *Item) Type() (itemType string, err error) {

var variant dbus.Variant

// Legacy spec.
if i.collection.service.Legacy {
return
}

if variant, err = i.Dbus.GetProperty(DbusItemType); err != nil {
return
}

View File

@ -1,57 +0,0 @@
package gosecret

import (
`fmt`
)

/*
NewErrors returns a new MultiError based on a slice of error.Error (errs).
Any nil errors are trimmed. If there are no actual errors after trimming, err will be nil.
*/
func NewErrors(errs ...error) (err error) {

if errs == nil || len(errs) == 0 {
return
}

var realErrs []error = make([]error, 0)

for _, e := range errs {
if e == nil {
continue
}
realErrs = append(realErrs, e)
}

if len(realErrs) == 0 {
return
}

err = &MultiError{
Errors: realErrs,
ErrorSep: "\n",
}

return
}

func (e *MultiError) Error() (errStr string) {

var numErrs int

if e == nil || len(e.Errors) == 0 {
return
} else {
numErrs = len(e.Errors)
}

for idx, err := range e.Errors {
if (idx + 1) < numErrs {
errStr += fmt.Sprintf(err.Error(), e.ErrorSep)
} else {
errStr += err.Error()
}
}

return
}

View File

@ -5,9 +5,10 @@ import (
"fmt"
"path/filepath"
"strings"
`time`
"time"

"github.com/godbus/dbus/v5"
`r00t2.io/goutils/multierr`
)

// NewService returns a pointer to a new Service connection.
@ -38,18 +39,28 @@ func NewService() (service *Service, err error) {
// Close cleanly closes a Service and all its underlying connections (e.g. Service.Session).
func (s *Service) Close() (err error) {

err = s.Session.Close()
if err = s.Session.Close(); err != nil {
return
}

if err = s.Conn.Close(); err != nil {
return
}

return
}

// Collections returns a slice of Collection items accessible to this Service.
/*
Collections returns a slice of Collection items accessible to this Service.

err MAY be a *multierr.MultiError.
*/
func (s *Service) Collections() (collections []*Collection, err error) {

var paths []dbus.ObjectPath
var variant dbus.Variant
var coll *Collection
var errs []error = make([]error, 0)
var errs *multierr.MultiError = multierr.NewMultiError()

if variant, err = s.Dbus.GetProperty(DbusServiceCollections); err != nil {
return
@ -62,14 +73,16 @@ func (s *Service) Collections() (collections []*Collection, err error) {
for _, path := range paths {
coll = nil
if coll, err = NewCollection(s, path); err != nil {
// return
errs = append(errs, err)
errs.AddError(err)
err = nil
continue
}
collections = append(collections, coll)
}
err = NewErrors(err)

if !errs.IsEmpty() {
err = errs
}

return
}
@ -80,6 +93,7 @@ func (s *Service) Collections() (collections []*Collection, err error) {
*/
func (s *Service) CreateAliasedCollection(label, alias string) (collection *Collection, err error) {

var call *dbus.Call
var variant *dbus.Variant
var path dbus.ObjectPath
var promptPath dbus.ObjectPath
@ -90,9 +104,13 @@ func (s *Service) CreateAliasedCollection(label, alias string) (collection *Coll
props[DbusCollectionCreated] = dbus.MakeVariant(uint64(time.Now().Unix()))
props[DbusCollectionModified] = dbus.MakeVariant(uint64(time.Now().Unix()))

if err = s.Dbus.Call(
if call = s.Dbus.Call(
DbusServiceCreateCollection, 0, props, alias,
).Store(&path, &promptPath); err != nil {
); call.Err != nil {
err = call.Err
return
}
if err = call.Store(&path, &promptPath); err != nil {
return
}

@ -125,10 +143,12 @@ func (s *Service) CreateCollection(label string) (collection *Collection, err er
/*
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.

err MAY be a *multierr.MultiError.
*/
func (s *Service) GetCollection(name string) (c *Collection, err error) {

var errs []error = make([]error, 0)
var errs *multierr.MultiError = multierr.NewMultiError()
var colls []*Collection
var pathName string

@ -149,7 +169,7 @@ func (s *Service) GetCollection(name string) (c *Collection, err error) {
}
for _, i := range colls {
if pathName, err = NameFromPath(i.Dbus.Path()); err != nil {
errs = append(errs, err)
errs.AddError(err)
err = nil
continue
}
@ -168,9 +188,8 @@ func (s *Service) GetCollection(name string) (c *Collection, err error) {
}

// Couldn't find it by the given name.
if errs != nil || len(errs) > 0 {
errs = append([]error{ErrDoesNotExist}, errs...)
err = NewErrors(errs...)
if !errs.IsEmpty() {
err = errs
} else {
err = ErrDoesNotExist
}
@ -197,6 +216,7 @@ func (s *Service) GetSecrets(itemPaths ...dbus.ObjectPath) (secrets map[dbus.Obj
}
*/
var results map[dbus.ObjectPath][]interface{}
var call *dbus.Call

if itemPaths == nil || len(itemPaths) == 0 {
err = ErrMissingPaths
@ -207,9 +227,13 @@ func (s *Service) GetSecrets(itemPaths ...dbus.ObjectPath) (secrets map[dbus.Obj
results = make(map[dbus.ObjectPath][]interface{}, len(itemPaths))

// TODO: trigger a Service.Unlock for any locked items?
if err = s.Dbus.Call(
if call = s.Dbus.Call(
DbusServiceGetSecrets, 0, itemPaths, s.Session.Dbus.Path(),
).Store(&results); err != nil {
); call.Err != nil {
err = call.Err
return
}
if err = call.Store(&results); err != nil {
return
}

@ -233,9 +257,31 @@ func (s *Service) GetSession() (ssn *Session, err error) {
return
}

// 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
}
*/

// Lock locks an Unlocked Collection or Item (LockableObject).
func (s *Service) Lock(objects ...LockableObject) (err error) {

var call *dbus.Call
var toLock []dbus.ObjectPath
// We only use these as destinations.
var locked []dbus.ObjectPath
@ -253,9 +299,13 @@ func (s *Service) Lock(objects ...LockableObject) (err error) {
toLock[idx] = o.path()
}

if err = s.Dbus.Call(
if call = s.Dbus.Call(
DbusServiceLock, 0, toLock,
).Store(&locked, &promptPath); err != nil {
); call.Err != nil {
err = call.Err
return
}
if err = call.Store(&locked, &promptPath); err != nil {
return
}

@ -285,6 +335,7 @@ func (s *Service) Lock(objects ...LockableObject) (err error) {
*/
func (s *Service) OpenSession(algo, input string) (session *Session, output dbus.Variant, err error) {

var call *dbus.Call
var path dbus.ObjectPath
var inputVariant dbus.Variant

@ -298,9 +349,13 @@ func (s *Service) OpenSession(algo, input string) (session *Session, output dbus
// 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.
if err = s.Dbus.Call(
if call = s.Dbus.Call(
DbusServiceOpenSession, 0, algo, inputVariant,
).Store(&output, &path); err != nil {
); call.Err != nil {
err = call.Err
return
}
if err = call.Store(&output, &path); err != nil {
return
}

@ -316,17 +371,20 @@ func (s *Service) OpenSession(algo, input string) (session *Session, output dbus
*/
func (s *Service) ReadAlias(alias string) (collection *Collection, err error) {

var call *dbus.Call
var objectPath dbus.ObjectPath

err = s.Dbus.Call(
if call = s.Dbus.Call(
DbusServiceReadAlias, 0, alias,
).Store(&objectPath)

); call.Err != nil {
err = call.Err
return
}
/*
TODO: Confirm that a nonexistent alias will NOT cause an error to return.
If it does, alter the below logic.
*/
if err != nil {
if err = call.Store(&objectPath); err != nil {
return
}

@ -355,9 +413,12 @@ func (s *Service) RemoveAlias(alias string) (err error) {

/*
SearchItems searches all Collection objects and returns all matches based on the map of attributes.

err MAY be a *multierr.MultiError.
*/
func (s *Service) SearchItems(attributes map[string]string) (unlockedItems []*Item, lockedItems []*Item, err error) {

var call *dbus.Call
var locked []dbus.ObjectPath
var unlocked []dbus.ObjectPath
var collectionObjs []*Collection
@ -366,16 +427,20 @@ func (s *Service) SearchItems(attributes map[string]string) (unlockedItems []*It
var c *Collection
var cPath dbus.ObjectPath
var item *Item
var errs []error = make([]error, 0)
var errs *multierr.MultiError = multierr.NewMultiError()

if attributes == nil || len(attributes) == 0 {
err = ErrMissingAttrs
return
}

err = s.Dbus.Call(
if call = s.Dbus.Call(
DbusServiceSearchItems, 0, attributes,
).Store(&unlocked, &locked)
); call.Err != nil {
}
if err = call.Store(&unlocked, &locked); err != nil {
return
}

lockedItems = make([]*Item, 0)
unlockedItems = make([]*Item, 0)
@ -397,16 +462,17 @@ func (s *Service) SearchItems(attributes map[string]string) (unlockedItems []*It
cPath = dbus.ObjectPath(filepath.Dir(string(i)))

if c, ok = collections[cPath]; !ok {
errs = append(errs, errors.New(fmt.Sprintf(
errs.AddError(errors.New(fmt.Sprintf(
"could not find matching Collection for locked item %v", string(i),
)))
continue
}

if item, err = NewItem(c, i); err != nil {
errs = append(errs, errors.New(fmt.Sprintf(
"could not create Item for locked item %v", string(i),
errs.AddError(errors.New(fmt.Sprintf(
"could not create Item for locked item %v; error follows", string(i),
)))
errs.AddError(err)
err = nil
continue
}
@ -420,24 +486,25 @@ func (s *Service) SearchItems(attributes map[string]string) (unlockedItems []*It
cPath = dbus.ObjectPath(filepath.Dir(string(i)))

if c, ok = collections[cPath]; !ok {
errs = append(errs, errors.New(fmt.Sprintf(
errs.AddError(errors.New(fmt.Sprintf(
"could not find matching Collection for unlocked item %v", string(i),
)))
continue
}

if item, err = NewItem(c, i); err != nil {
errs = append(errs, errors.New(fmt.Sprintf(
"could not create Item for unlocked item %v", string(i),
errs.AddError(errors.New(fmt.Sprintf(
"could not create Item for unlocked item %v; error follows", string(i),
)))
errs.AddError(err)
err = nil
continue
}
unlockedItems = append(unlockedItems, item)
}

if errs != nil && len(errs) > 0 {
err = NewErrors(errs...)
if !errs.IsEmpty() {
err = errs
}

return
@ -450,18 +517,17 @@ func (s *Service) SearchItems(attributes map[string]string) (unlockedItems []*It
*/
func (s *Service) SetAlias(alias string, objectPath dbus.ObjectPath) (err error) {

var c *dbus.Call
var call *dbus.Call
var collection *Collection

if collection, err = s.GetCollection(alias); err != nil {
return
}

c = s.Dbus.Call(
if call = s.Dbus.Call(
DbusServiceSetAlias, 0, alias, objectPath,
)

if err = c.Err; err != nil {
); call.Err != nil {
err = call.Err
return
}

@ -477,6 +543,7 @@ func (s *Service) SetAlias(alias string, objectPath dbus.ObjectPath) (err error)
// Unlock unlocks a locked Collection or Item (LockableObject).
func (s *Service) Unlock(objects ...LockableObject) (err error) {

var call *dbus.Call
var toUnlock []dbus.ObjectPath
// We only use these as destinations.
var unlocked []dbus.ObjectPath
@ -494,9 +561,13 @@ func (s *Service) Unlock(objects ...LockableObject) (err error) {
toUnlock[idx] = o.path()
}

if err = s.Dbus.Call(
if call = s.Dbus.Call(
DbusServiceUnlock, 0, toUnlock,
).Store(&unlocked, &resultPath); err != nil {
); call.Err != nil {
err = call.Err
return
}
if err = call.Store(&unlocked, &resultPath); err != nil {
return
}


View File

@ -1,6 +1,8 @@
package gosecret

import (
"fmt"

"github.com/godbus/dbus/v5"
)

@ -32,13 +34,21 @@ func NewSession(service *Service, path dbus.ObjectPath) (session *Session, err e
// Close cleanly closes a Session.
func (s *Session) Close() (err error) {

var c *dbus.Call
var call *dbus.Call

c = s.Dbus.Call(
if call = s.Dbus.Call(
DbusSessionClose, 0,
)

_ = c
); call.Err != nil {
/*
I... still haven't 100% figured out why this happens, but the session DOES seem to close...?
PRs or input welcome.
TODO: figure out why this error gets triggered.
*/
if call.Err.Error() != fmt.Sprintf("The name %v was not provided by any .service files", DbusInterfaceSession) {
err = call.Err
return
}
}

return
}

View File

@ -6,16 +6,6 @@ import (
"github.com/godbus/dbus/v5"
)

/*
MultiError is a type of error.Error that can contain multiple error.Errors. Confused? Don't worry about it.
*/
type MultiError struct {
// Errors is a slice of errors to combine/concatenate when .Error() is called.
Errors []error `json:"errors"`
// ErrorSep is a string to use to separate errors for .Error(). The default is "\n".
ErrorSep string `json:"separator"`
}

/*
SecretServiceError is a translated error from SecretService API.
See https://developer-old.gnome.org/libsecret/unstable/libsecret-SecretError.html#SecretError and
@ -74,6 +64,31 @@ type Service struct {
Session *Session `json:"-"`
// IsLocked indicates if the Service is locked or not. Status updated by Service.Locked.
IsLocked bool `json:"locked"`
/*
Legacy indicates that this SecretService implementation breaks current spec
by implementing the legacy/obsolete draft spec rather than current libsecret spec
for the Dbus API.

If you're using SecretService with KeePassXC, for instance, or a much older version
of Gnome-Keyring *before* libsecret integration(?), or if you are getting strange errors
when performing a Service.SearchItems, you probably need to enable this field on the
Service returned by NewService. The coverage of this field may expand in the future, but
currently it only prevents/suppresses the (non-existent, in legacy spec) Type property
from being read or written on Items during e.g.:

Service.SearchItems
Collection.CreateItem
NewItem
Item.ChangeItemType
Item.Type

It will perform a no-op if enabled in the above contexts to maintain cross-compatability
in codebase between legacy and proper current spec systems, avoiding an error return.

You can use CheckErrIsFromLegacy if Service.Legacy is false and Service.SearchItems returns
a non-nil err to determine if this Service is (probably) interfacing with a legacy spec API.
*/
Legacy bool `json:"is_legacy"`
}

/*
@ -83,7 +98,7 @@ type Service struct {
*/
type Session struct {
*DbusObject
// collection tracks the Service this Session was created from.
// service tracks the Service this Session was created from.
service *Service
}