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 package gosecret


import ( import (
"strings" `strings`
"time" "time"


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


/* /*
CreateCollection creates a new Collection named `name` using the dbus.BusObject `secretServiceConn`. NewCollection returns a pointer to a Collection based on a Service and a Dbus path.
`secretServiceConn` should be the same as used for Collection.Dbus (and/or NewCollection). You will almost always want to use Service.GetCollection instead.
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.
*/ */
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 err = ErrNoDbusConn
return
} }


path = dbus.ObjectPath(strings.Join([]string{DbusPath, ""}, "/")) if _, err = validConnPath(service.Conn, path); err != nil {

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


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


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

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

_, _, err = coll.Modified() _, _, err = coll.Modified()


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


var paths []dbus.ObjectPath 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 return
} }


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

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


for idx, path := range 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 return
} }
@ -90,27 +92,35 @@ func (c *Collection) Delete() (err error) {
} }


// SearchItems searches a Collection for a matching profile string. // 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 paths []dbus.ObjectPath
var errs []error = make([]error, 0)
var attrs map[string]string = make(map[string]string, 0) var attrs map[string]string = make(map[string]string, 0)


attrs["profile"] = profile 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 return
} }


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


for idx, path := range 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 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) { func (c *Collection) CreateItem(label string, secret *Secret, replace bool) (item *Item, err error) {


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


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


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


var variant dbus.Variant 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 isLocked = true
return return
} }
@ -163,7 +173,13 @@ func (c *Collection) Locked() (isLocked bool, err error) {
// Label returns the Collection label (name). // Label returns the Collection label (name).
func (c *Collection) Label() (label string, err error) { 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 return
} }
@ -171,7 +187,16 @@ func (c *Collection) Label() (label string, err error) {
// Created returns the time.Time of when a Collection was created. // Created returns the time.Time of when a Collection was created.
func (c *Collection) Created() (created time.Time, err error) { 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 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. that indicates if the collection has changed since the last call of Collection.Modified.


Note that when calling NewCollection, the internal library-tracked modification Note that when calling NewCollection, the internal library-tracked modification
time (Collection.lastModified) will be set to the modification time of the Collection time (Collection.lastModified) will be set to the latest modification time of the Collection
itself as reported by Dbus. itself as reported by Dbus rather than the time that NewCollection was called.
*/ */
func (c *Collection) Modified() (modified time.Time, isChanged bool, err error) { 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) { if variant, err = c.Dbus.GetProperty(DbusCollectionModified); err != nil {
// It's "nil", so set it to modified. return
c.lastModified = modified
} }


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 return
} }

View File

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


// FLAGS // FLAGS

View File

@ -14,6 +14,10 @@ var (
ErrNoDbusConn error = errors.New("no valid dbus connection") 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 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") 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` `github.com/godbus/dbus`
) )


// NewItem returns a pointer to a new Item based on a Dbus connection and a Dbus path. // NewItem returns a pointer to an Item based on Collection and a Dbus path.
func NewItem(conn *dbus.Conn, path dbus.ObjectPath) (item *Item) { 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{ item = &Item{
Conn: conn, &DbusObject{
Dbus: conn.Object(DbusService, path), Conn: collection.Conn,
Dbus: collection.Conn.Object(DbusService, path),
},
} }


return return

View File

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


// Collections returns a slice of Collection items accessible to this Service. // 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 paths []dbus.ObjectPath
var variant dbus.Variant var variant dbus.Variant
@ -45,15 +45,16 @@ func (s *Service) Collections() (collections []Collection, err error) {


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


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


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


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


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


return 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. GetAlias allows one to fetch a Collection based on an alias name.
If the alias does not exist, objectPath will be dbus.ObjectPath("/"). An ErrDoesNotExist will be raised if the alias does not exist.
TODO: return a Collection instead of a dbus.ObjectPath. 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( err = s.Dbus.Call(
DbusServiceReadAlias, 0, alias, DbusServiceReadAlias, 0, alias,
).Store(&objectPath) ).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 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. 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: 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) { 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( err = s.Dbus.Call(
DbusServiceSearchItems, 0, attributes, DbusServiceSearchItems, 0, attributes,
).Store(&unlockedItems, &lockedItems) ).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" "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. NewSession returns a pointer to a new Session based on a Service and a dbus.ObjectPath.
If path is empty (""), the default If path is empty (""), the default

View File

@ -87,6 +87,10 @@ type Collection struct {
*DbusObject *DbusObject
// lastModified is unexported because it's important that API users don't change it; it's used by Collection.Modified. // lastModified is unexported because it's important that API users don't change it; it's used by Collection.Modified.
lastModified time.Time 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
} }


/* /*