diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 063c298..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/.ref b/.ref
index ab4db83..c39a67a 100644
--- a/.ref
+++ b/.ref
@@ -1 +1,9 @@
https://specifications.freedesktop.org/secret-service/latest/index.html
+https://developer-old.gnome.org/libsecret/unstable/
+https://developer-old.gnome.org/libsecret/0.18/
+https://people.gnome.org/~stefw/libsecret-docs/index.html
+
+
+Quick reference URLs:
+
+Dbus paths: https://specifications.freedesktop.org/secret-service/latest/ch12.html
diff --git a/consts.go b/consts.go
index b350926..90c189c 100644
--- a/consts.go
+++ b/consts.go
@@ -18,16 +18,16 @@ const (
// Methods
- // DbusServiceChangeLock is [FUNCTION UNKNOWN; TODO.]
- DbusServiceChangeLock string = DbusInterfaceService + ".ChangeLock"
+ /*
+ DbusServiceChangeLock has some references in the SecretService Dbus API but
+ it seems to be obsolete - undocumented, at the least.
+ */
+ // DbusServiceChangeLock string = DbusInterfaceService + ".ChangeLock"
- // DbusServiceCreateCollection is used to create a new Collection if it doesn't exist in Dbus.
+ // DbusServiceCreateCollection is used to create a new Collection via Service.CreateCollection.
DbusServiceCreateCollection string = DbusInterfaceService + ".CreateCollection"
- /*
- DbusServiceGetSecrets is used to fetch all Secret / Item items in a given Collection
- (via Service.GetSecrets).
- */
+ // DbusServiceGetSecrets is used to fetch all Secret / Item items in a given Collection (via Service.GetSecrets).
DbusServiceGetSecrets string = DbusInterfaceService + ".GetSecrets"
// DbusServiceLock is used by Service.Lock.
@@ -63,7 +63,7 @@ const (
DbusInterfaceSession is the Dbus interface for working with a Session.
Found at /org/freedesktop/secrets/session//(DbusInterfaceSession)
*/
- DbusInterfaceSession = DbusServiceBase + ".Session"
+ DbusInterfaceSession = DbusServiceBase + ".Session"
// Methods
@@ -128,6 +128,26 @@ const (
// DbusItemSetSecret is used by Item.SetSecret.
DbusItemSetSecret string = DbusInterfaceItem + ".SetSecret"
+
+ // Properties
+
+ // DbusItemLocked is a Dbus boolean for Item.Locked.
+ DbusItemLocked string = DbusInterfaceItem + ".Locked"
+
+ // DbusItemAttributes contains attributes (metadata, schema, etc.) for Item.Attributes.
+ DbusItemAttributes string = DbusInterfaceItem + ".Attributes"
+
+ // DbusItemLabel is the name (label) for Item.Label.
+ DbusItemLabel string = DbusInterfaceItem + ".Label"
+
+ // DbusItemType is the type of an Item (Item.Type).
+ DbusItemType string = DbusInterfaceItem + ".Type"
+
+ // DbusItemCreated is the time an Item was created (in a UNIX Epoch uint64) for Item.Created.
+ DbusItemCreated string = DbusInterfaceItem + ".Created"
+
+ // DbusItemModified is the time an Item was last modified (in a UNIX Epoch uint64) for Item.Modified.
+ DbusItemModified string = DbusInterfaceItem + ".Modified"
)
// Dbus paths.
@@ -137,3 +157,73 @@ const (
// DbusPromptPrefix is the path used for prompts comparison.
DbusPromptPrefix string = DbusPath + "/prompt/"
)
+
+// FLAGS
+// These are not currently used, but may be in the future.
+
+// SERVICE
+
+// ServiceInitFlag is a flag for Service.Open.
+type ServiceInitFlag int
+
+const (
+ FlagServiceNone ServiceInitFlag = iota
+ FlagServiceOpenSession
+ FlagServiceLoadCollections
+)
+
+// ServiceSearchFlag is a flag for Service.SearchItems.
+type ServiceSearchFlag int
+
+const (
+ FlagServiceSearchNone ServiceSearchFlag = iota
+ FlagServiceSearchAll
+ FlagServiceSearchUnlock
+ FlagServiceSearchLoadSecrets
+)
+
+// COLLECTION
+
+// CollectionInitFlag is a flag for Collection.SearchItems and Collection.Items.
+type CollectionInitFlag int
+
+const (
+ FlagCollectionNone CollectionInitFlag = iota
+ FlagCollectionLoadItems
+)
+
+// ITEM
+
+// ItemInitFlag are flags for Collection.SearchItems and Collection.Items.
+type ItemInitFlag int
+
+const (
+ FlagItemNone ItemInitFlag = iota
+ FlagItemLoadSecret
+)
+
+// ItemSearchFlag are flags for Collection.CreateItem.
+type ItemSearchFlag int
+
+const (
+ FlagItemCreateNone ItemSearchFlag = iota
+ FlatItemCreateReplace
+)
+
+// ERRORS
+
+/*
+ SecretServiceErrEnum are just constants for the enum'd errors;
+ see SecretServiceError type and ErrSecretService* vars for what
+ actually gets returned.
+ They're used for finding the appropriate matching error.
+*/
+type SecretServiceErrEnum int
+
+const (
+ EnumErrProtocol SecretServiceErrEnum = iota
+ EnumErrIsLocked
+ EnumErrNoSuchObject
+ EnumErrAlreadyExists
+ EnumErrInvalidFileFormat
+)
diff --git a/errs.go b/errs.go
index 6753ca9..e685895 100644
--- a/errs.go
+++ b/errs.go
@@ -1,15 +1,67 @@
package gosecret
import (
- `errors`
+ "errors"
)
-// Errors.
+// General errors.
var (
// ErrBadDbusPath indicates an invalid path - either nothing exists at that path or the path is malformed.
ErrBadDbusPath error = errors.New("invalid dbus path")
// ErrInvalidProperty indicates a dbus.Variant is not the "real" type expected.
ErrInvalidProperty error = errors.New("invalid variant type; cannot convert")
// ErrNoDbusConn gets triggered if a connection to Dbus can't be detected.
- ErrNoDbusConn error = errors.New("no valid dbus connection")
+ ErrNoDbusConn error = errors.New("no valid dbus connection")
+)
+
+/*
+ Translated SecretService errors.
+ See https://developer-old.gnome.org/libsecret/unstable/libsecret-SecretError.html#SecretError.
+ Used by TranslateError.
+*/
+var (
+ ErrUnknownSecretServiceErr error = errors.New("cannot find matching SecretService error")
+ ErrSecretServiceProto SecretServiceError = SecretServiceError{
+ ErrCode: EnumErrProtocol,
+ ErrName: "SECRET_ERROR_PROTOCOL",
+ ErrDesc: "an invalid message or data was received from SecretService",
+ }
+ ErrSecretServiceLocked SecretServiceError = SecretServiceError{
+ ErrCode: EnumErrIsLocked,
+ ErrName: "SECRET_ERROR_IS_LOCKED",
+ ErrDesc: "the item/collection is locked; the specified operation cannot be performed",
+ }
+ ErrSecretServiceNoObj SecretServiceError = SecretServiceError{
+ ErrCode: EnumErrNoSuchObject,
+ ErrName: "SECRET_ERROR_NO_SUCH_OBJECT",
+ ErrDesc: "no such item/collection was found in SecretService",
+ }
+ ErrSecretServiceExists SecretServiceError = SecretServiceError{
+ ErrCode: EnumErrAlreadyExists,
+ ErrName: "SECRET_ERROR_ALREADY_EXISTS",
+ ErrDesc: "a relevant item/collection already exists",
+ }
+ ErrSecretServiceInvalidFormat SecretServiceError = SecretServiceError{
+ ErrCode: EnumErrInvalidFileFormat,
+ ErrName: "SECRET_ERROR_INVALID_FILE_FORMAT",
+ ErrDesc: "the file/content format is invalid",
+ }
+ /*
+ AllSecretServiceErrs provides a slice of these for easier iteration when translating.
+ TECHNICALLY, because they are indexed in the order of their enums, you could
+ simplify and optimize translation by just doing e.g.
+
+ err = AllSecretServiceErrs[EnumErrProtocol]
+
+ But this should be considered UNSTABLE and UNSAFE due to it being potentially unpredictable in the future.
+ There are only 5 errors currently, so the performance benefits would be negligible compared to iteration.
+ If SecretService adds more errors, however, this may be more desirable.
+ */
+ AllSecretServiceErrs []SecretServiceError = []SecretServiceError{
+ ErrSecretServiceProto,
+ ErrSecretServiceLocked,
+ ErrSecretServiceNoObj,
+ ErrSecretServiceExists,
+ ErrSecretServiceInvalidFormat,
+ }
)
diff --git a/service_funcs.go b/service_funcs.go
index 1c39cbc..af5e2c0 100644
--- a/service_funcs.go
+++ b/service_funcs.go
@@ -1,42 +1,39 @@
package gosecret
import (
- `github.com/godbus/dbus`
+ "github.com/godbus/dbus"
)
-// NewService returns a pointer to a new Service.
+// NewService returns a pointer to a new Service connection.
func NewService() (service *Service, err error) {
- service = &Service{
- Conn: nil,
- Dbus: nil,
- }
+ var svc Service = Service{}
- if service.Conn, err = dbus.SessionBus(); err != nil {
+ if svc.Conn, err = dbus.SessionBus(); err != nil {
return
}
- service.Dbus = service.Conn.Object(DbusService, dbus.ObjectPath(DbusPath))
+ svc.Dbus = service.Conn.Object(DbusService, dbus.ObjectPath(DbusPath))
- return
-}
+ if svc.Session, err = svc.Open(); err != nil {
+ 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()
+ service = &svc
return
}
// Open returns a pointer to a Session from the Service.
-func (s *Service) Open() (session *Session, err error) {
+func (s *Service) Open() (session *Session, output dbus.Variant, err error) {
- var output dbus.Variant
var path dbus.ObjectPath
+ // In *theory*, SecretService supports multiple "algorithms" for encryption in-transit, but I don't think it's implemented (yet)?
+ // TODO: confirm this.
+ // Possible flags are dbus.Flags consts: https://pkg.go.dev/github.com/godbus/dbus#Flags
+ // Oddly, there is no "None" flag. So it's explicitly specified as a null byte.
if err = s.Dbus.Call(
- "org.freedesktop.Secret.Service.OpenSession", 0, "plain", dbus.MakeVariant(""),
+ DbusServiceOpenSession, 0x0, "plain", dbus.MakeVariant(""),
).Store(&output, &path); err != nil {
return
}
@@ -46,7 +43,7 @@ func (s *Service) Open() (session *Session, err error) {
return
}
-// Collections returns a slice of Collection keyrings accessible to this Service.
+// Collections returns a slice of Collection items accessible to this Service.
func (s *Service) Collections() (collections []Collection, err error) {
var paths []dbus.ObjectPath
@@ -67,7 +64,7 @@ func (s *Service) Collections() (collections []Collection, err error) {
return
}
-// CreateCollection creates a new Collection (keyring) via a Service with the name specified by label.
+// CreateCollection creates a new Collection (keyring) via a Service with the name specified by label and returns the new Collection.
func (s *Service) CreateCollection(label string) (collection *Collection, err error) {
var variant *dbus.Variant
diff --git a/service_funcs.go.old b/service_funcs.go.old
new file mode 100644
index 0000000..c8b25b0
--- /dev/null
+++ b/service_funcs.go.old
@@ -0,0 +1,147 @@
+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/sserror_funcs.go b/sserror_funcs.go
new file mode 100644
index 0000000..ab0d80a
--- /dev/null
+++ b/sserror_funcs.go
@@ -0,0 +1,28 @@
+package gosecret
+
+/*
+ TranslateError translates a SecretServiceErrEnum into a SecretServiceError.
+ If a matching error was found, ok will be true and err will be the matching SecretServiceError.
+ If no matching error was found, however, then ok will be false and err will be ErrUnknownSecretServiceErr.
+*/
+func TranslateError(ssErr SecretServiceErrEnum) (ok bool, err error) {
+
+ err = ErrUnknownSecretServiceErr
+
+ for _, e := range AllSecretServiceErrs {
+ if e.ErrCode == ssErr {
+ ok = true
+ err = e
+ return
+ }
+ }
+
+ return
+}
+
+func (e SecretServiceError) Error() (errStr string) {
+
+ errStr = e.ErrDesc
+
+ return
+}
diff --git a/types.go b/types.go
index d1dc6a7..c8a14a1 100644
--- a/types.go
+++ b/types.go
@@ -1,9 +1,9 @@
package gosecret
import (
- `time`
+ "time"
- `github.com/godbus/dbus`
+ "github.com/godbus/dbus"
)
/*
@@ -11,22 +11,70 @@ import (
*/
type MultiError struct {
// Errors is a slice of errors to combine/concatenate when .Error() is called.
- Errors []error
+ Errors []error `json:"errors"`
// ErrorSep is a string to use to separate errors for .Error(). The default is "\n".
- ErrorSep string
+ ErrorSep string `json:"separator"`
+}
+
+/*
+ SecretServiceError is a translated error from SecretService API.
+ See https://developer-old.gnome.org/libsecret/unstable/libsecret-SecretError.html#SecretError and
+ ErrSecretService* errors.
+*/
+type SecretServiceError struct {
+ // ErrCode is the SecretService API's enum value.
+ ErrCode SecretServiceErrEnum `json:"code"`
+ // ErrName is the SecretService API's error name.
+ ErrName string `json:"name"`
+ /*
+ ErrDesc is the actual error description/text.
+ This is what should be displayed to users, and is returned by SecretServiceError.Error.
+ */
+ ErrDesc string `json:"desc"`
}
// ConnPathCheckResult contains the result of validConnPath.
type ConnPathCheckResult struct {
// ConnOK is true if the dbus.Conn is valid.
- ConnOK bool
+ ConnOK bool `json:"conn"`
// PathOK is true if the Dbus path given is a valid type and value.
- PathOK bool
+ PathOK bool `json:"path"`
}
-// DBusObject is any type that has a Path method that returns a dbus.ObjectPath.
-type DBusObject interface {
- Path() dbus.ObjectPath
+// DbusObject is a base struct type to be anonymized by other types.
+type DbusObject struct {
+ // Conn is an active connection to the Dbus.
+ Conn *dbus.Conn `json:"-"`
+ // Dbus is the Dbus bus object.
+ Dbus dbus.BusObject `json:"-"`
+}
+
+/*
+ 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 {
+ *DbusObject
+}
+
+/*
+ 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 {
+ *DbusObject
+ Session *Session `json:"-"`
+}
+
+/*
+ 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 {
+ *DbusObject
}
/*
@@ -36,10 +84,7 @@ type DBusObject interface {
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
+ *DbusObject
// lastModified is unexported because it's important that API users don't change it; it's used by Collection.Modified.
lastModified time.Time
}
@@ -50,22 +95,7 @@ type Collection struct {
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
+ *DbusObject
}
/*
@@ -76,35 +106,11 @@ type Prompt struct {
*/
type Secret struct {
// Session is a Dbus object path for the associated Session.
- Session dbus.ObjectPath
+ Session dbus.ObjectPath `json:"-"`
// Parameters are "algorithm dependent parameters for secret value encoding" - likely this will just be an empty byteslice.
- Parameters []byte
+ Parameters []byte `json:"params"`
// Value is the secret's content in []byte format.
- Value []byte
+ Value []byte `json:"value"`
// 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
+ ContentType string `json:"content_type"`
}