diff --git a/collection_funcs.go b/collection_funcs.go index 5029d62..11511ee 100644 --- a/collection_funcs.go +++ b/collection_funcs.go @@ -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 } diff --git a/consts.go b/consts.go index 3616f29..f0e349c 100644 --- a/consts.go +++ b/consts.go @@ -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 diff --git a/errs.go b/errs.go index 7569eac..5822fe2 100644 --- a/errs.go +++ b/errs.go @@ -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") ) /* diff --git a/item_funcs.go b/item_funcs.go index 1794b8b..acb8162 100644 --- a/item_funcs.go +++ b/item_funcs.go @@ -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 } diff --git a/service_funcs.go b/service_funcs.go index 07b3546..d03b1d4 100644 --- a/service_funcs.go +++ b/service_funcs.go @@ -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) diff --git a/service_funcs.go.old b/service_funcs.go.old deleted file mode 100644 index c8b25b0..0000000 --- a/service_funcs.go.old +++ /dev/null @@ -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 -} diff --git a/session_funcs.go b/session_funcs.go index 4664698..edc4026 100644 --- a/session_funcs.go +++ b/session_funcs.go @@ -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 diff --git a/types.go b/types.go index c8a14a1..d783e9f 100644 --- a/types.go +++ b/types.go @@ -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 } /*