From 5975b5ed5c664fd412550c6c1c0b579d6b1dbc97 Mon Sep 17 00:00:00 2001 From: brent s Date: Sun, 21 Nov 2021 18:07:52 -0500 Subject: [PATCH] v0.0.1 is now done. Cleaned up file naming/organization, optimized some things like using preallocated indexed slices instead of appends, etc. --- LICENSE | 3 +- README.adoc | 62 +++++++++++++++++++ collection.go | 124 ------------------------------------- collection_funcs.go | 138 +++++++++++++++++++++++++++++++++++++++++ consts.go | 7 +++ funcs.go | 16 +++++ go.mod | 5 ++ go.sum | 2 + item.go | 77 ----------------------- item_funcs.go | 89 +++++++++++++++++++++++++++ prompt.go | 55 ----------------- prompt_funcs.go | 50 +++++++++++++++ secret.go | 21 ------- secret_funcs.go | 14 +++++ service.go | 141 ------------------------------------------ service_funcs.go | 147 ++++++++++++++++++++++++++++++++++++++++++++ session.go | 22 ------- session_funcs.go | 25 ++++++++ types.go | 88 ++++++++++++++++++++++++++ 19 files changed, 644 insertions(+), 442 deletions(-) create mode 100644 README.adoc delete mode 100644 collection.go create mode 100644 collection_funcs.go create mode 100644 consts.go create mode 100644 funcs.go create mode 100644 go.mod create mode 100644 go.sum delete mode 100644 item.go create mode 100644 item_funcs.go delete mode 100644 prompt.go create mode 100644 prompt_funcs.go delete mode 100644 secret.go create mode 100644 secret_funcs.go delete mode 100644 service.go create mode 100644 service_funcs.go delete mode 100644 session.go create mode 100644 session_funcs.go create mode 100644 types.go diff --git a/LICENSE b/LICENSE index 51f2292..02d5586 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016 Goran Sterjov +Copyright (c) 2021 Brent Saner Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -19,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/README.adoc b/README.adoc new file mode 100644 index 0000000..8e5d42b --- /dev/null +++ b/README.adoc @@ -0,0 +1,62 @@ += libsecret/gosecret +Brent Saner +Last updated {localdatetime} +:doctype: book +:docinfo: shared +:data-uri: +:imagesdir: images +:sectlinks: +:sectnums: +:sectnumlevels: 7 +:toc: preamble +:toc2: left +:idprefix: +:toclevels: 7 +:source-highlighter: rouge + +This project is originally forked from https://github.com/gsterjov/go-libsecret[go-libsecret^] due to: + +* Lack of response from the developer +* Complete lack of documentation +* Poor, ineffecient, or just plain antipattern design +* Missing functionality + +and as such, hopefully this library should serve as a more effective libsecret/SecretService interface. + +== Backwards Compatability/Drop-In Replacement Support +Version series `v0.X.X` of this library promises full and non-breaking backwards support of API interaction with the original project. The only changes should be internal optimizations, adding documentation, some file reorganizing, adding Golang module support, etc. -- all transparent from the library API itself. + +To use this library as a replacement without significantly modifying your code, you can simply use a `replace` directive: + +// TODO: did I do this correctly? I never really use replacements so someone PR if this is incorrect. +.go.mod +[source] +---- +# ... +replace ( + github.com/gsterjov/go-libsecret dev => r00t2.io/gosecret v0 +) +---- + +and then run `go mod tidy`. + +== New Developer API +Starting from `v1.0.0` onwards, entirely breaking changes can be assumed from the original project. + +To use the new version, + +[source,go] +---- +import ( + `r00t2.io/gosecret/v1` +) +---- + +To reflect the absolute breaking changes, the module name changes as well from `libsecret` to `gosecret`. + +== Usage +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. +//// diff --git a/collection.go b/collection.go deleted file mode 100644 index a835ae5..0000000 --- a/collection.go +++ /dev/null @@ -1,124 +0,0 @@ -package libsecret - -import "github.com/godbus/dbus" - - -type Collection struct { - conn *dbus.Conn - dbus dbus.BusObject -} - - -func NewCollection(conn *dbus.Conn, path dbus.ObjectPath) *Collection { - return &Collection{ - conn: conn, - dbus: conn.Object(DBusServiceName, path), - } -} - - -func (collection Collection) Path() dbus.ObjectPath { - return collection.dbus.Path() -} - - -// READ Array Items; -func (collection *Collection) Items() ([]Item, error) { - val, err := collection.dbus.GetProperty("org.freedesktop.Secret.Collection.Items") - if err != nil { - return []Item{}, err - } - - items := []Item{} - for _, path := range val.Value().([]dbus.ObjectPath) { - items = append(items, *NewItem(collection.conn, path)) - } - - return items, nil -} - - -// Delete (OUT ObjectPath prompt); -func (collection *Collection) Delete() error { - var prompt dbus.ObjectPath - - err := collection.dbus.Call("org.freedesktop.Secret.Collection.Delete", 0).Store(&prompt) - if err != nil { - return err - } - - if isPrompt(prompt) { - prompt := NewPrompt(collection.conn, prompt) - - _, err := prompt.Prompt() - if err != nil { - return err - } - } - - return nil -} - - -// SearchItems (IN Dict attributes, OUT Array results); -func (collection *Collection) SearchItems(profile string) ([]Item, error) { - attributes := make(map[string]string) - attributes["profile"] = profile - - var paths []dbus.ObjectPath - - err := collection.dbus.Call("org.freedesktop.Secret.Collection.SearchItems", 0, attributes).Store(&paths) - if err != nil { - return []Item{}, err - } - - items := []Item{} - for _, path := range paths { - items = append(items, *NewItem(collection.conn, path)) - } - - return items, nil -} - - -// CreateItem (IN Dict properties, IN Secret secret, IN Boolean replace, OUT ObjectPath item, OUT ObjectPath prompt); -func (collection *Collection) CreateItem(label string, secret *Secret, replace bool) (*Item, error) { - properties := make(map[string]dbus.Variant) - attributes := make(map[string]string) - - attributes["profile"] = label - properties["org.freedesktop.Secret.Item.Label"] = dbus.MakeVariant(label) - properties["org.freedesktop.Secret.Item.Attributes"] = dbus.MakeVariant(attributes) - - var path dbus.ObjectPath - var prompt dbus.ObjectPath - - err := collection.dbus.Call("org.freedesktop.Secret.Collection.CreateItem", 0, properties, secret, replace).Store(&path, &prompt) - if err != nil { - return &Item{}, err - } - - if isPrompt(prompt) { - prompt := NewPrompt(collection.conn, prompt) - - result, err := prompt.Prompt() - if err != nil { - return &Item{}, err - } - - path = result.Value().(dbus.ObjectPath) - } - - return NewItem(collection.conn, path), nil -} - - -// READ Boolean Locked; -func (collection *Collection) Locked() (bool, error) { - val, err := collection.dbus.GetProperty("org.freedesktop.Secret.Collection.Locked") - if err != nil { - return true, err - } - - return val.Value().(bool), nil -} diff --git a/collection_funcs.go b/collection_funcs.go new file mode 100644 index 0000000..0b2c9e4 --- /dev/null +++ b/collection_funcs.go @@ -0,0 +1,138 @@ +package libsecret + +import ( + `github.com/godbus/dbus` +) + +// 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) { + + coll = &Collection{ + Conn: conn, + Dbus: conn.Object(DBusServiceName, path), + } + + return +} + +// Path returns the dbus.ObjectPath of the Collection. +func (c Collection) Path() (coll dbus.ObjectPath) { + + // Remove this method in V1. It's bloat since we now have an exported Dbus. + coll = c.Dbus.Path() + + return +} + +// Items returns a slice of Item items in the Collection. +func (c *Collection) Items() (items []Item, err error) { + + var variant dbus.Variant + var paths []dbus.ObjectPath + + if variant, err = c.Dbus.GetProperty("org.freedesktop.Secret.Collection.Items"); err != nil { + return + } + + paths = variant.Value().([]dbus.ObjectPath) + + items = make([]Item, len(paths)) + + for idx, path := range paths { + items[idx] = *NewItem(c.Conn, path) + } + + return +} + +// Delete removes a Collection. +func (c *Collection) Delete() (err error) { + + var promptPath dbus.ObjectPath + var prompt *Prompt + + if err = c.Dbus.Call("org.freedesktop.Secret.Collection.Delete", 0).Store(&promptPath); err != nil { + return + } + + if isPrompt(promptPath) { + + prompt = NewPrompt(c.Conn, promptPath) + if _, err = prompt.Prompt(); err != nil { + return + } + } + + return +} + +// SearchItems searches a Collection for a matching profile string. +func (c *Collection) SearchItems(profile string) (items []Item, err error) { + + var paths []dbus.ObjectPath + 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 { + return + } + + items = make([]Item, len(paths)) + + for idx, path := range paths { + items[idx] = *NewItem(c.Conn, path) + } + + return +} + +// CreateItem returns a pointer to an Item based on a label, a Secret, and whether any existing secret should be replaced or not. +func (c *Collection) CreateItem(label string, secret *Secret, replace bool) (item *Item, err error) { + + var prompt *Prompt + var path dbus.ObjectPath + var promptPath dbus.ObjectPath + var variant *dbus.Variant + var props map[string]dbus.Variant = make(map[string]dbus.Variant) + var attrs map[string]string = make(map[string]string) + + attrs["profile"] = label + props["org.freedesktop.Secret.Item.Label"] = dbus.MakeVariant(label) + props["org.freedesktop.Secret.Item.Attributes"] = dbus.MakeVariant(attrs) + + if err = c.Dbus.Call( + "org.freedesktop.Secret.Collection.CreateItem", 0, props, secret, replace, + ).Store(&path, &promptPath); err != nil { + return + } + + if isPrompt(promptPath) { + prompt = NewPrompt(c.Conn, promptPath) + + if variant, err = prompt.Prompt(); err != nil { + return + } + + path = variant.Value().(dbus.ObjectPath) + } + + item = NewItem(c.Conn, path) + + return +} + +// Locked indicates that a Collection is locked (true) or unlocked (false). +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 { + isLocked = true + return + } + + isLocked = variant.Value().(bool) + + return +} diff --git a/consts.go b/consts.go new file mode 100644 index 0000000..c8e574a --- /dev/null +++ b/consts.go @@ -0,0 +1,7 @@ +package libsecret + +const ( + DBusServiceName string = "org.freedesktop.secrets" + DBusPath string = "/org/freedesktop/secrets" + PromptPrefix string = DBusPath + "/prompt/" +) diff --git a/funcs.go b/funcs.go new file mode 100644 index 0000000..612e7eb --- /dev/null +++ b/funcs.go @@ -0,0 +1,16 @@ +package libsecret + +import ( + `strings` + + `github.com/godbus/dbus` +) + +// isPrompt returns a boolean that is true if path is/requires a prompt(ed path) and false if it is/does not. +func isPrompt(path dbus.ObjectPath) (prompt bool) { + + prompt = strings.HasPrefix(string(path), PromptPrefix) + + return + +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..477c7cb --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module r00t2.io/gosecret + +go 1.17 + +require github.com/godbus/dbus v4.1.0+incompatible diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3a4ab68 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4= +github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= diff --git a/item.go b/item.go deleted file mode 100644 index f0e6ad3..0000000 --- a/item.go +++ /dev/null @@ -1,77 +0,0 @@ -package libsecret - -import "github.com/godbus/dbus" - - -type Item struct { - conn *dbus.Conn - dbus dbus.BusObject -} - - -func NewItem(conn *dbus.Conn, path dbus.ObjectPath) *Item { - return &Item{ - conn: conn, - dbus: conn.Object(DBusServiceName, path), - } -} - - -func (item Item) Path() dbus.ObjectPath { - return item.dbus.Path() -} - - -// READWRITE String Label; -func (item *Item) Label() (string, error) { - val, err := item.dbus.GetProperty("org.freedesktop.Secret.Item.Label") - if err != nil { - return "", err - } - - return val.Value().(string), nil -} - - -// READ Boolean Locked; -func (item *Item) Locked() (bool, error) { - val, err := item.dbus.GetProperty("org.freedesktop.Secret.Item.Locked") - if err != nil { - return true, err - } - - return val.Value().(bool), nil -} - - -// GetSecret (IN ObjectPath session, OUT Secret secret); -func (item *Item) GetSecret(session *Session) (*Secret, error) { - secret := Secret{} - - err := item.dbus.Call("org.freedesktop.Secret.Item.GetSecret", 0, session.Path()).Store(&secret) - if err != nil { - return &Secret{}, err - } - - return &secret, nil -} - - -// Delete (OUT ObjectPath Prompt); -func (item *Item) Delete() error { - var prompt dbus.ObjectPath - - err := item.dbus.Call("org.freedesktop.Secret.Item.Delete", 0).Store(&prompt) - if err != nil { - return err - } - - if isPrompt(prompt) { - prompt := NewPrompt(item.conn, prompt) - if _, err := prompt.Prompt(); err != nil { - return err - } - } - - return nil -} diff --git a/item_funcs.go b/item_funcs.go new file mode 100644 index 0000000..dbec50f --- /dev/null +++ b/item_funcs.go @@ -0,0 +1,89 @@ +package libsecret + +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) { + + item = &Item{ + Conn: conn, + Dbus: conn.Object(DBusServiceName, path), + } + + return +} + +// Path returns the path of the underlying Dbus connection. +func (i Item) Path() (path dbus.ObjectPath) { + + // Remove this method in V1. It's bloat since we now have an exported Dbus. + path = i.Dbus.Path() + + return +} + +// Label returns the label ("name") of an Item. +func (i *Item) Label() (label string, err error) { + + var variant dbus.Variant + + if variant, err = i.Dbus.GetProperty("org.freedesktop.Secret.Item.Label"); err != nil { + return + } + + label = variant.Value().(string) + + return +} + +// Locked indicates that an Item is locked (true) or unlocked (false). +func (i *Item) Locked() (isLocked bool, err error) { + + var variant dbus.Variant + + if variant, err = i.Dbus.GetProperty("org.freedesktop.Secret.Item.Locked"); err != nil { + isLocked = true + return + } + + isLocked = variant.Value().(bool) + + return +} + +// GetSecret returns the Secret in an Item using a Session. +func (i *Item) GetSecret(session *Session) (secret *Secret, err error) { + + secret = new(Secret) + + if err = i.Dbus.Call( + "org.freedesktop.Secret.Item.GetSecret", 0, session.Path(), + ).Store(&secret); err != nil { + return + } + + return +} + +// Delete removes an Item from a Collection. +func (i *Item) Delete() (err error) { + + var prompt *Prompt + var promptPath dbus.ObjectPath + + if err = i.Dbus.Call("org.freedesktop.Secret.Item.Delete", 0).Store(&promptPath); err != nil { + return + } + + if isPrompt(promptPath) { + prompt = NewPrompt(i.Conn, promptPath) + + if _, err = prompt.Prompt(); err != nil { + return + } + } + + return +} diff --git a/prompt.go b/prompt.go deleted file mode 100644 index 2c7d3f8..0000000 --- a/prompt.go +++ /dev/null @@ -1,55 +0,0 @@ -package libsecret - -import ( - "github.com/godbus/dbus" - "strings" -) - - -type Prompt struct { - conn *dbus.Conn - dbus dbus.BusObject -} - - -func NewPrompt(conn *dbus.Conn, path dbus.ObjectPath) *Prompt { - return &Prompt{ - conn: conn, - dbus: conn.Object(DBusServiceName, path), - } -} - - -func (prompt Prompt) Path() dbus.ObjectPath { - return prompt.dbus.Path() -} - - -func isPrompt(path dbus.ObjectPath) bool { - promptPath := DBusPath + "/prompt/" - return strings.HasPrefix(string(path), promptPath) -} - - -// Prompt (IN String window-id); -func (prompt *Prompt) Prompt() (*dbus.Variant, error) { - // prompts are asynchronous so we connect to the signal - // and block with a channel until we get a response - c := make(chan *dbus.Signal, 10) - defer close(c) - - prompt.conn.Signal(c) - defer prompt.conn.RemoveSignal(c) - - err := prompt.dbus.Call("org.freedesktop.Secret.Prompt.Prompt", 0, "").Store() - if err != nil { - return &dbus.Variant{}, err - } - - for { - if result := <-c; result.Path == prompt.Path() { - value := result.Body[1].(dbus.Variant) - return &value, nil - } - } -} diff --git a/prompt_funcs.go b/prompt_funcs.go new file mode 100644 index 0000000..3f46a4c --- /dev/null +++ b/prompt_funcs.go @@ -0,0 +1,50 @@ +package libsecret + +import ( + `github.com/godbus/dbus` +) + +// NewPrompt returns a pointer to a new Prompt based on a Dbus connection and a Dbus path. +func NewPrompt(conn *dbus.Conn, path dbus.ObjectPath) (prompt *Prompt) { + + prompt = &Prompt{ + Conn: conn, + Dbus: conn.Object(DBusServiceName, path), + } + + return +} + +// Path returns the path of the underlying Dbus connection. +func (p Prompt) Path() (path dbus.ObjectPath) { + + // Remove this method in V1. It's bloat since we now have an exported Dbus. + path = p.Dbus.Path() + + return +} + +// Prompt issues/waits for a prompt for unlocking a Locked Collection or Secret / Item. +func (p *Prompt) Prompt() (promptValue *dbus.Variant, err error) { + + var c chan *dbus.Signal + var result *dbus.Signal + + // Prompts are asynchronous; we connect to the signal and block with a channel until we get a response. + c = make(chan *dbus.Signal, 10) + defer close(c) + + p.Conn.Signal(c) + defer p.Conn.RemoveSignal(c) + + if err = p.Dbus.Call("org.freedesktop.Secret.Prompt.Prompt", 0, "").Store(); err != nil { + return + } + + for { + if result = <-c; result.Path == p.Path() { + *promptValue = result.Body[1].(dbus.Variant) + return + } + } +} diff --git a/secret.go b/secret.go deleted file mode 100644 index 09393be..0000000 --- a/secret.go +++ /dev/null @@ -1,21 +0,0 @@ -package libsecret - -import "github.com/godbus/dbus" - - -type Secret struct { - Session dbus.ObjectPath - Parameters []byte - Value []byte - ContentType string -} - - -func NewSecret(session *Session, params []byte, value []byte, contentType string) *Secret { - return &Secret{ - Session: session.Path(), - Parameters: params, - Value: value, - ContentType: contentType, - } -} diff --git a/secret_funcs.go b/secret_funcs.go new file mode 100644 index 0000000..ab32734 --- /dev/null +++ b/secret_funcs.go @@ -0,0 +1,14 @@ +package libsecret + +// NewSecret returns a pointer to a new Secret based on a Session, parameters, (likely an empty byte slice), a value, and the MIME content type. +func NewSecret(session *Session, params []byte, value []byte, contentType string) (secret *Secret) { + + secret = &Secret{ + Session: session.Path(), + Parameters: params, + Value: value, + ContentType: contentType, + } + + return +} diff --git a/service.go b/service.go deleted file mode 100644 index bfe0f27..0000000 --- a/service.go +++ /dev/null @@ -1,141 +0,0 @@ -package libsecret - -import "github.com/godbus/dbus" - - -const ( - DBusServiceName = "org.freedesktop.secrets" - DBusPath = "/org/freedesktop/secrets" -) - -type DBusObject interface { - Path() dbus.ObjectPath -} - - -type Service struct { - conn *dbus.Conn - dbus dbus.BusObject -} - - -func NewService() (*Service, error) { - conn, err := dbus.SessionBus() - if err != nil { - return &Service{}, err - } - - return &Service{ - conn: conn, - dbus: conn.Object(DBusServiceName, DBusPath), - }, nil -} - - -func (service Service) Path() dbus.ObjectPath { - return service.dbus.Path() -} - - -// OpenSession (IN String algorithm, IN Variant input, OUT Variant output, OUT ObjectPath result); -func (service *Service) Open() (*Session, error) { - var output dbus.Variant - var path dbus.ObjectPath - - err := service.dbus.Call("org.freedesktop.Secret.Service.OpenSession", 0, "plain", dbus.MakeVariant("")).Store(&output, &path) - if err != nil { - return &Session{}, err - } - - return NewSession(service.conn, path), nil -} - - -// READ Array Collections; -func (service *Service) Collections() ([]Collection, error) { - val, err := service.dbus.GetProperty("org.freedesktop.Secret.Service.Collections") - if err != nil { - return []Collection{}, err - } - - collections := []Collection{} - for _, path := range val.Value().([]dbus.ObjectPath) { - collections = append(collections, *NewCollection(service.conn, path)) - } - - return collections, nil -} - - -// CreateCollection (IN Dict properties, IN String alias, OUT ObjectPath collection, OUT ObjectPath prompt); -func (service *Service) CreateCollection(label string) (*Collection, error) { - properties := make(map[string]dbus.Variant) - properties["org.freedesktop.Secret.Collection.Label"] = dbus.MakeVariant(label) - - var path dbus.ObjectPath - var prompt dbus.ObjectPath - - err := service.dbus.Call("org.freedesktop.Secret.Service.CreateCollection", 0, properties, "").Store(&path, &prompt) - if err != nil { - return &Collection{}, err - } - - if isPrompt(prompt) { - prompt := NewPrompt(service.conn, prompt) - - result, err := prompt.Prompt() - if err != nil { - return &Collection{}, err - } - - path = result.Value().(dbus.ObjectPath) - } - - return NewCollection(service.conn, path), nil -} - - -// Unlock (IN Array objects, OUT Array unlocked, OUT ObjectPath prompt); -func (service *Service) Unlock(object DBusObject) error { - objects := []dbus.ObjectPath{object.Path()} - - var unlocked []dbus.ObjectPath - var prompt dbus.ObjectPath - - err := service.dbus.Call("org.freedesktop.Secret.Service.Unlock", 0, objects).Store(&unlocked, &prompt) - if err != nil { - return err - } - - if isPrompt(prompt) { - prompt := NewPrompt(service.conn, prompt) - if _, err := prompt.Prompt(); err != nil { - return err - } - } - - return nil -} - - -// Lock (IN Array objects, OUT Array locked, OUT ObjectPath Prompt); -func (service *Service) Lock(object DBusObject) error { - objects := []dbus.ObjectPath{object.Path()} - - var locked []dbus.ObjectPath - var prompt dbus.ObjectPath - - err := service.dbus.Call("org.freedesktop.Secret.Service.Lock", 0, objects).Store(&locked, &prompt) - if err != nil { - return err - } - - if isPrompt(prompt) { - prompt := NewPrompt(service.conn, prompt) - if _, err := prompt.Prompt(); err != nil { - return err - } - } - - return nil -} diff --git a/service_funcs.go b/service_funcs.go new file mode 100644 index 0000000..44097f9 --- /dev/null +++ b/service_funcs.go @@ -0,0 +1,147 @@ +package libsecret + +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(DBusServiceName, 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.go b/session.go deleted file mode 100644 index 8cef0ce..0000000 --- a/session.go +++ /dev/null @@ -1,22 +0,0 @@ -package libsecret - -import "github.com/godbus/dbus" - - -type Session struct { - conn *dbus.Conn - dbus dbus.BusObject -} - - -func NewSession(conn *dbus.Conn, path dbus.ObjectPath) *Session { - return &Session{ - conn: conn, - dbus: conn.Object(DBusServiceName, path), - } -} - - -func (session Session) Path() dbus.ObjectPath { - return session.dbus.Path() -} diff --git a/session_funcs.go b/session_funcs.go new file mode 100644 index 0000000..2fb98b5 --- /dev/null +++ b/session_funcs.go @@ -0,0 +1,25 @@ +package libsecret + +import ( + `github.com/godbus/dbus` +) + +// NewSession returns a pointer to a new Session based on a Dbus connection and a Dbus path. +func NewSession(conn *dbus.Conn, path dbus.ObjectPath) (session *Session) { + + session = &Session{ + Conn: conn, + Dbus: conn.Object(DBusServiceName, path), + } + + return +} + +// Path returns the path of the underlying Dbus connection. +func (s Session) Path() (path dbus.ObjectPath) { + + // Remove this method in V1. It's bloat since we now have an exported Dbus. + path = s.Dbus.Path() + + return +} diff --git a/types.go b/types.go new file mode 100644 index 0000000..c58ad68 --- /dev/null +++ b/types.go @@ -0,0 +1,88 @@ +package libsecret + +import ( + `github.com/godbus/dbus` +) + +// DBusObject is any type that has a Path method that returns a dbus.ObjectPath. +type DBusObject interface { + Path() dbus.ObjectPath +} + +/* + Collection is an accessor for libsecret collections, which contain multiple Secret Item items. + Reference: + https://developer-old.gnome.org/libsecret/0.18/SecretCollection.html + https://specifications.freedesktop.org/secret-service/latest/ch03.html +*/ +type Collection struct { + // Conn is an active connection to the Dbus. + Conn *dbus.Conn + // Dbus is the Dbus bus object. + Dbus dbus.BusObject +} + +/* + Item is an entry in a Collection that contains a Secret. + https://developer-old.gnome.org/libsecret/0.18/SecretItem.html + https://specifications.freedesktop.org/secret-service/latest/re03.html +*/ +type Item struct { + // Conn is an active connection to the Dbus. + Conn *dbus.Conn + // Dbus is the Dbus bus object. + Dbus dbus.BusObject +} + +/* + Prompt is an interface to handling unlocking prompts. + https://developer-old.gnome.org/libsecret/0.18/SecretPrompt.html + https://specifications.freedesktop.org/secret-service/latest/ch09.html +*/ +type Prompt struct { + // Conn is an active connection to the Dbus. + Conn *dbus.Conn + // Dbus is the Dbus bus object. + Dbus dbus.BusObject +} + +/* + Secret is the "Good Stuff" - the actual secret content. + https://developer-old.gnome.org/libsecret/0.18/SecretValue.html + https://specifications.freedesktop.org/secret-service/latest/re03.html + https://specifications.freedesktop.org/secret-service/latest/ch14.html#type-Secret +*/ +type Secret struct { + // Session is a Dbus object path for the associated Session. + Session dbus.ObjectPath + // Parameters are "algorithm dependent parameters for secret value encoding" - likely this will just be an empty byteslice. + Parameters []byte + // Value is the secret's content in []byte format. + Value []byte + // ContentType is the MIME type of Value. + ContentType string +} + +/* + Service is a general SecretService interface, sort of handler for Dbus - it's used for fetching a Session, Collections, etc. + https://developer-old.gnome.org/libsecret/0.18/SecretService.html + https://specifications.freedesktop.org/secret-service/latest/re01.html +*/ +type Service struct { + // Conn is an active connection to the Dbus. + Conn *dbus.Conn + // Dbus is the Dbus bus object. + Dbus dbus.BusObject +} + +/* + Session is a session/instance/connection to SecretService. + https://developer-old.gnome.org/libsecret/0.18/SecretService.html + https://specifications.freedesktop.org/secret-service/latest/ch06.html +*/ +type Session struct { + // Conn is an active connection to the Dbus. + Conn *dbus.Conn + // Dbus is the Dbus bus object. + Dbus dbus.BusObject +}