checking in - all basic funcs in place; add a few more then v1 merge
This commit is contained in:
parent
1d093627f6
commit
0fc0e0c269
55
README.adoc
55
README.adoc
@ -59,16 +59,17 @@ To reflect the absolute breaking changes, the module name changes as well from `
|
|||||||
|
|
||||||
=== Status
|
=== Status
|
||||||
|
|
||||||
The new API is underway, and all functionality in V0 is present. However, It's not "complete". https://github.com/johnnybubonic/gosecret/pulls[PRs^] welcome, of course, but this will be an ongoing effort for a bit of time.
|
The new API is underway, and all functionality in V0 is present. However, it's not "complete". https://github.com/johnnybubonic/gosecret/pulls[PRs^] welcome, of course, but this will be an ongoing effort for a bit of time.
|
||||||
|
|
||||||
== SecretService Concepts
|
== SecretService Concepts
|
||||||
|
|
||||||
For reference:
|
For reference:
|
||||||
|
|
||||||
* A *`Service`* allows one to operate on/with *`Session`* objects.
|
* A `*Service*` allows one to retrieve and operate on/with `*Session*` and `*Collection*` objects.
|
||||||
* A *`Session`* allows one to operate on/with `*Collection*` objects.
|
* A `*Session*` allows one to operate on/with `*Item*` objects (e.g. parsing/decoding/decrypting them).
|
||||||
* A `*Collection*` allows one to operate on/with `*Item*` objects.
|
* A `*Collection*` allows one to retrieve and operate on/with `*Item*` objects.
|
||||||
* An `*Item*` allows one to operate on/with `*Secrets*`.
|
* An `*Item*` allows one to retrieve and operate on/with `*Secret*` objects.
|
||||||
|
|
||||||
(`*Secrets*` are considered "terminating objects" in this model, and contain
|
(`*Secrets*` are considered "terminating objects" in this model, and contain
|
||||||
actual secret value(s) and metadata).
|
actual secret value(s) and metadata).
|
||||||
|
|
||||||
@ -79,35 +80,21 @@ So the object hierarchy in *theory* looks kind of like this:
|
|||||||
----
|
----
|
||||||
Service
|
Service
|
||||||
├─ Session "A"
|
├─ Session "A"
|
||||||
│ ├─ Collection "A.1"
|
├─ Session "B"
|
||||||
│ │ ├─ Item "A.1.a"
|
├─ Collection "A"
|
||||||
│ │ │ ├─ Secret "A_1_a_I"
|
│ ├─ Item "A.1"
|
||||||
│ │ │ └─ Secret "A_1_a_II"
|
│ │ ├─ Secret "A_1_a"
|
||||||
│ │ └─ Item "A.1.b"
|
│ │ └─ Secret "A_1_b"
|
||||||
│ │ ├─ Secret "A_1_b_I"
|
│ └─ Item "A.2"
|
||||||
│ │ └─ Secret "A_1_b_II"
|
│ ├─ Secret "A_2_a"
|
||||||
│ └─ Collection "A.2"
|
│ └─ Secret "A_2_b"
|
||||||
│ ├─ Item "A.2.a"
|
└─ Collection "B"
|
||||||
│ │ ├─ Secret "A_2_a_I"
|
├─ Item "B.1"
|
||||||
│ │ └─ Secret "A_2_a_II"
|
│ ├─ Secret "B_1_a"
|
||||||
│ └─ Item "A.2.b"
|
│ └─ Secret "B_1_b"
|
||||||
│ ├─ Secret "A_2_b_I"
|
└─ Item "B.2"
|
||||||
│ └─ Secret "A_2_b_II"
|
├─ Secret "B_2_a"
|
||||||
└─ Session "B"
|
└─ Secret "B_2_b"
|
||||||
├─ Collection "B.1"
|
|
||||||
│ ├─ Item "B.1.a"
|
|
||||||
│ │ ├─ Secret "B_1_a_I"
|
|
||||||
│ │ └─ Secret "B_1_a_II"
|
|
||||||
│ └─ Item "B.1.b"
|
|
||||||
│ ├─ Secret "B_1_b_I"
|
|
||||||
│ └─ Secret "B_1_b_II"
|
|
||||||
└─ Collection "B.2"#
|
|
||||||
├─ Item "B.2.a"
|
|
||||||
│ ├─ Secret "B_2_a_I"
|
|
||||||
│ └─ Secret "B_2_a_II"
|
|
||||||
└─ Item "B.2.b"
|
|
||||||
├─ Secret "B_2_b_I"
|
|
||||||
└─ Secret "B_2_b_II"
|
|
||||||
----
|
----
|
||||||
|
|
||||||
And so on.
|
And so on.
|
||||||
|
10
TODO
Normal file
10
TODO
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
- TEST CASES
|
||||||
|
-- https://pkg.go.dev/testing
|
||||||
|
-- https://go.dev/doc/tutorial/add-a-test
|
||||||
|
-- https://gobyexample.com/testing
|
||||||
|
-- https://blog.alexellis.io/golang-writing-unit-tests/
|
||||||
|
- Example usage
|
||||||
|
- Merge master into V1
|
||||||
|
-- and tag release (v1.0.0)
|
||||||
|
- Merge doc.go and README.adoc to V0
|
||||||
|
-- and tag release (v0.1.3)
|
@ -4,9 +4,11 @@ import (
|
|||||||
`strings`
|
`strings`
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
`github.com/godbus/dbus`
|
`github.com/godbus/dbus/v5`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO: add method Relabel
|
||||||
|
|
||||||
/*
|
/*
|
||||||
NewCollection returns a pointer to a Collection based on a Service and a Dbus path.
|
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.
|
You will almost always want to use Service.GetCollection instead.
|
||||||
@ -28,10 +30,11 @@ func NewCollection(service *Service, path dbus.ObjectPath) (coll *Collection, er
|
|||||||
Conn: service.Conn,
|
Conn: service.Conn,
|
||||||
Dbus: service.Conn.Object(DbusService, path),
|
Dbus: service.Conn.Object(DbusService, path),
|
||||||
},
|
},
|
||||||
|
service: service,
|
||||||
// lastModified: time.Now(),
|
// lastModified: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
splitPath = strings.Split(string(coll.Dbus.Path()), "")
|
splitPath = strings.Split(string(coll.Dbus.Path()), "/")
|
||||||
|
|
||||||
coll.name = splitPath[len(splitPath)-1]
|
coll.name = splitPath[len(splitPath)-1]
|
||||||
|
|
||||||
@ -58,7 +61,6 @@ func (c *Collection) Items() (items []*Item, err error) {
|
|||||||
|
|
||||||
for idx, path := range paths {
|
for idx, path := range paths {
|
||||||
if item, err = NewItem(c, path); err != nil {
|
if item, err = NewItem(c, path); err != nil {
|
||||||
// return
|
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
err = nil
|
err = nil
|
||||||
continue
|
continue
|
||||||
@ -120,22 +122,20 @@ func (c *Collection) SearchItems(profile string) (items []*Item, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
// CreateItem returns a pointer to an Item based on a label, some attributes, 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, attrs map[string]string, secret *Secret, replace bool) (item *Item, err error) {
|
||||||
|
|
||||||
var prompt *Prompt
|
var prompt *Prompt
|
||||||
var path dbus.ObjectPath
|
var path dbus.ObjectPath
|
||||||
var promptPath dbus.ObjectPath
|
var promptPath dbus.ObjectPath
|
||||||
var variant *dbus.Variant
|
var variant *dbus.Variant
|
||||||
var props map[string]dbus.Variant = make(map[string]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[DbusItemLabel] = dbus.MakeVariant(label)
|
||||||
props["org.freedesktop.Secret.Item.Label"] = dbus.MakeVariant(label)
|
props[DbusItemAttributes] = dbus.MakeVariant(attrs)
|
||||||
props["org.freedesktop.Secret.Item.Attributes"] = dbus.MakeVariant(attrs)
|
|
||||||
|
|
||||||
if err = c.Dbus.Call(
|
if err = c.Dbus.Call(
|
||||||
"org.freedesktop.Secret.Collection.CreateItem", 0, props, secret, replace,
|
DbusCollectionCreateItem, 0, props, secret, replace,
|
||||||
).Store(&path, &promptPath); err != nil {
|
).Store(&path, &promptPath); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -181,6 +181,10 @@ func (c *Collection) Label() (label string, err error) {
|
|||||||
|
|
||||||
label = variant.Value().(string)
|
label = variant.Value().(string)
|
||||||
|
|
||||||
|
if label != c.name {
|
||||||
|
c.name = label
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
24
consts.go
24
consts.go
@ -1,11 +1,23 @@
|
|||||||
package gosecret
|
package gosecret
|
||||||
|
|
||||||
|
// Constants for use with gosecret.
|
||||||
|
const (
|
||||||
|
/*
|
||||||
|
ExplicitAttrEmptyValue is the constant used in Item.ModifyAttributes to explicitly set a value as empty.
|
||||||
|
Between the surrounding with %'s, the weird name that includes "gosecret", and the UUID4...
|
||||||
|
I am fairly confident this is unique enough.
|
||||||
|
*/
|
||||||
|
ExplicitAttrEmptyValue string = "%EXPLICIT_GOSECRET_BLANK_VALUE_8A4E3D7D-F30E-4754-8C56-9C172D1400F6%"
|
||||||
|
)
|
||||||
|
|
||||||
// Libsecret/SecretService Dbus interfaces.
|
// Libsecret/SecretService Dbus interfaces.
|
||||||
const (
|
const (
|
||||||
// DbusService is the Dbus service bus identifier.
|
// DbusService is the Dbus service bus identifier.
|
||||||
DbusService string = "org.freedesktop.secrets"
|
DbusService string = "org.freedesktop.secrets"
|
||||||
// DbusServiceBase is the base identifier used by interfaces.
|
// DbusServiceBase is the base identifier used by interfaces.
|
||||||
DbusServiceBase string = "org.freedesktop.Secret"
|
DbusServiceBase string = "org.freedesktop.Secret"
|
||||||
|
// DbusPrompterInterface is an interface for issuing a Prompt. Yes, it should be doubled up like that.
|
||||||
|
DbusPrompterInterface string = DbusServiceBase + ".Prompt.Prompt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Service interface.
|
// Service interface.
|
||||||
@ -37,10 +49,10 @@ const (
|
|||||||
// DbusServiceLockService is [FUNCTION UNKNOWN/UNDOCUMENTED; TODO? NOT IMPLEMENTED.]
|
// DbusServiceLockService is [FUNCTION UNKNOWN/UNDOCUMENTED; TODO? NOT IMPLEMENTED.]
|
||||||
DbusServiceLockService string = DbusInterfaceService + ".LockService"
|
DbusServiceLockService string = DbusInterfaceService + ".LockService"
|
||||||
|
|
||||||
// DbusServiceOpenSession is used by Service.Open.
|
// DbusServiceOpenSession is used by Service.OpenSession.
|
||||||
DbusServiceOpenSession string = DbusInterfaceService + ".OpenSession"
|
DbusServiceOpenSession string = DbusInterfaceService + ".OpenSession"
|
||||||
|
|
||||||
// DbusServiceReadAlias is used by Service.GetAlias to return a Collection based on its aliased name.
|
// DbusServiceReadAlias is used by Service.ReadAlias to return a Collection based on its aliased name.
|
||||||
DbusServiceReadAlias string = DbusInterfaceService + ".ReadAlias"
|
DbusServiceReadAlias string = DbusInterfaceService + ".ReadAlias"
|
||||||
|
|
||||||
// DbusServiceSearchItems is used by Service.SearchItems to get arrays of locked and unlocked Item objects.
|
// DbusServiceSearchItems is used by Service.SearchItems to get arrays of locked and unlocked Item objects.
|
||||||
@ -135,13 +147,13 @@ const (
|
|||||||
// DbusItemLocked is a Dbus boolean for Item.Locked.
|
// DbusItemLocked is a Dbus boolean for Item.Locked.
|
||||||
DbusItemLocked string = DbusInterfaceItem + ".Locked"
|
DbusItemLocked string = DbusInterfaceItem + ".Locked"
|
||||||
|
|
||||||
// DbusItemAttributes contains attributes (metadata, schema, etc.) for Item.Attributes.
|
// DbusItemAttributes contains attributes (metadata, schema, etc.) for Item.Attrs.
|
||||||
DbusItemAttributes string = DbusInterfaceItem + ".Attributes"
|
DbusItemAttributes string = DbusInterfaceItem + ".Attributes"
|
||||||
|
|
||||||
// DbusItemLabel is the name (label) for Item.Label.
|
// DbusItemLabel is the name (label) for Item.Label.
|
||||||
DbusItemLabel string = DbusInterfaceItem + ".Label"
|
DbusItemLabel string = DbusInterfaceItem + ".Label"
|
||||||
|
|
||||||
// DbusItemType is the type of an Item (Item.Type).
|
// DbusItemType is the type of Item (Item.ItemType).
|
||||||
DbusItemType string = DbusInterfaceItem + ".Type"
|
DbusItemType string = DbusInterfaceItem + ".Type"
|
||||||
|
|
||||||
// DbusItemCreated is the time an Item was created (in a UNIX Epoch uint64) for Item.Created.
|
// DbusItemCreated is the time an Item was created (in a UNIX Epoch uint64) for Item.Created.
|
||||||
@ -159,6 +171,8 @@ const (
|
|||||||
DbusPromptPrefix string = DbusPath + "/prompt/"
|
DbusPromptPrefix string = DbusPath + "/prompt/"
|
||||||
// DbusNewCollectionPath is used to create a new Collection.
|
// DbusNewCollectionPath is used to create a new Collection.
|
||||||
DbusNewCollectionPath string = DbusPath + "/collection/"
|
DbusNewCollectionPath string = DbusPath + "/collection/"
|
||||||
|
// DbusNewSessionPath is used to create a new Session.
|
||||||
|
DbusNewSessionPath string = DbusPath + "/session/"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FLAGS
|
// FLAGS
|
||||||
@ -166,7 +180,7 @@ const (
|
|||||||
|
|
||||||
// SERVICE
|
// SERVICE
|
||||||
|
|
||||||
// ServiceInitFlag is a flag for Service.Open.
|
// ServiceInitFlag is a flag for Service.OpenSession.
|
||||||
type ServiceInitFlag int
|
type ServiceInitFlag int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
54
doc.go
54
doc.go
@ -43,13 +43,13 @@ SecretService Concepts
|
|||||||
|
|
||||||
For reference:
|
For reference:
|
||||||
|
|
||||||
- A Service allows one to operate on/with Session objects.
|
- A Service allows one to retrieve and operate on/with Session and Collection objects.
|
||||||
|
|
||||||
- A Session allows one to operate on/with Collection objects.
|
- A Session allows one to operate on/with Item objects (e.g. parsing/decoding/decrypting them).
|
||||||
|
|
||||||
- A Collection allows one to operate on/with Item objects.
|
- A Collection allows one to retrieve and operate on/with Item objects.
|
||||||
|
|
||||||
- An Item allows one to operate on/with Secrets.
|
- An Item allows one to retrieve and operate on/with Secret objects.
|
||||||
|
|
||||||
(Secrets are considered "terminating objects" in this model, and contain actual secret value(s) and metadata).
|
(Secrets are considered "terminating objects" in this model, and contain actual secret value(s) and metadata).
|
||||||
|
|
||||||
@ -59,38 +59,24 @@ So the object hierarchy in THEORY looks kind of like this:
|
|||||||
|
|
||||||
Service
|
Service
|
||||||
├─ Session "A"
|
├─ Session "A"
|
||||||
│ ├─ Collection "A.1"
|
├─ Session "B"
|
||||||
│ │ ├─ Item "A.1.a"
|
├─ Collection "A"
|
||||||
│ │ │ ├─ Secret "A_1_a_I"
|
│ ├─ Item "A.1"
|
||||||
│ │ │ └─ Secret "A_1_a_II"
|
│ │ ├─ Secret "A_1_a"
|
||||||
│ │ └─ Item "A.1.b"
|
│ │ └─ Secret "A_1_b"
|
||||||
│ │ ├─ Secret "A_1_b_I"
|
│ └─ Item "A.2"
|
||||||
│ │ └─ Secret "A_1_b_II"
|
│ ├─ Secret "A_2_a"
|
||||||
│ └─ Collection "A.2"
|
│ └─ Secret "A_2_b"
|
||||||
│ ├─ Item "A.2.a"
|
└─ Collection "B"
|
||||||
│ │ ├─ Secret "A_2_a_I"
|
├─ Item "B.1"
|
||||||
│ │ └─ Secret "A_2_a_II"
|
│ ├─ Secret "B_1_a"
|
||||||
│ └─ Item "A.2.b"
|
│ └─ Secret "B_1_b"
|
||||||
│ ├─ Secret "A_2_b_I"
|
└─ Item "B.2"
|
||||||
│ └─ Secret "A_2_b_II"
|
├─ Secret "B_2_a"
|
||||||
└─ Session "B"
|
└─ Secret "B_2_b"
|
||||||
├─ Collection "B.1"
|
|
||||||
│ ├─ Item "B.1.a"
|
|
||||||
│ │ ├─ Secret "B_1_a_I"
|
|
||||||
│ │ └─ Secret "B_1_a_II"
|
|
||||||
│ └─ Item "B.1.b"
|
|
||||||
│ ├─ Secret "B_1_b_I"
|
|
||||||
│ └─ Secret "B_1_b_II"
|
|
||||||
└─ Collection "B.2"#
|
|
||||||
├─ Item "B.2.a"
|
|
||||||
│ ├─ Secret "B_2_a_I"
|
|
||||||
│ └─ Secret "B_2_a_II"
|
|
||||||
└─ Item "B.2.b"
|
|
||||||
├─ Secret "B_2_b_I"
|
|
||||||
└─ Secret "B_2_b_II"
|
|
||||||
|
|
||||||
And so on.
|
And so on.
|
||||||
In PRACTICE, however, most users will only have two Session types
|
In PRACTICE, however, most users will only have two Session items
|
||||||
(a default "system" one and a temporary one that may or may not exist, running in memory for the current login session)
|
(a default "system" one and a temporary one that may or may not exist, running in memory for the current login session)
|
||||||
and a single Collection, named "login" (and aliased to "default", usually).
|
and a single Collection, named "login" (and aliased to "default", usually).
|
||||||
|
|
||||||
|
2
funcs.go
2
funcs.go
@ -3,7 +3,7 @@ package gosecret
|
|||||||
import (
|
import (
|
||||||
`strings`
|
`strings`
|
||||||
|
|
||||||
`github.com/godbus/dbus`
|
`github.com/godbus/dbus/v5`
|
||||||
)
|
)
|
||||||
|
|
||||||
// isPrompt returns a boolean that is true if path is/requires a prompt(ed path) and false if it is/does not.
|
// isPrompt returns a boolean that is true if path is/requires a prompt(ed path) and false if it is/does not.
|
||||||
|
2
go.mod
2
go.mod
@ -2,4 +2,4 @@ module r00t2.io/gosecret
|
|||||||
|
|
||||||
go 1.17
|
go 1.17
|
||||||
|
|
||||||
require github.com/godbus/dbus v4.1.0+incompatible
|
require github.com/godbus/dbus/v5 v5.0.6
|
||||||
|
4
go.sum
4
go.sum
@ -1,2 +1,2 @@
|
|||||||
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
|
github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro=
|
||||||
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
|
234
item_funcs.go
234
item_funcs.go
@ -1,12 +1,20 @@
|
|||||||
package gosecret
|
package gosecret
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`github.com/godbus/dbus`
|
`strconv`
|
||||||
|
`strings`
|
||||||
|
`time`
|
||||||
|
|
||||||
|
`github.com/godbus/dbus/v5`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO: add method Relabel
|
||||||
|
|
||||||
// NewItem returns a pointer to an Item based on Collection and a Dbus path.
|
// 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) {
|
func NewItem(collection *Collection, path dbus.ObjectPath) (item *Item, err error) {
|
||||||
|
|
||||||
|
var splitPath []string
|
||||||
|
|
||||||
if collection == nil {
|
if collection == nil {
|
||||||
err = ErrNoDbusConn
|
err = ErrNoDbusConn
|
||||||
}
|
}
|
||||||
@ -16,12 +24,84 @@ func NewItem(collection *Collection, path dbus.ObjectPath) (item *Item, err erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
item = &Item{
|
item = &Item{
|
||||||
&DbusObject{
|
DbusObject: &DbusObject{
|
||||||
Conn: collection.Conn,
|
Conn: collection.Conn,
|
||||||
Dbus: collection.Conn.Object(DbusService, path),
|
Dbus: collection.Conn.Object(DbusService, path),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
splitPath = strings.Split(string(item.Dbus.Path()), "/")
|
||||||
|
|
||||||
|
item.idx, err = strconv.Atoi(splitPath[len(splitPath)-1])
|
||||||
|
item.collection = collection
|
||||||
|
if _, err = item.Attributes(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = item.Type(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = item.Modified()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attributes updates the Item.Attrs from Dbus (and returns them).
|
||||||
|
func (i *Item) Attributes() (attrs map[string]string, err error) {
|
||||||
|
|
||||||
|
var variant dbus.Variant
|
||||||
|
|
||||||
|
if variant, err = i.Dbus.GetProperty(DbusItemAttributes); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
i.Attrs = variant.Value().(map[string]string)
|
||||||
|
attrs = i.Attrs
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes an Item from a Collection.
|
||||||
|
func (i *Item) Delete() (err error) {
|
||||||
|
|
||||||
|
var promptPath dbus.ObjectPath
|
||||||
|
var prompt *Prompt
|
||||||
|
|
||||||
|
if err = i.Dbus.Call(DbusItemDelete, 0).Store(&promptPath); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if isPrompt(promptPath) {
|
||||||
|
|
||||||
|
prompt = NewPrompt(i.Conn, promptPath)
|
||||||
|
if _, err = prompt.Prompt(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSecret returns the Secret in an Item using a Session.
|
||||||
|
func (i *Item) GetSecret(session *Session) (secret *Secret, err error) {
|
||||||
|
|
||||||
|
if session == nil {
|
||||||
|
err = ErrNoDbusConn
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = connIsValid(session.Conn); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = i.Dbus.Call(
|
||||||
|
DbusItemGetSecret, 0, session.Dbus.Path(),
|
||||||
|
).Store(&secret); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
secret.session = session
|
||||||
|
secret.item = i
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,7 +110,7 @@ func (i *Item) Label() (label string, err error) {
|
|||||||
|
|
||||||
var variant dbus.Variant
|
var variant dbus.Variant
|
||||||
|
|
||||||
if variant, err = i.Dbus.GetProperty("org.freedesktop.Secret.Item.Label"); err != nil {
|
if variant, err = i.Dbus.GetProperty(DbusItemLabel); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,12 +119,109 @@ func (i *Item) Label() (label string, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locked indicates that an Item is locked (true) or unlocked (false).
|
/*
|
||||||
|
ModifyAttributes modifies the Item.Attrs, both in the object and in Dbus.
|
||||||
|
This is similar to Item.ReplaceAttributes but will only modify the map's given keys so you do not need to provide
|
||||||
|
the entire attribute map.
|
||||||
|
If you wish to remove an attribute, use the value "" (empty string).
|
||||||
|
If you wish to explicitly provide a blank value/empty string, use the constant gosecret.ExplicitAttrEmptyValue.
|
||||||
|
|
||||||
|
This is more or less a convenience/wrapper function around Item.ReplaceAttributes.
|
||||||
|
*/
|
||||||
|
func (i *Item) ModifyAttributes(replaceAttrs map[string]string) (err error) {
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
var currentProps map[string]string = make(map[string]string, 0)
|
||||||
|
var currentVal string
|
||||||
|
|
||||||
|
if replaceAttrs == nil || len(replaceAttrs) == 0 {
|
||||||
|
err = ErrMissingAttrs
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentProps, err = i.Attributes(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range replaceAttrs {
|
||||||
|
if currentVal, ok = currentProps[k]; !ok { // If it isn't in the replacement map, do nothing (i.e. keep it).
|
||||||
|
continue
|
||||||
|
} else if v == currentVal { // If the value is the same, do nothing.
|
||||||
|
continue
|
||||||
|
} else if v == ExplicitAttrEmptyValue { // If it's the "magic empty value" constant, delete the key/value pair.
|
||||||
|
delete(currentProps, k)
|
||||||
|
continue
|
||||||
|
} else { // Otherwise, replace the value.
|
||||||
|
currentProps[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = i.ReplaceAttributes(currentProps)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplaceAttributes replaces the Item.Attrs, both in the object and in Dbus.
|
||||||
|
func (i *Item) ReplaceAttributes(newAttrs map[string]string) (err error) {
|
||||||
|
|
||||||
|
var label string
|
||||||
|
var props map[string]dbus.Variant = make(map[string]dbus.Variant, 0)
|
||||||
|
|
||||||
|
if label, err = i.Label(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
props[DbusItemLabel] = dbus.MakeVariant(label)
|
||||||
|
props[DbusItemAttributes] = dbus.MakeVariant(newAttrs)
|
||||||
|
|
||||||
|
if err = i.Dbus.SetProperty(DbusItemAttributes, props); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
i.Attrs = newAttrs
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSecret sets the Secret for an Item.
|
||||||
|
func (i *Item) SetSecret(secret *Secret) (err error) {
|
||||||
|
|
||||||
|
var c *dbus.Call
|
||||||
|
|
||||||
|
c = i.Dbus.Call(
|
||||||
|
DbusItemSetSecret, 0,
|
||||||
|
)
|
||||||
|
if c.Err != nil {
|
||||||
|
err = c.Err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
i.Secret = secret
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type updates the Item.ItemType from DBus (and returns it).
|
||||||
|
func (i *Item) Type() (itemType string, err error) {
|
||||||
|
|
||||||
|
var variant dbus.Variant
|
||||||
|
|
||||||
|
if variant, err = i.Dbus.GetProperty(DbusItemType); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
i.ItemType = variant.Value().(string)
|
||||||
|
itemType = i.ItemType
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locked indicates if an Item is locked (true) or unlocked (false).
|
||||||
func (i *Item) Locked() (isLocked bool, err error) {
|
func (i *Item) Locked() (isLocked bool, err error) {
|
||||||
|
|
||||||
var variant dbus.Variant
|
var variant dbus.Variant
|
||||||
|
|
||||||
if variant, err = i.Dbus.GetProperty("org.freedesktop.Secret.Item.Locked"); err != nil {
|
if variant, err = i.Dbus.GetProperty(DbusItemLocked); err != nil {
|
||||||
isLocked = true
|
isLocked = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -54,37 +231,52 @@ func (i *Item) Locked() (isLocked bool, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSecret returns the Secret in an Item using a Session.
|
// Created returns the time.Time of when an Item was created.
|
||||||
func (i *Item) GetSecret(session *Session) (secret *Secret, err error) {
|
func (i *Item) Created() (created time.Time, err error) {
|
||||||
|
|
||||||
secret = new(Secret)
|
var variant dbus.Variant
|
||||||
|
var timeInt uint64
|
||||||
|
|
||||||
if err = i.Dbus.Call(
|
if variant, err = i.Dbus.GetProperty(DbusItemCreated); err != nil {
|
||||||
"org.freedesktop.Secret.Item.GetSecret", 0, session.Path(),
|
|
||||||
).Store(&secret); err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
timeInt = variant.Value().(uint64)
|
||||||
|
|
||||||
|
created = time.Unix(int64(timeInt), 0)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete removes an Item from a Collection.
|
/*
|
||||||
func (i *Item) Delete() (err error) {
|
Modified returns the time.Time of when an Item was last modified along with a boolean
|
||||||
|
that indicates if the collection has changed since the last call of Item.Modified.
|
||||||
|
|
||||||
var prompt *Prompt
|
Note that when calling NewItem, the internal library-tracked modification
|
||||||
var promptPath dbus.ObjectPath
|
time (Item.lastModified) will be set to the latest modification time of the Item
|
||||||
|
itself as reported by Dbus rather than the time that NewItem was called.
|
||||||
|
*/
|
||||||
|
func (i *Item) Modified() (modified time.Time, isChanged bool, err error) {
|
||||||
|
|
||||||
if err = i.Dbus.Call("org.freedesktop.Secret.Item.Delete", 0).Store(&promptPath); err != nil {
|
var variant dbus.Variant
|
||||||
|
var timeInt uint64
|
||||||
|
|
||||||
|
if variant, err = i.Dbus.GetProperty(DbusItemModified); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isPrompt(promptPath) {
|
timeInt = variant.Value().(uint64)
|
||||||
prompt = NewPrompt(i.Conn, promptPath)
|
|
||||||
|
|
||||||
if _, err = prompt.Prompt(); err != nil {
|
modified = time.Unix(int64(timeInt), 0)
|
||||||
return
|
|
||||||
}
|
if !i.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.
|
||||||
|
i.lastModified = modified
|
||||||
|
i.lastModifiedSet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isChanged = modified.After(i.lastModified)
|
||||||
|
i.lastModified = modified
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,29 +1,22 @@
|
|||||||
package gosecret
|
package gosecret
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`github.com/godbus/dbus`
|
`github.com/godbus/dbus/v5`
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewPrompt returns a pointer to a new Prompt based on a Dbus connection and a Dbus path.
|
// 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) {
|
func NewPrompt(conn *dbus.Conn, path dbus.ObjectPath) (prompt *Prompt) {
|
||||||
|
|
||||||
prompt = &Prompt{
|
prompt = &Prompt{
|
||||||
|
DbusObject: &DbusObject{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
Dbus: conn.Object(DbusService, path),
|
Dbus: conn.Object(DbusService, path),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
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.
|
// Prompt issues/waits for a prompt for unlocking a Locked Collection or Secret / Item.
|
||||||
func (p *Prompt) Prompt() (promptValue *dbus.Variant, err error) {
|
func (p *Prompt) Prompt() (promptValue *dbus.Variant, err error) {
|
||||||
|
|
||||||
@ -37,12 +30,14 @@ func (p *Prompt) Prompt() (promptValue *dbus.Variant, err error) {
|
|||||||
p.Conn.Signal(c)
|
p.Conn.Signal(c)
|
||||||
defer p.Conn.RemoveSignal(c)
|
defer p.Conn.RemoveSignal(c)
|
||||||
|
|
||||||
if err = p.Dbus.Call("org.freedesktop.Secret.Prompt.Prompt", 0, "").Store(); err != nil {
|
if err = p.Dbus.Call(
|
||||||
|
DbusPrompterInterface, 0, "", // TODO: This last argument, the string, is for "window ID". I'm unclear what for.
|
||||||
|
).Store(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if result = <-c; result.Path == p.Path() {
|
if result = <-c; result.Path == p.Dbus.Path() {
|
||||||
*promptValue = result.Body[1].(dbus.Variant)
|
*promptValue = result.Body[1].(dbus.Variant)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ package gosecret
|
|||||||
func NewSecret(session *Session, params []byte, value []byte, contentType string) (secret *Secret) {
|
func NewSecret(session *Session, params []byte, value []byte, contentType string) (secret *Secret) {
|
||||||
|
|
||||||
secret = &Secret{
|
secret = &Secret{
|
||||||
Session: session.Path(),
|
Session: session.Dbus.Path(),
|
||||||
Parameters: params,
|
Parameters: params,
|
||||||
Value: value,
|
Value: value,
|
||||||
ContentType: contentType,
|
ContentType: contentType,
|
||||||
|
13
secretvalue_funcs.go
Normal file
13
secretvalue_funcs.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package gosecret
|
||||||
|
|
||||||
|
/*
|
||||||
|
MarshalJSON converts a SecretValue to a JSON representation.
|
||||||
|
For compat reasons, the MarshalText is left "unmolested" (i.e. renders to a Base64 value).
|
||||||
|
I don't bother with an UnmarshalJSON because it makes exactly 0 sense to unmarshal due to runtime and unexported fields in Secret.
|
||||||
|
*/
|
||||||
|
func (s *SecretValue) MarshalJSON() (b []byte, err error) {
|
||||||
|
|
||||||
|
b = []byte(string(*s))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
222
service_funcs.go
222
service_funcs.go
@ -1,9 +1,16 @@
|
|||||||
package gosecret
|
package gosecret
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/godbus/dbus"
|
`errors`
|
||||||
|
`fmt`
|
||||||
|
`path/filepath`
|
||||||
|
`strings`
|
||||||
|
|
||||||
|
"github.com/godbus/dbus/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO: Lock method (DbusServiceLockService)?
|
||||||
|
|
||||||
// NewService returns a pointer to a new Service connection.
|
// NewService returns a pointer to a new Service connection.
|
||||||
func NewService() (service *Service, err error) {
|
func NewService() (service *Service, err error) {
|
||||||
|
|
||||||
@ -14,7 +21,7 @@ func NewService() (service *Service, err error) {
|
|||||||
}
|
}
|
||||||
svc.Dbus = service.Conn.Object(DbusService, dbus.ObjectPath(DbusPath))
|
svc.Dbus = service.Conn.Object(DbusService, dbus.ObjectPath(DbusPath))
|
||||||
|
|
||||||
if svc.Session, _, err = svc.Open(); err != nil {
|
if svc.Session, err = svc.GetSession(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,40 +115,6 @@ func (s *Service) CreateCollection(label string) (collection *Collection, err er
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
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) (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).
|
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.
|
It's a helper function that avoids needing to make multiple calls in user code.
|
||||||
@ -151,7 +124,7 @@ func (s *Service) GetCollection(name string) (c *Collection, err error) {
|
|||||||
var colls []*Collection
|
var colls []*Collection
|
||||||
|
|
||||||
// First check for an alias.
|
// First check for an alias.
|
||||||
if c, err = s.GetAlias(name); err != nil && err != ErrDoesNotExist{
|
if c, err = s.ReadAlias(name); err != nil && err != ErrDoesNotExist {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c != nil {
|
if c != nil {
|
||||||
@ -192,16 +165,6 @@ func (s *Service) GetSecrets(itemPaths ...dbus.ObjectPath) (secrets map[dbus.Obj
|
|||||||
secrets = make(map[dbus.ObjectPath]*Secret, len(itemPaths))
|
secrets = make(map[dbus.ObjectPath]*Secret, len(itemPaths))
|
||||||
|
|
||||||
// TODO: trigger a Service.Unlock for any locked items?
|
// TODO: trigger a Service.Unlock for any locked items?
|
||||||
/*
|
|
||||||
// TODO: make any errs in here a MultiError instead.
|
|
||||||
for _, secretPath := range itemPaths {
|
|
||||||
if err = s.Dbus.Call(
|
|
||||||
DbusServiceGetSecrets, 0, secretPath,
|
|
||||||
).Store(&result); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
if err = s.Dbus.Call(
|
if err = s.Dbus.Call(
|
||||||
DbusServiceGetSecrets, 0, itemPaths,
|
DbusServiceGetSecrets, 0, itemPaths,
|
||||||
).Store(&secrets); err != nil {
|
).Store(&secrets); err != nil {
|
||||||
@ -211,6 +174,17 @@ func (s *Service) GetSecrets(itemPaths ...dbus.ObjectPath) (secrets map[dbus.Obj
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
GetSession returns a single Session.
|
||||||
|
It's a helper function that wraps Service.OpenSession.
|
||||||
|
*/
|
||||||
|
func (s *Service) GetSession() (ssn *Session, err error) {
|
||||||
|
|
||||||
|
ssn, _, err = s.OpenSession("", "")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Lock locks an Unlocked Service, Collection, etc.
|
Lock locks an Unlocked Service, Collection, etc.
|
||||||
You can usually get objectPath for the object(s) to unlock via <object>.Dbus.Path().
|
You can usually get objectPath for the object(s) to unlock via <object>.Dbus.Path().
|
||||||
@ -218,6 +192,8 @@ func (s *Service) GetSecrets(itemPaths ...dbus.ObjectPath) (secrets map[dbus.Obj
|
|||||||
*/
|
*/
|
||||||
func (s *Service) Lock(objectPaths ...dbus.ObjectPath) (err error) {
|
func (s *Service) Lock(objectPaths ...dbus.ObjectPath) (err error) {
|
||||||
|
|
||||||
|
var errs []error = make([]error, 0)
|
||||||
|
// We only use these as destinations.
|
||||||
var locked []dbus.ObjectPath
|
var locked []dbus.ObjectPath
|
||||||
var prompt *Prompt
|
var prompt *Prompt
|
||||||
var resultPath dbus.ObjectPath
|
var resultPath dbus.ObjectPath
|
||||||
@ -226,12 +202,13 @@ func (s *Service) Lock(objectPaths ...dbus.ObjectPath) (err error) {
|
|||||||
objectPaths = []dbus.ObjectPath{s.Dbus.Path()}
|
objectPaths = []dbus.ObjectPath{s.Dbus.Path()}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make any errs in here a MultiError instead.
|
|
||||||
for _, p := range objectPaths {
|
for _, p := range objectPaths {
|
||||||
if err = s.Dbus.Call(
|
if err = s.Dbus.Call(
|
||||||
DbusServiceLock, 0, p,
|
DbusServiceLock, 0, p,
|
||||||
).Store(&locked, &resultPath); err != nil {
|
).Store(&locked, &resultPath); err != nil {
|
||||||
return
|
errs = append(errs, err)
|
||||||
|
err = nil
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if isPrompt(resultPath) {
|
if isPrompt(resultPath) {
|
||||||
@ -239,28 +216,44 @@ func (s *Service) Lock(objectPaths ...dbus.ObjectPath) (err error) {
|
|||||||
prompt = NewPrompt(s.Conn, resultPath)
|
prompt = NewPrompt(s.Conn, resultPath)
|
||||||
|
|
||||||
if _, err = prompt.Prompt(); err != nil {
|
if _, err = prompt.Prompt(); err != nil {
|
||||||
return
|
errs = append(errs, err)
|
||||||
|
err = nil
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if errs != nil && len(errs) > 0 {
|
||||||
|
err = NewErrors(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Open returns a pointer to a Session from the Service.
|
OpenSession returns a pointer to a Session from the Service.
|
||||||
It's a convenience function around NewSession.
|
It's a convenience function around NewSession.
|
||||||
*/
|
*/
|
||||||
func (s *Service) Open() (session *Session, output dbus.Variant, err error) {
|
func (s *Service) OpenSession(algo, input string) (session *Session, output dbus.Variant, err error) {
|
||||||
|
|
||||||
var path dbus.ObjectPath
|
var path dbus.ObjectPath
|
||||||
|
var algoVariant dbus.Variant
|
||||||
|
var inputVariant dbus.Variant
|
||||||
|
|
||||||
|
if strings.TrimSpace(algo) == "" {
|
||||||
|
algoVariant = dbus.MakeVariant("plain")
|
||||||
|
} else {
|
||||||
|
algoVariant = dbus.MakeVariant(algo)
|
||||||
|
}
|
||||||
|
|
||||||
|
inputVariant = dbus.MakeVariant(input)
|
||||||
|
|
||||||
// In *theory*, SecretService supports multiple "algorithms" for encryption in-transit, but I don't think it's implemented (yet)?
|
// In *theory*, SecretService supports multiple "algorithms" for encryption in-transit, but I don't think it's implemented (yet)?
|
||||||
// TODO: confirm this.
|
// TODO: confirm this.
|
||||||
// Possible flags are dbus.Flags consts: https://pkg.go.dev/github.com/godbus/dbus#Flags
|
// 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.
|
// Oddly, there is no "None" flag. So it's explicitly specified as a null byte.
|
||||||
if err = s.Dbus.Call(
|
if err = s.Dbus.Call(
|
||||||
DbusServiceOpenSession, 0, "plain", dbus.MakeVariant(""),
|
DbusServiceOpenSession, 0, algoVariant, inputVariant,
|
||||||
).Store(&output, &path); err != nil {
|
).Store(&output, &path); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -271,10 +264,52 @@ 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.
|
ReadAlias allows one to fetch a Collection based on an alias name.
|
||||||
TODO: return arrays of Items instead of dbus.ObjectPaths.
|
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) SearchItems(attributes map[string]string) (unlockedItems []dbus.ObjectPath, lockedItems []dbus.ObjectPath, err error) {
|
func (s *Service) ReadAlias(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
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
SearchItems searches all Collection objects and returns all matches based on the map of attributes.
|
||||||
|
*/
|
||||||
|
func (s *Service) SearchItems(attributes map[string]string) (unlockedItems []*Item, lockedItems []*Item, err error) {
|
||||||
|
|
||||||
|
var locked []dbus.ObjectPath
|
||||||
|
var unlocked []dbus.ObjectPath
|
||||||
|
var collectionObjs []*Collection
|
||||||
|
var collections map[dbus.ObjectPath]*Collection = make(map[dbus.ObjectPath]*Collection, 0)
|
||||||
|
var ok bool
|
||||||
|
var c *Collection
|
||||||
|
var cPath dbus.ObjectPath
|
||||||
|
var errs []error = make([]error, 0)
|
||||||
|
|
||||||
if attributes == nil || len(attributes) == 0 {
|
if attributes == nil || len(attributes) == 0 {
|
||||||
err = ErrMissingAttrs
|
err = ErrMissingAttrs
|
||||||
@ -283,7 +318,66 @@ func (s *Service) SearchItems(attributes map[string]string) (unlockedItems []dbu
|
|||||||
|
|
||||||
err = s.Dbus.Call(
|
err = s.Dbus.Call(
|
||||||
DbusServiceSearchItems, 0, attributes,
|
DbusServiceSearchItems, 0, attributes,
|
||||||
).Store(&unlockedItems, &lockedItems)
|
).Store(&unlocked, &locked)
|
||||||
|
|
||||||
|
lockedItems = make([]*Item, len(locked))
|
||||||
|
unlockedItems = make([]*Item, len(unlocked))
|
||||||
|
|
||||||
|
if collectionObjs, err = s.Collections(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c = range collectionObjs {
|
||||||
|
if _, ok = collections[c.Dbus.Path()]; !ok {
|
||||||
|
collections[c.Dbus.Path()] = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locked items
|
||||||
|
for idx, i := range locked {
|
||||||
|
|
||||||
|
cPath = dbus.ObjectPath(filepath.Dir(string(i)))
|
||||||
|
|
||||||
|
if c, ok = collections[cPath]; !ok {
|
||||||
|
errs = append(errs, errors.New(fmt.Sprintf(
|
||||||
|
"could not find matching Collection for locked item %v", string(i),
|
||||||
|
)))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if lockedItems[idx], err = NewItem(c, i); err != nil {
|
||||||
|
errs = append(errs, errors.New(fmt.Sprintf(
|
||||||
|
"could not create Item for locked item %v", string(i),
|
||||||
|
)))
|
||||||
|
err = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlocked items
|
||||||
|
for idx, i := range unlocked {
|
||||||
|
|
||||||
|
cPath = dbus.ObjectPath(filepath.Dir(string(i)))
|
||||||
|
|
||||||
|
if c, ok = collections[cPath]; !ok {
|
||||||
|
errs = append(errs, errors.New(fmt.Sprintf(
|
||||||
|
"could not find matching Collection for unlocked item %v", string(i),
|
||||||
|
)))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if unlockedItems[idx], err = NewItem(c, i); err != nil {
|
||||||
|
errs = append(errs, errors.New(fmt.Sprintf(
|
||||||
|
"could not create Item for unlocked item %v", string(i),
|
||||||
|
)))
|
||||||
|
err = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if errs != nil && len(errs) > 0 {
|
||||||
|
err = NewErrors(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -312,6 +406,7 @@ func (s *Service) SetAlias(alias string, objectPath dbus.ObjectPath) (err error)
|
|||||||
*/
|
*/
|
||||||
func (s *Service) Unlock(objectPaths ...dbus.ObjectPath) (err error) {
|
func (s *Service) Unlock(objectPaths ...dbus.ObjectPath) (err error) {
|
||||||
|
|
||||||
|
var errs []error = make([]error, 0)
|
||||||
var unlocked []dbus.ObjectPath
|
var unlocked []dbus.ObjectPath
|
||||||
var prompt *Prompt
|
var prompt *Prompt
|
||||||
var resultPath dbus.ObjectPath
|
var resultPath dbus.ObjectPath
|
||||||
@ -320,12 +415,13 @@ func (s *Service) Unlock(objectPaths ...dbus.ObjectPath) (err error) {
|
|||||||
objectPaths = []dbus.ObjectPath{s.Dbus.Path()}
|
objectPaths = []dbus.ObjectPath{s.Dbus.Path()}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: make any errs in here a MultiError instead.
|
|
||||||
for _, p := range objectPaths {
|
for _, p := range objectPaths {
|
||||||
if err = s.Dbus.Call(
|
if err = s.Dbus.Call(
|
||||||
DbusServiceUnlock, 0, p,
|
DbusServiceUnlock, 0, p,
|
||||||
).Store(&unlocked, &resultPath); err != nil {
|
).Store(&unlocked, &resultPath); err != nil {
|
||||||
return
|
errs = append(errs, err)
|
||||||
|
err = nil
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if isPrompt(resultPath) {
|
if isPrompt(resultPath) {
|
||||||
@ -333,10 +429,16 @@ func (s *Service) Unlock(objectPaths ...dbus.ObjectPath) (err error) {
|
|||||||
prompt = NewPrompt(s.Conn, resultPath)
|
prompt = NewPrompt(s.Conn, resultPath)
|
||||||
|
|
||||||
if _, err = prompt.Prompt(); err != nil {
|
if _, err = prompt.Prompt(); err != nil {
|
||||||
return
|
errs = append(errs, err)
|
||||||
|
err = nil
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if errs != nil && len(errs) > 0 {
|
||||||
|
err = NewErrors(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,22 @@
|
|||||||
package gosecret
|
package gosecret
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/godbus/dbus"
|
"github.com/godbus/dbus/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
// I'm still not 100% certain what Sessions are used for?
|
// I'm still not 100% certain what Sessions are used for, aside from getting Secrets from Items.
|
||||||
|
|
||||||
/*
|
/*
|
||||||
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
|
You will almost always want to use Service.GetSession or Service.OpenSession instead.
|
||||||
*/
|
*/
|
||||||
func NewSession(service *Service, path dbus.ObjectPath) (session *Session) {
|
func NewSession(service *Service, path dbus.ObjectPath) (session *Session) {
|
||||||
|
|
||||||
var ssn Session = Session{
|
var ssn Session = Session{
|
||||||
&DbusObject{
|
DbusObject: &DbusObject{
|
||||||
Conn: service.Conn,
|
Conn: service.Conn,
|
||||||
},
|
},
|
||||||
|
service: service,
|
||||||
}
|
}
|
||||||
session.Dbus = session.Conn.Object(DbusInterfaceSession, path)
|
session.Dbus = session.Conn.Object(DbusInterfaceSession, path)
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package gosecret
|
package gosecret
|
||||||
|
|
||||||
|
// This currently is not used.
|
||||||
|
|
||||||
/*
|
/*
|
||||||
TranslateError translates a SecretServiceErrEnum into a SecretServiceError.
|
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 a matching error was found, ok will be true and err will be the matching SecretServiceError.
|
||||||
@ -20,6 +22,7 @@ func TranslateError(ssErr SecretServiceErrEnum) (ok bool, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Error returns the string format of the error; this is necessary to be considered a valid error interface.
|
||||||
func (e SecretServiceError) Error() (errStr string) {
|
func (e SecretServiceError) Error() (errStr string) {
|
||||||
|
|
||||||
errStr = e.ErrDesc
|
errStr = e.ErrDesc
|
||||||
|
53
types.go
53
types.go
@ -3,9 +3,11 @@ package gosecret
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/godbus/dbus"
|
"github.com/godbus/dbus/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO: add label fields to Collection and Item, make their respective Label methods update the field.
|
||||||
|
|
||||||
/*
|
/*
|
||||||
MultiError is a type of error.Error that can contain multiple error.Errors. Confused? Don't worry about it.
|
MultiError is a type of error.Error that can contain multiple error.Errors. Confused? Don't worry about it.
|
||||||
*/
|
*/
|
||||||
@ -65,6 +67,7 @@ type Prompt struct {
|
|||||||
*/
|
*/
|
||||||
type Service struct {
|
type Service struct {
|
||||||
*DbusObject
|
*DbusObject
|
||||||
|
// Session is a default Session initiated automatically.
|
||||||
Session *Session `json:"-"`
|
Session *Session `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,6 +78,8 @@ type Service struct {
|
|||||||
*/
|
*/
|
||||||
type Session struct {
|
type Session struct {
|
||||||
*DbusObject
|
*DbusObject
|
||||||
|
// collection tracks the Service this Session was created from.
|
||||||
|
service *Service
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -89,8 +94,10 @@ type Collection struct {
|
|||||||
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 is unexported; it's only used to determine if this is a first-initialization of the modification time or not.
|
||||||
lastModifiedSet bool
|
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 is used for the Collection's name/label so the Dbus path doesn't need to be parsed all the time.
|
||||||
name string
|
name string
|
||||||
|
// service tracks the Service this Collection was created from.
|
||||||
|
service *Service
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -100,6 +107,30 @@ type Collection struct {
|
|||||||
*/
|
*/
|
||||||
type Item struct {
|
type Item struct {
|
||||||
*DbusObject
|
*DbusObject
|
||||||
|
/*
|
||||||
|
Attrs are the attributes to assign to this Item.
|
||||||
|
They should be considered non-secret; they're primarily used to *look up* an Item.
|
||||||
|
*Do NOT put secret/sensitive data in an Item's Attrs!*
|
||||||
|
*/
|
||||||
|
Attrs map[string]string `json:"attributes"`
|
||||||
|
// Secret is the corresponding Secret object.
|
||||||
|
Secret *Secret `json:"secret"`
|
||||||
|
/*
|
||||||
|
ItemType is the type of this Item as a Dbus interface name.
|
||||||
|
e.g. org.gnome.keyring.NetworkPassword, org.freedesktop.Secret.Generic, org.remmina.Password, etc.
|
||||||
|
*/
|
||||||
|
ItemType string `json:"dbus_type"`
|
||||||
|
// 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
|
||||||
|
/*
|
||||||
|
idx is the index identifier of the Item.
|
||||||
|
It SHOULD correlate to indices in Collection.Items, but don't rely on this.
|
||||||
|
*/
|
||||||
|
idx int
|
||||||
|
// collection tracks the Collection this Item is in.
|
||||||
|
collection *Collection
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -109,12 +140,22 @@ type Item struct {
|
|||||||
https://specifications.freedesktop.org/secret-service/latest/ch14.html#type-Secret
|
https://specifications.freedesktop.org/secret-service/latest/ch14.html#type-Secret
|
||||||
*/
|
*/
|
||||||
type Secret struct {
|
type Secret struct {
|
||||||
// Session is a Dbus object path for the associated Session.
|
// Session is a Dbus object path for the associated Session (the actual Session is stored in an unexported field).
|
||||||
Session dbus.ObjectPath `json:"-"`
|
Session dbus.ObjectPath `json:"session_path"`
|
||||||
// Parameters are "algorithm dependent parameters for secret value encoding" - likely this will just be an empty byteslice.
|
/*
|
||||||
|
Parameters are "algorithm dependent parameters for secret value encoding" - likely this will just be an empty byteslice.
|
||||||
|
Refer to Session for more information.
|
||||||
|
*/
|
||||||
Parameters []byte `json:"params"`
|
Parameters []byte `json:"params"`
|
||||||
// Value is the secret's content in []byte format.
|
// Value is the secret's content in []byte format.
|
||||||
Value []byte `json:"value"`
|
Value SecretValue `json:"value"`
|
||||||
// ContentType is the MIME type of Value.
|
// ContentType is the MIME type of Value.
|
||||||
ContentType string `json:"content_type"`
|
ContentType string `json:"content_type"`
|
||||||
|
// item is the Item this Secret belongs to.
|
||||||
|
item *Item
|
||||||
|
// session is the Session used to decode/decrypt this Secret.
|
||||||
|
session *Session
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SecretValue is a custom type that handles JSON encoding/decoding a little more easily.
|
||||||
|
type SecretValue []byte
|
||||||
|
Loading…
Reference in New Issue
Block a user