adding example and test information to readme.adoc

This commit is contained in:
brent s. 2021-12-10 03:47:10 -05:00
parent 94ae20829e
commit 142c0ba74f
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
3 changed files with 146 additions and 3 deletions

View File

@ -110,6 +110,128 @@ and a single Collection, named `login` (and aliased to `default`, usually).

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

////
However, here's a quick demonstration.
////

[source,go]
----
package main

import (
`fmt`
`log`

// "github.com/johnnybubonic/gosecret" // GitHub mirror
"r00t2.io/gosecret" // real upstream; recommended
)

const (
// The default collection; it should be available on all SecretService implementations.
collectionName string = "login"
// A label for an Item used in examples below.
exampleLabel string = "Some Website Credentials"
)

func main() {

var err error
var service *gosecret.Service
var collection *gosecret.Collection
var item *gosecret.Item
var itemAttrs map[string]string
var itemLabel string
var secret *gosecret.Secret

// All interactions with SecretService start with initiating a Service connection.
if service, err = gosecret.NewService(); err != nil {
log.Panicln(err)
}
defer service.Close()

// And unless operating directly on a Service via its methods, you probably need a Collection as well.
if collection, err = service.GetCollection(collectionName); err != nil {
log.Panicln(err)
}

/*
Create a Secret which gets stored in an Item which gets stored in a Collection.
See the documentation for details.
*/
// Incidentally, I believe this is the only exported function/method that does not return an error returner.
secret = gosecret.NewSecret(
service.Session, // The session associated with this Secret. You're likely fine with the automatically-created *(Service).Session.
[]byte{}, // The "parameters". Likely this is an empty byteslice.
[]byte("a super secret password"), // The actual secret value.
"text/plain", // The content type (MIME type/media type). See https://www.iana.org/assignments/media-types/media-types.xhtml.
)

/*
Item attributes are a map[string]string of *metadata* about a Secret/Item.
Do *NOT* store sensitive information in these.
They're primarily used for searching for Items.
*/
itemAttrs = map[string]string{
"Use": "an example secret",
"note": "These keys can be anything you want!",
"url": "https://somewebsite.tld/login",
"username": "user.name",
}

// And create the Item (and add it to SecretService).
if item, err = collection.CreateItem(
exampleLabel, // The label of the item. This should also be considered not secret.
itemAttrs, // Attributes for the item; see above.
secret, // The actual secret.
true, // Whether to replace an existing item with the same label or not.
); err != nil {
log.Panicln(err)
}

/*
Now let's fetch the same Item via its attributes.
The results are split into locked items and unlocked items.
*/
var unlockedItems []*gosecret.Item
var lockedItems []*gosecret.Item

if unlockedItems, lockedItems, err = service.SearchItems(itemAttrs); err != nil {
log.Panicln(err)
}

// We should only have one Item that matches the search attributes, and unless the item or collection is locked, ...
item = unlockedItems[0]
if itemLabel, err = item.Label(); err != nil {
log.Panicln(err)
}
fmt.Printf("Found item: %v\n", itemLabel)

// Alternatively if you are unsure of the attributes but know the label of the item you want, you can iterate through them.
var itemResults []*gosecret.Item

if itemResults, err = collection.Items(); err != nil {
log.Panicln(err)
}

for idx, i := range itemResults {
if itemLabel, err = i.Label(); err != nil {
fmt.Printf("Cannot read label for item at path '%v'\n", i.Dbus.Path())
continue
}
if itemLabel != exampleLabel { // Matching against a desired label - exampleLabel, in this case.
continue
}
fmt.Printf("Found item labeled '%v'! Index number %v at path '%v'\n", itemLabel, idx, i.Dbus.Path())
fmt.Printf("Password: %v\n", string(i.Secret.Value))
break
}
}
----

== Library Hacking

=== Tests

Many functions are consolidated into a single test due to how dependent certain processes are on other objects. However, all functionality should be covered by test cases and the error string will always be passed through the stack to `go test -v` output.

Obviously since this library interacts directly with Dbus (and I don't want to spend the time to mock up an entire Dbus-like interface to test), all tests are integration tests rather than unit tests. Therefore in the event of a failed run, you will need to open e.g. Seahorse or d-feet or some other Dbus/SecretService browser and manually delete the created Secret Service collection. It/they should be easily identified; they use a generated UUID4 string as the collection name and it is highly unlikely that you will see any other collections named as such. If running `go test` with the verbose flag (`-v`), the name and path of the collection will be printed out. If all tests pass, the test collection should be removed automatically.

The same UUID is used for all tests in a test run.

View File

@ -91,7 +91,11 @@ func (c *Collection) Delete() (err error) {
return
}

// 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.
I promise it's not useful for any other implementation/storage of SecretService whatsoever.
*/
func (c *Collection) SearchItems(profile string) (items []*Item, err error) {

var paths []dbus.ObjectPath

View File

@ -108,6 +108,7 @@ func TestService_CreateAliasedCollection(t *testing.T) {
collectionName.String(), collectionAlias.String(), err.Error(),
)
}
t.Logf("created collection '%v' at path '%v' successfully", collectionName.String(), string(collection.Dbus.Path()))
}

if err = svc.Close(); err != nil {
@ -189,6 +190,8 @@ func TestService_Secrets(t *testing.T) {
t.Errorf("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()))
}

// Create a secret
@ -280,6 +283,8 @@ func TestService_Locking(t *testing.T) {
t.Errorf("could not close Service.Session: %v", err.Error())
}
t.Errorf("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 isLocked, err = collection.Locked(); err != nil {
@ -327,4 +332,16 @@ func TestService_Locking(t *testing.T) {
collectionName.String(), stateChangeLock, isLocked,
)
}

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