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: Quick reference URLs:


Dbus paths: https://specifications.freedesktop.org/secret-service/latest/ch12.html 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 package gosecret


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

// Paths. // Paths.
const ( const (
DbusDefaultCollectionPath string = DbusPath + "/collections/login" 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). 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. 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 { if errs == nil || len(errs) == 0 {
return return
@ -46,7 +46,7 @@ func (e *MultiError) Error() (errStr string) {
} }


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

View File

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


if err = p.Dbus.Call( 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 { ).Store(); err != nil {
return return
} }

View File

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


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

if variant, err = prompt.Prompt(); err != nil { if variant, err = prompt.Prompt(); err != nil {
return 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) { 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 { if itemPaths == nil || len(itemPaths) == 0 {
err = ErrMissingPaths err = ErrMissingPaths
return return
} }


secrets = make(map[dbus.ObjectPath]*Secret, len(itemPaths)) 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? // TODO: trigger a Service.Unlock for any locked items?
if err = s.Dbus.Call( if err = s.Dbus.Call(
DbusServiceGetSecrets, 0, itemPaths, DbusServiceGetSecrets, 0, itemPaths, s.Session.Dbus.Path(),
).Store(&secrets); err != nil { ).Store(&results); err != nil {
return return
} }


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

return return
} }


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


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


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


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


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


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


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

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


return 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) { func (s *Service) Unlock(objectPaths ...dbus.ObjectPath) (err error) {


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


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


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


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

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


return return
} }

View File

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


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

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

const (
defaultCollection string = "Login"
testAlias string = "GOSECRET_TESTING_ALIAS"
) )


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


var err error var err error
@ -105,6 +96,12 @@ func TestService_CreateAliasedCollection(t *testing.T) {
collectionName.String(), collectionAlias.String(), err.Error(), collectionName.String(), collectionAlias.String(), err.Error(),
) )
} else { } 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 { if err = collection.Delete(); err != nil {
t.Errorf( t.Errorf(
"error when deleting aliased collection '%v' with alias '%v': %v", "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 { 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())
} }
} }
*/


/* /*
TestService_GetCollection tests the following internal functions/methods via nested calls: 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) (all calls in TestService_Collections)
Service.ReadAlias 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) { func TestService_GetCollection(t *testing.T) {


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


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


var err error var err error
var svc *Service var svc *Service
var collection *Collection 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 itemPaths []dbus.ObjectPath
var itemAttrs map[string]string = map[string]string{
"GOSECRET": "yes",
}


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


// service.Lock & service.Unlock // 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,
)
}
}