collection, service, and session should be done

This commit is contained in:
brent s. 2021-12-04 19:38:26 -05:00
parent c0a1e4a281
commit 1d093627f6
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
8 changed files with 176 additions and 203 deletions

View File

@ -1,50 +1,40 @@
package gosecret

import (
"strings"
`strings`
"time"

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

/*
CreateCollection creates a new Collection named `name` using the dbus.BusObject `secretServiceConn`.
`secretServiceConn` should be the same as used for Collection.Dbus (and/or NewCollection).
It will be called by NewCollection if the Collection does not exist in Dbus.

Generally speaking, you should probably not use this function directly and instead use NewCollection.
NewCollection returns a pointer to a Collection based on a Service and a Dbus path.
You will almost always want to use Service.GetCollection instead.
*/
func CreateCollection(secretServiceConn *dbus.BusObject, name string) (c *Collection, err error) {
func NewCollection(service *Service, path dbus.ObjectPath) (coll *Collection, err error) {

var path dbus.ObjectPath
var splitPath []string

if secretServiceConn == nil {
if service == nil {
err = ErrNoDbusConn
return
}

path = dbus.ObjectPath(strings.Join([]string{DbusPath, ""}, "/"))

// TODO.

return
}

// NewCollection returns a pointer to a new Collection based on a Dbus connection and a Dbus path.
func NewCollection(conn *dbus.Conn, path dbus.ObjectPath) (coll *Collection, err error) {

if _, err = validConnPath(conn, path); err != nil {
if _, err = validConnPath(service.Conn, path); err != nil {
return
}

coll = &Collection{
DbusObject: &DbusObject{
Conn: conn,
Dbus: conn.Object(DbusService, path),
Conn: service.Conn,
Dbus: service.Conn.Object(DbusService, path),
},
// lastModified: time.Now(),
}

splitPath = strings.Split(string(coll.Dbus.Path()), "")

coll.name = splitPath[len(splitPath)-1]

_, _, err = coll.Modified()

return
@ -54,16 +44,28 @@ func NewCollection(conn *dbus.Conn, path dbus.ObjectPath) (coll *Collection, err
func (c *Collection) Items() (items []*Item, err error) {

var paths []dbus.ObjectPath
var item *Item
var variant dbus.Variant
var errs []error = make([]error, 0)

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

paths = variant.Value().([]dbus.ObjectPath)

items = make([]*Item, len(paths))

for idx, path := range paths {
items[idx] = NewItem(c.Conn, path)
if item, err = NewItem(c, path); err != nil {
// return
errs = append(errs, err)
err = nil
continue
}
items[idx] = item
}
err = NewErrors(err)

return
}
@ -90,27 +92,35 @@ func (c *Collection) Delete() (err error) {
}

// SearchItems searches a Collection for a matching profile string.
func (c *Collection) SearchItems(profile string) (items []Item, err error) {
func (c *Collection) SearchItems(profile string) (items []*Item, err error) {

var paths []dbus.ObjectPath
var errs []error = make([]error, 0)
var attrs map[string]string = make(map[string]string, 0)

attrs["profile"] = profile

if err = c.Dbus.Call("org.freedesktop.Secret.Collection.SearchItems", 0, attrs).Store(&paths); err != nil {
if err = c.Dbus.Call(
DbusCollectionSearchItems, 0, attrs,
).Store(&paths); err != nil {
return
}

items = make([]Item, len(paths))
items = make([]*Item, len(paths))

for idx, path := range paths {
items[idx] = *NewItem(c.Conn, path)
if items[idx], err = NewItem(c, path); err != nil {
errs = append(errs, err)
err = nil
continue
}
}
err = NewErrors(err)

return
}

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

var prompt *Prompt
@ -140,7 +150,7 @@ func (c *Collection) CreateItem(label string, secret *Secret, replace bool) (ite
path = variant.Value().(dbus.ObjectPath)
}

item = NewItem(c.Conn, path)
item, err = NewItem(c, path)

return
}
@ -150,7 +160,7 @@ func (c *Collection) Locked() (isLocked bool, err error) {

var variant dbus.Variant

if variant, err = c.Dbus.GetProperty("org.freedesktop.Secret.Collection.Locked"); err != nil {
if variant, err = c.Dbus.GetProperty(DbusCollectionLocked); err != nil {
isLocked = true
return
}
@ -163,7 +173,13 @@ func (c *Collection) Locked() (isLocked bool, err error) {
// Label returns the Collection label (name).
func (c *Collection) Label() (label string, err error) {

// TODO.
var variant dbus.Variant

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

label = variant.Value().(string)

return
}
@ -171,7 +187,16 @@ func (c *Collection) Label() (label string, err error) {
// Created returns the time.Time of when a Collection was created.
func (c *Collection) Created() (created time.Time, err error) {

// TODO.
var variant dbus.Variant
var timeInt uint64

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

timeInt = variant.Value().(uint64)

created = time.Unix(int64(timeInt), 0)

return
}
@ -181,17 +206,30 @@ func (c *Collection) Created() (created time.Time, err error) {
that indicates if the collection has changed since the last call of Collection.Modified.

Note that when calling NewCollection, the internal library-tracked modification
time (Collection.lastModified) will be set to the modification time of the Collection
itself as reported by Dbus.
time (Collection.lastModified) will be set to the latest modification time of the Collection
itself as reported by Dbus rather than the time that NewCollection was called.
*/
func (c *Collection) Modified() (modified time.Time, isChanged bool, err error) {

// TODO.
var variant dbus.Variant
var timeInt uint64

if c.lastModified == time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC) {
// It's "nil", so set it to modified.
c.lastModified = modified
if variant, err = c.Dbus.GetProperty(DbusCollectionModified); err != nil {
return
}

timeInt = variant.Value().(uint64)

modified = time.Unix(int64(timeInt), 0)

if !c.lastModifiedSet {
// It's "nil", so set it to modified. We can't check for a zero-value in case Dbus has it as a zero-value.
c.lastModified = modified
c.lastModifiedSet = true
}

isChanged = modified.After(c.lastModified)
c.lastModified = modified

return
}

View File

@ -157,6 +157,8 @@ const (
DbusPath string = "/org/freedesktop/secrets"
// DbusPromptPrefix is the path used for prompts comparison.
DbusPromptPrefix string = DbusPath + "/prompt/"
// DbusNewCollectionPath is used to create a new Collection.
DbusNewCollectionPath string = DbusPath + "/collection/"
)

// FLAGS

View File

@ -14,6 +14,10 @@ var (
ErrNoDbusConn error = errors.New("no valid dbus connection")
// ErrMissingPaths gets triggered if one or more Dbus object paths are expected but non/not enough are received.
ErrMissingPaths error = errors.New("one or more Dbus object paths were expected but an insufficient amount were received")
// ErrMissingAttrs gets triggered if attributes were expected but not passed.
ErrMissingAttrs error = errors.New("attributes must not be empty/nil")
// ErrDoesNotExist gets triggered if a Collection, Item, etc. is attempted to be fetched but none exists via the specified identifier.
ErrDoesNotExist error = errors.New("the object under that name/label/alias does not exist")
)

/*

View File

@ -4,12 +4,22 @@ import (
`github.com/godbus/dbus`
)

// NewItem returns a pointer to a new Item based on a Dbus connection and a Dbus path.
func NewItem(conn *dbus.Conn, path dbus.ObjectPath) (item *Item) {
// NewItem returns a pointer to an Item based on Collection and a Dbus path.
func NewItem(collection *Collection, path dbus.ObjectPath) (item *Item, err error) {

if collection == nil {
err = ErrNoDbusConn
}

if _, err = validConnPath(collection.Conn, path); err != nil {
return
}

item = &Item{
Conn: conn,
Dbus: conn.Object(DbusService, path),
&DbusObject{
Conn: collection.Conn,
Dbus: collection.Conn.Object(DbusService, path),
},
}

return
@ -51,7 +61,7 @@ func (i *Item) GetSecret(session *Session) (secret *Secret, err error) {

if err = i.Dbus.Call(
"org.freedesktop.Secret.Item.GetSecret", 0, session.Path(),
).Store(&secret); err != nil {
).Store(&secret); err != nil {
return
}


View File

@ -32,7 +32,7 @@ func (s *Service) Close() (err error) {
}

// Collections returns a slice of Collection items accessible to this Service.
func (s *Service) Collections() (collections []Collection, err error) {
func (s *Service) Collections() (collections []*Collection, err error) {

var paths []dbus.ObjectPath
var variant dbus.Variant
@ -45,15 +45,16 @@ func (s *Service) Collections() (collections []Collection, err error) {

paths = variant.Value().([]dbus.ObjectPath)

collections = make([]Collection, len(paths))
collections = make([]*Collection, len(paths))

for idx, path := range paths {
if coll, err = NewCollection(s.Conn, path); err != nil {
if coll, err = NewCollection(s, path); err != nil {
// return
errs = append(errs, err)
err = nil
continue
}
collections[idx] = *coll
collections[idx] = coll
}
err = NewErrors(err)

@ -91,7 +92,7 @@ func (s *Service) CreateAliasedCollection(label, alias string) (collection *Coll
path = variant.Value().(dbus.ObjectPath)
}

collection, err = NewCollection(s.Conn, path)
collection, err = NewCollection(s, path)

return
}
@ -108,16 +109,71 @@ func (s *Service) CreateCollection(label string) (collection *Collection, err er
}

/*
GetAlias allows one to fetch a Collection dbus.ObjectPath based on an alias name.
If the alias does not exist, objectPath will be dbus.ObjectPath("/").
TODO: return a Collection instead of a dbus.ObjectPath.
GetAlias allows one to fetch a Collection based on an alias name.
An ErrDoesNotExist will be raised if the alias does not exist.
You will almost assuredly want to use Service.GetCollection instead; it works for both alias names and real names.
*/
func (s *Service) GetAlias(alias string) (objectPath dbus.ObjectPath, err error) {
func (s *Service) GetAlias(alias string) (collection *Collection, err error) {

var objectPath dbus.ObjectPath

err = s.Dbus.Call(
DbusServiceReadAlias, 0, alias,
).Store(&objectPath)

/*
TODO: Confirm that a nonexistent alias will NOT cause an error to return.
If it does, alter the below logic.
*/
if err != nil {
return
}

// If the alias does not exist, objectPath will be dbus.ObjectPath("/").
if objectPath == dbus.ObjectPath("/") {
err = ErrDoesNotExist
return
}

if collection, err = NewCollection(s, objectPath); err != nil {
return
}

return
}

/*
GetCollection returns a single Collection based on the name (name can also be an alias).
It's a helper function that avoids needing to make multiple calls in user code.
*/
func (s *Service) GetCollection(name string) (c *Collection, err error) {

var colls []*Collection

// First check for an alias.
if c, err = s.GetAlias(name); err != nil && err != ErrDoesNotExist{
return
}
if c != nil {
return
} else {
c = nil
}

// We didn't get it by alias, so let's try by name...
if colls, err = s.Collections(); err != nil {
return
}
for _, i := range colls {
if i.name == name {
c = i
return
}
}

// Couldn't find it by the given name.
err = ErrDoesNotExist

return
}

@ -217,10 +273,14 @@ func (s *Service) Open() (session *Session, output dbus.Variant, err error) {
/*
SearchItems searches all Collection objects and returns all matches based on the map of attributes.
TODO: return arrays of Items instead of dbus.ObjectPaths.
TODO: check attributes for empty/nil.
*/
func (s *Service) SearchItems(attributes map[string]string) (unlockedItems []dbus.ObjectPath, lockedItems []dbus.ObjectPath, err error) {

if attributes == nil || len(attributes) == 0 {
err = ErrMissingAttrs
return
}

err = s.Dbus.Call(
DbusServiceSearchItems, 0, attributes,
).Store(&unlockedItems, &lockedItems)

View File

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

import (
`github.com/godbus/dbus`
)

// NewService returns a pointer to a new Service.
func NewService() (service *Service, err error) {

service = &Service{
Conn: nil,
Dbus: nil,
}

if service.Conn, err = dbus.SessionBus(); err != nil {
return
}
service.Dbus = service.Conn.Object(DbusService, dbus.ObjectPath(DbusPath))

return
}

// Path returns the path of the underlying Dbus connection.
func (s *Service) Path() (path dbus.ObjectPath) {

// Remove this method in V1. It's bloat since we now have an exported Dbus.
path = s.Dbus.Path()

return
}

// Open returns a pointer to a Session from the Service.
func (s *Service) Open() (session *Session, err error) {

var output dbus.Variant
var path dbus.ObjectPath

if err = s.Dbus.Call(
"org.freedesktop.Secret.Service.OpenSession", 0, "plain", dbus.MakeVariant(""),
).Store(&output, &path); err != nil {
return
}

session = NewSession(s.Conn, path)

return
}

// Collections returns a slice of Collection keyrings accessible to this Service.
func (s *Service) Collections() (collections []Collection, err error) {

var paths []dbus.ObjectPath
var variant dbus.Variant

if variant, err = s.Dbus.GetProperty("org.freedesktop.Secret.Service.Collections"); err != nil {
return
}

paths = variant.Value().([]dbus.ObjectPath)

collections = make([]Collection, len(paths))

for idx, path := range paths {
collections[idx] = *NewCollection(s.Conn, path)
}

return
}

// CreateCollection creates a new Collection (keyring) via a Service with the name specified by label.
func (s *Service) CreateCollection(label string) (collection *Collection, err error) {

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

props["org.freedesktop.Secret.Collection.Label"] = dbus.MakeVariant(label)

if err = s.Dbus.Call("org.freedesktop.Secret.Service.CreateCollection", 0, props, "").Store(&path, &promptPath); err != nil {
return
}

if isPrompt(promptPath) {

prompt = NewPrompt(s.Conn, promptPath)

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

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

collection = NewCollection(s.Conn, path)

return
}

// Unlock unlocks a Locked Service.
func (s *Service) Unlock(object DBusObject) (err error) {

var unlocked []dbus.ObjectPath
var prompt *Prompt
var promptPath dbus.ObjectPath
var paths []dbus.ObjectPath = []dbus.ObjectPath{object.Path()}

if err = s.Dbus.Call("org.freedesktop.Secret.Service.Unlock", 0, paths).Store(&unlocked, &promptPath); err != nil {
return
}

if isPrompt(promptPath) {

prompt = NewPrompt(s.Conn, promptPath)

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

return
}

// Lock locks an Unlocked Service.
func (s *Service) Lock(object DBusObject) (err error) {

var locked []dbus.ObjectPath
var prompt *Prompt
var promptPath dbus.ObjectPath
var paths []dbus.ObjectPath = []dbus.ObjectPath{object.Path()}

if err = s.Dbus.Call("org.freedesktop.Secret.Service.Lock", 0, paths).Store(&locked, &promptPath); err != nil {
return
}

if isPrompt(promptPath) {

prompt = NewPrompt(s.Conn, promptPath)

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

return
}

View File

@ -4,6 +4,8 @@ import (
"github.com/godbus/dbus"
)

// I'm still not 100% certain what Sessions are used for?

/*
NewSession returns a pointer to a new Session based on a Service and a dbus.ObjectPath.
If path is empty (""), the default

View File

@ -87,6 +87,10 @@ type Collection struct {
*DbusObject
// lastModified is unexported because it's important that API users don't change it; it's used by Collection.Modified.
lastModified time.Time
// lastModifiedSet is unexported; it's only used to determine if this is a first-initialization of the modification time or not.
lastModifiedSet bool
// name is used for the Collection's name/label so the Dnus path doesn't need to be parsed all the time.
name string
}

/*