adding ref mat'l, Service tests done

This commit is contained in:
brent s. 2021-12-10 02:50:30 -05:00
parent eda1777431
commit 94ae20829e
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
7 changed files with 348 additions and 73 deletions

View File

@ -7,3 +7,7 @@ https://people.gnome.org/~stefw/libsecret-docs/index.html
Quick reference URLs:

Dbus paths: https://specifications.freedesktop.org/secret-service/latest/ch12.html
Message types: https://dbus.freedesktop.org/doc/dbus-specification.html#type-system
Use this for reference for errors like:
Type of message, “(ao)”, does not match expected type “(aoo)”
See dbus_types subdir for Golang structs that can be used in the future to improve these error messages.

116
.ref/dbus_types/structs.go Normal file
View File

@ -0,0 +1,116 @@
package dbus_types

// https://dbus.freedesktop.org/doc/dbus-specification.html#type-system

type DbusType struct {
TypeName string
Symbol rune
Desc string
ZeroValue interface{}
}

/*
BASIC TYPES
*/

var DbusByte DbusType = DbusType{
TypeName: "BYTE",
Symbol: 'y',
Desc: "Unsigned 8-bit integer",
ZeroValue: byte(0x0),
}

var DbusBoolean DbusType = DbusType{
TypeName: "BOOLEAN",
Symbol: 'b',
Desc: "Boolean value: 0 is false, 1 is true, any other value allowed by the marshalling format is invalid",
ZeroValue: false,
}

var DbusInt16 DbusType = DbusType{
TypeName: "INT16",
Symbol: 'n',
Desc: "Signed (two's complement) 16-bit integer",
ZeroValue: int16(0),
}

var DbusUint16 DbusType = DbusType{
TypeName: "UINT16",
Symbol: 'q',
Desc: "Unsigned 16-bit integer",
ZeroValue: uint16(0),
}

var DbusInt32 DbusType = DbusType{
TypeName: "INT32",
Symbol: 'i',
Desc: "Signed (two's complement) 32-bit integer",
ZeroValue: int32(0),
}

var DbusUint32 DbusType = DbusType{
TypeName: "UINT32",
Symbol: 'u',
Desc: "Unsigned 32-bit integer",
ZeroValue: uint32(0),
}

var DbusInt64 DbusType = DbusType{
TypeName: "INT64",
Symbol: 'x',
Desc: "Signed (two's complement) 64-bit integer (mnemonic: x and t are the first characters in \"sixty\" not already used for something more common)",
ZeroValue: int64(0),
}

var DbusUint64 DbusType = DbusType{
TypeName: "UINT64",
Symbol: 't',
Desc: "Unsigned 64-bit integer",
ZeroValue: uint64(0),
}

var DbusDoubleFloat DbusType = DbusType{
TypeName: "DOUBLE",
Symbol: 'd',
Desc: "IEEE 754 double-precision floating point",
ZeroValue: float64(0),
}

var DbusUnixFD DbusType = DbusType{
TypeName: "UNIX_FD",
Symbol: 'h',
Desc: "Unsigned 32-bit integer representing an index into an out-of-band array of file descriptors, transferred via some platform-specific mechanism (mnemonic: h for handle)",
ZeroValue: uint32(0), // See https://pkg.go.dev/github.com/godbus/dbus#UnixFDIndex
}

var DbusString DbusType = DbusType{
TypeName: "STRING",
Symbol: 'o',
Desc: "No extra constraints",
ZeroValue: "",
}

var DbusObjectPath DbusType = DbusType{
TypeName: "OBJECT_PATH",
Symbol: 'o',
Desc: "A syntactically valid Path for Dbus",
ZeroValue: nil, // ???
}

var DbusSignature DbusType = DbusType{
TypeName: "SIGNATURE",
Symbol: 'g',
Desc: "0 or more single complete types", // ???
ZeroValue: nil, // ???
}

/*
CONTAINER TYPES
*/
/*
TODO: not sure how to struct this natively, but:
Dbus Struct: (<symbol(s)...>) // Note: structs can be nested e.g. (i(ii))
Dbus Array: a<symbol> // The symbol can be any type (even nested arrays, e.g. aai), but only one type is allowed. Arrays are like Golang slices; no fixed size.
Dbus Variant: v<symbol> // Dbus equivalent of interface{}, more or less. See https://dbus.freedesktop.org/doc/dbus-specification.html#container-types
Dbus Dict: [kv] // Where k is the key's type and v is the value's type.
*/

View File

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

import (
`github.com/google/uuid`
)

// Paths.
const (
DbusDefaultCollectionPath string = DbusPath + "/collections/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"
)

// Objects.
var (
collectionName uuid.UUID = uuid.New()
collectionAlias uuid.UUID = uuid.New()
)

View File

@ -8,7 +8,7 @@ import (
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 *MultiError) {
func NewErrors(errs ...error) (err error) {

if errs == nil || len(errs) == 0 {
return
@ -46,7 +46,7 @@ func (e *MultiError) Error() (errStr string) {
}

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

View File

@ -23,6 +23,8 @@ func (p *Prompt) Prompt() (promptValue *dbus.Variant, err error) {
var c chan *dbus.Signal
var result *dbus.Signal

promptValue = new(dbus.Variant)

// Prompts are asynchronous; we connect to the signal and block with a channel until we get a response.
c = make(chan *dbus.Signal, 10)
defer close(c)
@ -31,7 +33,7 @@ func (p *Prompt) Prompt() (promptValue *dbus.Variant, err error) {
defer p.Conn.RemoveSignal(c)

if err = p.Dbus.Call(
DbusPrompterInterface, 0, "", // TODO: This last argument, the string, is for "window ID". I'm unclear what for.
DbusPrompterInterface, 0, "GoSecret.Prompt", // TODO: This last argument, the string, is for "window ID". I'm unclear what for.
).Store(); err != nil {
return
}

View File

@ -95,7 +95,6 @@ func (s *Service) CreateAliasedCollection(label, alias string) (collection *Coll
if isPrompt(promptPath) {

prompt = NewPrompt(s.Conn, promptPath)

if variant, err = prompt.Prompt(); err != nil {
return
}
@ -161,20 +160,38 @@ func (s *Service) GetCollection(name string) (c *Collection, err error) {
*/
func (s *Service) GetSecrets(itemPaths ...dbus.ObjectPath) (secrets map[dbus.ObjectPath]*Secret, err error) {

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

if itemPaths == nil || len(itemPaths) == 0 {
err = ErrMissingPaths
return
}

secrets = make(map[dbus.ObjectPath]*Secret, len(itemPaths))
results = make(map[dbus.ObjectPath][]interface{}, len(itemPaths))

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

for p, r := range results {
secrets[p] = NewSecret(
s.Session, r[1].([]byte), r[2].([]byte), r[3].(string),
)
}

return
}

@ -196,40 +213,29 @@ func (s *Service) GetSession() (ssn *Session, err error) {
*/
func (s *Service) Lock(objectPaths ...dbus.ObjectPath) (err error) {

var errs []error = make([]error, 0)
// We only use these as destinations.
var locked []dbus.ObjectPath
var prompt *Prompt
var resultPath dbus.ObjectPath
var promptPath dbus.ObjectPath

if objectPaths == nil || len(objectPaths) == 0 {
objectPaths = []dbus.ObjectPath{s.Dbus.Path()}
}

for _, p := range objectPaths {
if err = s.Dbus.Call(
DbusServiceLock, 0, p,
).Store(&locked, &resultPath); err != nil {
errs = append(errs, err)
err = nil
continue
DbusServiceLock, 0, objectPaths,
).Store(&locked, &promptPath); err != nil {
return
}

if isPrompt(resultPath) {
if isPrompt(promptPath) {

prompt = NewPrompt(s.Conn, resultPath)
prompt = NewPrompt(s.Conn, promptPath)

if _, err = prompt.Prompt(); err != nil {
errs = append(errs, err)
err = nil
continue
return
}
}
}

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

return
}
@ -407,7 +413,6 @@ func (s *Service) SetAlias(alias string, objectPath dbus.ObjectPath) (err error)
*/
func (s *Service) Unlock(objectPaths ...dbus.ObjectPath) (err error) {

var errs []error = make([]error, 0)
var unlocked []dbus.ObjectPath
var prompt *Prompt
var resultPath dbus.ObjectPath
@ -416,13 +421,10 @@ func (s *Service) Unlock(objectPaths ...dbus.ObjectPath) (err error) {
objectPaths = []dbus.ObjectPath{s.Dbus.Path()}
}

for _, p := range objectPaths {
if err = s.Dbus.Call(
DbusServiceUnlock, 0, p,
DbusServiceUnlock, 0, objectPaths,
).Store(&unlocked, &resultPath); err != nil {
errs = append(errs, err)
err = nil
continue
return
}

if isPrompt(resultPath) {
@ -430,16 +432,9 @@ func (s *Service) Unlock(objectPaths ...dbus.ObjectPath) (err error) {
prompt = NewPrompt(s.Conn, resultPath)

if _, err = prompt.Prompt(); err != nil {
errs = append(errs, err)
err = nil
continue
return
}
}
}

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

return
}

View File

@ -3,17 +3,7 @@ package gosecret
import (
"testing"

"github.com/google/uuid"
)

var (
collectionName uuid.UUID = uuid.New()
collectionAlias uuid.UUID = uuid.New()
)

const (
defaultCollection string = "Login"
testAlias string = "GOSECRET_TESTING_ALIAS"
`github.com/godbus/dbus/v5`
)

/*
@ -64,7 +54,7 @@ func TestService_Collections(t *testing.T) {
t.Fatalf("could not get new Service via NewService: %v", err.Error())
}

if _, err = svc.Collections(); err != nil {
if colls, err = svc.Collections(); err != nil {
t.Errorf("could not get Service.Collections: %v", err.Error())
} else {
t.Logf("found %v collections via Service.Collections", len(colls))
@ -89,6 +79,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?
func TestService_CreateAliasedCollection(t *testing.T) {

var err error
@ -105,6 +96,12 @@ func TestService_CreateAliasedCollection(t *testing.T) {
collectionName.String(), collectionAlias.String(), err.Error(),
)
} else {
if err = svc.SetAlias(testAlias, collection.Dbus.Path()); err != nil {
t.Errorf(
"error when setting an alias '%v' for aliased collection '%v' (original alias '%v')",
testAlias, collectionName.String(), collectionAlias.String(),
)
}
if err = collection.Delete(); err != nil {
t.Errorf(
"error when deleting aliased collection '%v' with alias '%v': %v",
@ -113,17 +110,11 @@ func TestService_CreateAliasedCollection(t *testing.T) {
}
}

if err = svc.SetAlias(testAlias, collection.Dbus.Path()); err != nil {
t.Errorf(
"error when setting an alias '%v' for aliased collection '%v' (original alias '%v')",
testAlias, collectionName.String(), collectionAlias.String(),
)
}

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

/*
TestService_GetCollection tests the following internal functions/methods via nested calls:
@ -133,6 +124,8 @@ func TestService_CreateAliasedCollection(t *testing.T) {
(all calls in TestService_Collections)
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.
*/
func TestService_GetCollection(t *testing.T) {

@ -162,29 +155,176 @@ func TestService_GetCollection(t *testing.T) {
(all calls in TestService_CreateAliasedCollection)
Service.CreateCollection
Service.SearchItems
NewItem
NewErrors
Service.GetSecrets
NewSecret
Collection.CreateItem
Item.Label

*/
/* TODO: left off on this.
func TestService_Secrets(t *testing.T) {

var err error
var svc *Service
var collection *Collection
var itemResultsUnlocked []*Item
var itemResultsLocked []*Item
var itemName string
var resultItemName string
var testItem *Item
var testSecret *Secret
var secretsResult map[dbus.ObjectPath]*Secret
var itemPaths []dbus.ObjectPath
var itemAttrs map[string]string = map[string]string{
"GOSECRET": "yes",
}

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 create collection '%v': %v", collectionName.String(), err.Error())
}

// Create a secret
testSecret = NewSecret(svc.Session, nil, []byte(testSecretContent), "text/plain")
if testItem, err = collection.CreateItem(testItemLabel, itemAttrs, testSecret, true); err != nil {
if err = svc.Close(); err != nil {
t.Errorf("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 {
t.Errorf(
"could not get label for newly-created item '%v' in collection '%v': %v",
string(testItem.Dbus.Path()), collectionName.String(), err.Error(),
)
itemName = testItemLabel
}

// Search items
if itemResultsUnlocked, itemResultsLocked, err = svc.SearchItems(itemAttrs); err != nil {
t.Errorf("did not find Item '%v' in collection '%v' SearchItems: %v", itemName, collectionName.String(), err.Error())
} else {
if len(itemResultsLocked) != 0 && itemResultsUnlocked != nil {
t.Errorf("at least one locked item in collection '%v'", collectionName.String())
}
if len(itemResultsUnlocked) != 1 {
t.Errorf("number of unlocked items in collection '%v' is not equal to 1", collectionName.String())
}
if resultItemName, err = itemResultsUnlocked[0].Label(); err != nil {
t.Errorf("cannot fetch test Item name from collection '%v' in SearchItems: %v", collectionName.String(), err.Error())
} else {
if resultItemName != itemName {
t.Errorf("seem to have fetched an improper Item from collection '%v' in SearchItems", collectionName.String())
}
}
}

// Fetch secrets
itemPaths = make([]dbus.ObjectPath, len(itemResultsUnlocked))
if len(itemResultsUnlocked) >= 1 {
itemPaths[0] = itemResultsUnlocked[0].Dbus.Path()
if secretsResult, err = svc.GetSecrets(itemPaths...); err != nil {
t.Errorf("failed to fetch Item path '%v' via Service.GetSecrets: %v", string(itemPaths[0]), err.Error())
} else if len(secretsResult) != 1 {
t.Errorf("received %v secrets from Service.GetSecrets instead of 1", len(secretsResult))
}
}

// Delete the collection to clean up.
if err = collection.Delete(); err != nil {
t.Errorf(
"error when deleting collection '%v' when testing Service: %v",
collectionName.String(), err.Error(),
)
}

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

// service.Lock & service.Unlock

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

(all calls in TestNewService)
(all calls in TestService_CreateAliasedCollection)
Service.Lock
Service.Unlock
Collection.Locked

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

var err error
var isLocked bool
var stateChangeLock bool
var svc *Service
var collection *Collection

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 {
if err = svc.Close(); err != nil {
t.Errorf("could not close Service.Session: %v", err.Error())
}
t.Errorf("could not create collection '%v': %v", collectionName.String(), err.Error())
}

if isLocked, err = collection.Locked(); err != nil {
t.Errorf("received error when checking collection '%v' lock status: %v", collectionName.String(), err.Error())
if err = collection.Delete(); err != nil {
t.Errorf(
"error when deleting collection '%v' when testing Service: %v",
collectionName.String(), err.Error(),
)
}
if err = svc.Close(); err != nil {
t.Errorf("could not close Service.Session: %v", err.Error())
}
return
} else {
t.Logf("collection '%v' original lock status: %v", collectionName.String(), isLocked)
}

// Change the state.
if isLocked {
if err = svc.Unlock(collection.Dbus.Path()); err != nil {
t.Errorf("could not unlock collection '%v': %v", collectionName.String(), err.Error())
}
if stateChangeLock, err = collection.Locked(); err != nil {
t.Errorf("received error when checking collection '%v' lock status: %v", collectionName.String(), err.Error())
}
if err = svc.Lock(collection.Dbus.Path()); err != nil {
t.Errorf("could not lock collection '%v': %v", collectionName.String(), err.Error())
}
} else {
if err = svc.Lock(collection.Dbus.Path()); err != nil {
t.Errorf("could not lock collection '%v': %v", collectionName.String(), err.Error())
}
if stateChangeLock, err = collection.Locked(); err != nil {
t.Errorf("received error when checking collection '%v' lock status: %v", collectionName.String(), err.Error())
}
if err = svc.Unlock(collection.Dbus.Path()); err != nil {
t.Errorf("could not unlock collection '%v': %v", collectionName.String(), err.Error())
}
}

if stateChangeLock != !isLocked {
t.Errorf(
"flipped lock state for collection '%v' (locked: %v) is not opposite of original lock state (locked: %v)",
collectionName.String(), stateChangeLock, isLocked,
)
}
}