minor fixes/improvements...

This commit is contained in:
brent s. 2021-12-12 02:29:29 -05:00
parent 142c0ba74f
commit 56ba974dec
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
6 changed files with 405 additions and 109 deletions

View File

@ -3,6 +3,7 @@ https://developer-old.gnome.org/libsecret/unstable/
https://developer-old.gnome.org/libsecret/0.18/ https://developer-old.gnome.org/libsecret/0.18/
https://people.gnome.org/~stefw/libsecret-docs/index.html https://people.gnome.org/~stefw/libsecret-docs/index.html


https://freedesktop.org/wiki/Specifications/secret-storage-spec/secrets-api-0.1.html#eggdbus-interface-org.freedesktop.Secrets.Collection


Quick reference URLs: Quick reference URLs:



View File

@ -41,6 +41,66 @@ func NewCollection(service *Service, path dbus.ObjectPath) (coll *Collection, er
return return
} }


// CreateItem returns a pointer to an Item based on a label, some attributes, a Secret, and whether any existing secret with the same label should be replaced or not.
func (c *Collection) CreateItem(label string, attrs map[string]string, secret *Secret, replace bool) (item *Item, err error) {

var prompt *Prompt
var path dbus.ObjectPath
var promptPath dbus.ObjectPath
var variant *dbus.Variant
var props map[string]dbus.Variant = make(map[string]dbus.Variant)

props[DbusItemLabel] = dbus.MakeVariant(label)
props[DbusItemAttributes] = dbus.MakeVariant(attrs)

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

if isPrompt(promptPath) {
prompt = NewPrompt(c.Conn, promptPath)

if variant, err = prompt.Prompt(); err != nil {
return
}

path = variant.Value().(dbus.ObjectPath)
}

item, err = NewItem(c, path)

return
}

/*
Delete removes a Collection.
While *technically* not necessary, it is recommended that you iterate through
Collection.Items and do an Item.Delete for each item *before* calling Collection.Delete;
the item paths are cached as "orphaned paths" in Dbus otherwise if not deleted before deleting
their Collection. They should clear on a reboot or restart of Dbus (but rebooting Dbus on a system in use is... troublesome).
*/
func (c *Collection) Delete() (err error) {

var promptPath dbus.ObjectPath
var prompt *Prompt

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

if isPrompt(promptPath) {

prompt = NewPrompt(c.Conn, promptPath)
if _, err = prompt.Prompt(); err != nil {
return
}
}

return
}

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


@ -70,22 +130,46 @@ func (c *Collection) Items() (items []*Item, err error) {
return return
} }


// Delete removes a Collection. // Label returns the Collection label (name).
func (c *Collection) Delete() (err error) { func (c *Collection) Label() (label string, err error) {


var promptPath dbus.ObjectPath var variant dbus.Variant
var prompt *Prompt


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


if isPrompt(promptPath) { label = variant.Value().(string)

if label != c.name {
c.name = label
}


prompt = NewPrompt(c.Conn, promptPath)
if _, err = prompt.Prompt(); err != nil {
return return
} }

// Locked indicates if a Collection is locked (true) or unlocked (false).
func (c *Collection) Locked() (isLocked bool, err error) {

var variant dbus.Variant

if variant, err = c.Dbus.GetProperty(DbusCollectionLocked); err != nil {
isLocked = true
return
}

isLocked = variant.Value().(bool)

return
}

// Relabel modifies the Collection's label in Dbus.
func (c *Collection) Relabel(newLabel string) (err error) {

var variant dbus.Variant = dbus.MakeVariant(newLabel)

if err = c.Dbus.SetProperty(DbusCollectionLabel, variant); err != nil {
return
} }


return return
@ -124,84 +208,6 @@ func (c *Collection) SearchItems(profile string) (items []*Item, err error) {
return return
} }


// CreateItem returns a pointer to an Item based on a label, some attributes, a Secret, and whether any existing secret with the same label should be replaced or not.
func (c *Collection) CreateItem(label string, attrs map[string]string, secret *Secret, replace bool) (item *Item, err error) {

var prompt *Prompt
var path dbus.ObjectPath
var promptPath dbus.ObjectPath
var variant *dbus.Variant
var props map[string]dbus.Variant = make(map[string]dbus.Variant)

props[DbusItemLabel] = dbus.MakeVariant(label)
props[DbusItemAttributes] = dbus.MakeVariant(attrs)

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

if isPrompt(promptPath) {
prompt = NewPrompt(c.Conn, promptPath)

if variant, err = prompt.Prompt(); err != nil {
return
}

path = variant.Value().(dbus.ObjectPath)
}

item, err = NewItem(c, path)

return
}

// Locked indicates if a Collection is locked (true) or unlocked (false).
func (c *Collection) Locked() (isLocked bool, err error) {

var variant dbus.Variant

if variant, err = c.Dbus.GetProperty(DbusCollectionLocked); err != nil {
isLocked = true
return
}

isLocked = variant.Value().(bool)

return
}

// Label returns the Collection label (name).
func (c *Collection) Label() (label string, err error) {

var variant dbus.Variant

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

label = variant.Value().(string)

if label != c.name {
c.name = label
}

return
}

// Relabel modifies the Collection's label in Dbus.
func (c *Collection) Relabel(newLabel string) (err error) {

var variant dbus.Variant = dbus.MakeVariant(newLabel)

if err = c.Dbus.SetProperty(DbusItemLabel, variant); err != nil {
return
}

return
}

// Created returns the time.Time of when a Collection was created. // Created returns the time.Time of when a Collection was created.
func (c *Collection) Created() (created time.Time, err error) { func (c *Collection) Created() (created time.Time, err error) {


@ -251,3 +257,14 @@ func (c *Collection) Modified() (modified time.Time, isChanged bool, err error)


return return
} }

/*
setModify updates the Collection's modification time (as specified by Collection.Modified).
It seems that this does not update automatically.
*/
func (c *Collection) setModify() (err error) {

err = c.Dbus.SetProperty(DbusCollectionModified, uint64(time.Now().Unix()))

return
}

158
collection_funcs_test.go Normal file
View File

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

import (
`testing`

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

// Some functions are covered in the Service tests.

/*
TestNewCollection tests the following internal functions/methods via nested calls:

(all calls in TestNewService)
NewService
NewCollection

*/
func TestNewCollection(t *testing.T) {

var svc *Service
var collection *Collection
var err error

if svc, err = NewService(); err != nil {
t.Fatalf("NewService failed: %v", err.Error())
}

if collection, err = NewCollection(svc, dbus.ObjectPath(dbusDefaultCollectionPath)); err != nil {
t.Errorf(
"TestNewCollection failed when fetching collection at '%v': %v",
dbusDefaultCollectionPath, err.Error(),
)
}
_ = collection

if err = svc.Close(); err != nil {
t.Errorf("could not close Service.Session: %v", err.Error())
}
}

/*
TestCollection_Label tests the following internal functions/methods via nested calls:
(all calls in TestNewCollection)
Service.GetCollection
Collection.Label

*/
func TestCollection_Label(t *testing.T) {

var svc *Service
var collection *Collection
var collLabel string
var err error

if svc, err = NewService(); err != nil {
t.Fatalf("NewService failed: %v", err.Error())
}

if collection, err = svc.GetCollection(defaultCollectionLabel); err != nil {
t.Errorf(
"failed when fetching collection '%v': %v",
defaultCollectionLabel, err.Error(),
)
err = nil
if err = svc.Close(); err != nil {
t.Errorf("could not close Service.Session: %v", err.Error())
}
}

if collLabel, err = collection.Label(); err != nil {
t.Errorf("cannot fetch label for '%v': %v", string(collection.Dbus.Path()), err.Error())
if err = svc.Close(); err != nil {
t.Fatalf("could not close Service.Session: %v", err.Error())
}
}

if defaultCollection != collLabel {
t.Errorf("fetched collection ('%v') does not match fetched collection label ('%v')", collLabel, defaultCollection)
}

if err = svc.Close(); err != nil {
t.Errorf("could not close Service.Session: %v", err.Error())
}

}

/*
TestCollection_Items tests the following internal functions/methods via nested calls:

(all calls in TestNewCollection)
Service.GetCollection
Collection.Items
NewSecret
Collection.CreateItem
Collection.SearchItems
Item.Delete

*/
func TestCollection_Items(t *testing.T) {

var svc *Service
var collection *Collection
var items []*Item
var item *Item
var searchItemResults []*Item
var secret *Secret
var err error

if svc, err = NewService(); err != nil {
t.Fatalf("NewService failed: %v", err.Error())
}

if collection, err = svc.GetCollection(defaultCollection); err != nil {
if err = svc.Close(); err != nil {
t.Errorf("could not close Service.Session: %v", err.Error())
}
t.Fatalf("failed when fetching collection '%v': %v",
defaultCollection, err.Error(),
)
}

if items, err = collection.Items(); err != nil {
t.Errorf(
"failed fetching items for '%v' at '%v': %v",
defaultCollection, string(collection.Dbus.Path()), err.Error(),
)
} else {
t.Logf("found %v items in collection '%v' at '%v'", len(items), defaultCollection, string(collection.Dbus.Path()))
}

secret = NewSecret(svc.Session, []byte{}, []byte(testSecretContent), "text/plain")

if item, err = collection.CreateItem(testItemLabel, itemAttrs, secret, false); err != nil {
t.Errorf(
"could not create item '%v' in collection '%v': %v",
testItemLabel, defaultCollection, err.Error(),
)
} else {

if searchItemResults, err = collection.SearchItems(testItemLabel); err != nil {
t.Errorf("failed to find item '%v' via Collection.SearchItems: %v", string(item.Dbus.Path()), err.Error())
} else if len(searchItemResults) == 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))
}

if err = item.Delete(); err != nil {
t.Errorf("failed to delete created item '%v': %v", string(item.Dbus.Path()), err.Error())
}

}

if err = svc.Close(); err != nil {
t.Errorf("could not close Service.Session: %v", err.Error())
}
}

View File

@ -6,19 +6,27 @@ import (


// Paths. // Paths.
const ( const (
DbusDefaultCollectionPath string = DbusPath + "/collections/login" dbusCollectionPath string = DbusPath + "/collection"
dbusDefaultCollectionPath string = dbusCollectionPath + "/login"
) )


// Strings. // Strings.
const ( const (
defaultCollection string = "default" // SHOULD point to a collection named "login"; "default" is the alias. defaultCollectionAlias string = "default" // SHOULD point to a collection named "login" (below); "default" is the alias.
defaultCollection string = "login"
defaultCollectionLabel string = "Login" // a display name; the label is lowercased and normalized for the path (per above).
testAlias string = "GOSECRET_TESTING_ALIAS" testAlias string = "GOSECRET_TESTING_ALIAS"
testSecretContent string = "This is a test secret for gosecret." testSecretContent string = "This is a test secret for gosecret."
testItemLabel string = "gosecret_test_item" testItemLabel string = "Gosecret Test Item"
) )


// Objects. // Objects.
var ( var (
collectionName uuid.UUID = uuid.New() collectionName uuid.UUID = uuid.New()
collectionAlias uuid.UUID = uuid.New() collectionAlias uuid.UUID = uuid.New()
itemAttrs map[string]string = map[string]string{
"GOSECRET": "yes",
"foo": "bar",
"profile": testItemLabel,
}
) )

View File

@ -124,7 +124,9 @@ func (s *Service) CreateCollection(label string) (collection *Collection, err er
*/ */
func (s *Service) GetCollection(name string) (c *Collection, err error) { func (s *Service) GetCollection(name string) (c *Collection, err error) {


var errs []error
var colls []*Collection var colls []*Collection
var collLabel string


// First check for an alias. // First check for an alias.
if c, err = s.ReadAlias(name); err != nil && err != ErrDoesNotExist { if c, err = s.ReadAlias(name); err != nil && err != ErrDoesNotExist {
@ -147,8 +149,26 @@ func (s *Service) GetCollection(name string) (c *Collection, err error) {
} }
} }


// Still nothing? Try by label.
for _, i := range colls {
if collLabel, err = i.Label(); err != nil {
errs = append(errs, err)
err = nil
continue
}
if collLabel == name {
c = i
return
}
}

// Couldn't find it by the given name. // Couldn't find it by the given name.
if errs != nil || len(errs) > 0 {
errs = append([]error{ErrDoesNotExist}, errs...)
err = NewErrors(errs...)
} else {
err = ErrDoesNotExist err = ErrDoesNotExist
}


return return
} }

View File

@ -2,6 +2,7 @@ package gosecret


import ( import (
"testing" "testing"
`time`


`github.com/godbus/dbus/v5` `github.com/godbus/dbus/v5`
) )
@ -43,12 +44,16 @@ func TestNewService(t *testing.T) {
NewCollection NewCollection
Collection.Modified Collection.Modified
NewErrors NewErrors
Collection.Created
*/ */
func TestService_Collections(t *testing.T) { func TestService_Collections(t *testing.T) {


var err error var err error
var svc *Service var svc *Service
var colls []*Collection var colls []*Collection
var collLabel string
var created time.Time
var modified time.Time


if svc, err = NewService(); err != nil { if svc, err = NewService(); err != nil {
t.Fatalf("could not get new Service via NewService: %v", err.Error()) t.Fatalf("could not get new Service via NewService: %v", err.Error())
@ -59,6 +64,30 @@ func TestService_Collections(t *testing.T) {
} else { } else {
t.Logf("found %v collections via Service.Collections", len(colls)) t.Logf("found %v collections via Service.Collections", len(colls))
} }
for idx, c := range colls {
if collLabel, err = c.Label(); err != nil {
t.Errorf(
"failed to get label for collection '%v': %v",
string(c.Dbus.Path()), err.Error(),
)
}
if created, err = c.Created(); err != nil {
t.Errorf(
"failed to get created time for collection '%v': %v",
string(c.Dbus.Path()), err.Error(),
)
}
if modified, _, err = c.Modified(); err != nil {
t.Errorf(
"failed to get modified time for collection '%v': %v",
string(c.Dbus.Path()), err.Error(),
)
}
t.Logf(
"collection #%v (name '%v', label '%v'): created %v, last modified %v",
idx, c.name, collLabel, created, modified,
)
}


if err = svc.Close(); err != nil { if err = svc.Close(); err != nil {
t.Errorf("could not close Service.Session: %v", err.Error()) t.Errorf("could not close Service.Session: %v", err.Error())
@ -79,7 +108,7 @@ func TestService_Collections(t *testing.T) {
(By extension, Service.CreateCollection is also tested as it's a very thin wrapper (By extension, Service.CreateCollection is also tested as it's a very thin wrapper
around Service.CreateAliasedCollection). around Service.CreateAliasedCollection).
*/ */
/* DISABLED. Currently, *only* the alias "default" is allowed. TODO: revisit in future? /* DISABLED. Currently (as of 0.20.4), *only* the alias "default" is allowed. TODO: revisit in future?
func TestService_CreateAliasedCollection(t *testing.T) { func TestService_CreateAliasedCollection(t *testing.T) {


var err error var err error
@ -121,12 +150,18 @@ func TestService_CreateAliasedCollection(t *testing.T) {
TestService_GetCollection tests the following internal functions/methods via nested calls: TestService_GetCollection tests the following internal functions/methods via nested calls:


(all calls in TestService_CreateAliasedCollection) (all calls in TestService_CreateAliasedCollection)
Service.GetCollection
(all calls in TestService_Collections) (all calls in TestService_Collections)


NewErrors
Collection.Created
Service.GetCollection
NewCollection
Collection.Modified
Service.ReadAlias Service.ReadAlias


The default collection (login) is fetched instead of creating one as this collection should exist, The default collection (login) is fetched instead of creating one as this collection should exist,
and tests fetching existing collections instead of newly-created ones. and thus this function tests fetching existing collections instead of newly-created ones.
*/ */
func TestService_GetCollection(t *testing.T) { func TestService_GetCollection(t *testing.T) {


@ -162,6 +197,7 @@ func TestService_GetCollection(t *testing.T) {
NewSecret NewSecret
Collection.CreateItem Collection.CreateItem
Item.Label Item.Label
Item.Delete


*/ */
func TestService_Secrets(t *testing.T) { func TestService_Secrets(t *testing.T) {
@ -177,30 +213,49 @@ func TestService_Secrets(t *testing.T) {
var testSecret *Secret var testSecret *Secret
var secretsResult map[dbus.ObjectPath]*Secret var secretsResult map[dbus.ObjectPath]*Secret
var itemPaths []dbus.ObjectPath var itemPaths []dbus.ObjectPath
var itemAttrs map[string]string = map[string]string{ var created time.Time
"GOSECRET": "yes", var modified time.Time
} var newModified time.Time
var wasModified bool
var isModified bool


if svc, err = NewService(); err != nil { if svc, err = NewService(); err != nil {
t.Fatalf("could not get new Service via NewService: %v", err.Error()) t.Fatalf("could not get new Service via NewService: %v", err.Error())
} }


if collection, err = svc.CreateCollection(collectionName.String()); err != nil { if collection, err = svc.CreateCollection(collectionName.String()); err != nil {
t.Errorf("could not create collection '%v': %v", collectionName.String(), err.Error())
if err = svc.Close(); err != nil { if err = svc.Close(); err != nil {
t.Errorf("could not close Service.Session: %v", err.Error()) t.Fatalf("could not close Service.Session: %v", err.Error())
} }
t.Fatalf("could not create collection '%v': %v", collectionName.String(), err.Error())
} else { } else {
t.Logf("created collection '%v' at path '%v' successfully", collectionName.String(), string(collection.Dbus.Path())) t.Logf("created collection '%v' at path '%v' successfully", collectionName.String(), string(collection.Dbus.Path()))
} }


if created, err = collection.Created(); err != nil {
t.Errorf(
"failed to get created time for '%v': %v",
collectionName.String(), err.Error(),
)
}
if modified, wasModified, err = collection.Modified(); err != nil {
t.Errorf(
"failed to get modified time for '%v': %v",
collectionName.String(), err.Error(),
)
}
t.Logf(
"%v: collection '%v': created at %v, last modified at %v; was changed: %v",
time.Now(), collectionName.String(), created, modified, wasModified,
)

// Create a secret // Create a secret
testSecret = NewSecret(svc.Session, nil, []byte(testSecretContent), "text/plain") testSecret = NewSecret(svc.Session, nil, []byte(testSecretContent), "text/plain")
if testItem, err = collection.CreateItem(testItemLabel, itemAttrs, testSecret, true); err != nil { if testItem, err = collection.CreateItem(testItemLabel, itemAttrs, testSecret, true); err != nil {
t.Errorf("could not create Item in collection '%v': %v", collectionName.String(), err.Error())
if err = svc.Close(); err != nil { if err = svc.Close(); err != nil {
t.Errorf("could not close Service.Session: %v", err.Error()) t.Fatalf("could not close Service.Session: %v", err.Error())
} }
t.Fatalf("could not create Item in collection '%v': %v", collectionName.String(), err.Error())
} }


if itemName, err = testItem.Label(); err != nil { if itemName, err = testItem.Label(); err != nil {
@ -241,7 +296,46 @@ func TestService_Secrets(t *testing.T) {
} }
} }


// Delete the collection to clean up. // Confirm the modification information changed.
_ = newModified
_ = isModified
/* TODO: Disabled for now; it *seems* the collection modification time doesn't auto-update? See collection.setModify if not.
if newModified, isModified, err = collection.Modified(); err != nil {
t.Errorf(
"failed to get modified time for '%v': %v",
collectionName.String(), err.Error(),
)
}
t.Logf(
"%v: (post-change) collection '%v': last modified at %v; was changed: %v",
time.Now(), collectionName.String(), newModified, isModified,
)
if !isModified {
t.Errorf(
"modification tracking for collection '%v' failed; expected true but got false",
collectionName.String(),
)
} else {
t.Logf("(modification check passed)")
}
if !newModified.After(modified) {
t.Errorf(
"modification timestamp update for '%v' failed: old %v, new %v",
collectionName.String(), modified, newModified,
)
}
*/

/*
Delete the item and collection to clean up.
*Technically* the item is deleted if the collection is, but its path is held in memory if not
manually removed, leading to a "ghost" item.
*/
if err = testItem.Delete(); err != nil {
t.Errorf("could not delete test item '%v' in collection '%v': %v",
string(testItem.Dbus.Path()), collectionName.String(), err.Error(),
)
}
if err = collection.Delete(); err != nil { if err = collection.Delete(); err != nil {
t.Errorf( t.Errorf(
"error when deleting collection '%v' when testing Service: %v", "error when deleting collection '%v' when testing Service: %v",
@ -254,8 +348,6 @@ func TestService_Secrets(t *testing.T) {
} }
} }


// service.Lock & service.Unlock

/* /*
TestService_Locking tests the following internal functions/methods via nested calls: TestService_Locking tests the following internal functions/methods via nested calls: