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://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:


View File

@ -41,6 +41,66 @@ func NewCollection(service *Service, path dbus.ObjectPath) (coll *Collection, er
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.
func (c *Collection) Items() (items []*Item, err error) {

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

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

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

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

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

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

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
@ -124,84 +208,6 @@ func (c *Collection) SearchItems(profile string) (items []*Item, err error) {
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.
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
}

/*
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.
const (
DbusDefaultCollectionPath string = DbusPath + "/collections/login"
dbusCollectionPath string = DbusPath + "/collection"
dbusDefaultCollectionPath string = dbusCollectionPath + "/login"
)

// Strings.
const (
defaultCollection string = "default" // SHOULD point to a collection named "login"; "default" is the alias.
testAlias string = "GOSECRET_TESTING_ALIAS"
testSecretContent string = "This is a test secret for gosecret."
testItemLabel string = "gosecret_test_item"
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"
testSecretContent string = "This is a test secret for gosecret."
testItemLabel string = "Gosecret Test Item"
)

// Objects.
var (
collectionName uuid.UUID = uuid.New()
collectionAlias uuid.UUID = uuid.New()
collectionName 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) {

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

// First check for an alias.
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.
err = ErrDoesNotExist
if errs != nil || len(errs) > 0 {
errs = append([]error{ErrDoesNotExist}, errs...)
err = NewErrors(errs...)
} else {
err = ErrDoesNotExist
}

return
}

View File

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

import (
"testing"
`time`

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

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

if svc, err = NewService(); err != nil {
t.Fatalf("could not get new Service via NewService: %v", err.Error())
@ -59,6 +64,30 @@ func TestService_Collections(t *testing.T) {
} else {
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 {
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
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) {

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

(all calls in TestService_CreateAliasedCollection)
(all calls in TestService_Collections)


NewErrors
Collection.Created
Service.GetCollection
(all calls in TestService_Collections)
NewCollection
Collection.Modified
Service.ReadAlias

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) {

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

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

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

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 {
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 {
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
testSecret = NewSecret(svc.Session, nil, []byte(testSecretContent), "text/plain")
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 {
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 {
@ -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 {
t.Errorf(
"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: