Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
dcc8499efa
|
|||
|
e79d530e47
|
|||
|
b02aa9877c
|
|||
|
d81452a92c
|
|||
|
cf354c3fa9
|
|||
|
56ba974dec
|
|||
|
142c0ba74f
|
|||
|
94ae20829e
|
|||
|
eda1777431
|
|||
|
b6ba0f9736
|
|||
|
0fc0e0c269
|
|||
|
1d093627f6
|
|||
|
c0a1e4a281
|
|||
|
1d9145bcf2
|
|||
|
3cab98e584
|
|||
|
cf24035c85
|
|||
|
a5b479ae4e
|
|||
|
dbc0962e46
|
|||
|
|
644ac13451
|
||
|
|
bdee0ea110
|
||
|
|
e298633935
|
||
|
|
809c6d6f97
|
39
.gitignore
vendored
Normal file
39
.gitignore
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
*.7z
|
||||||
|
*.bak
|
||||||
|
*.deb
|
||||||
|
*.jar
|
||||||
|
*.rar
|
||||||
|
*.run
|
||||||
|
*.sig
|
||||||
|
*.tar
|
||||||
|
*.tar.bz2
|
||||||
|
*.tar.gz
|
||||||
|
*.tar.xz
|
||||||
|
*.tbz
|
||||||
|
*.tbz2
|
||||||
|
*.tgz
|
||||||
|
*.txz
|
||||||
|
*.zip
|
||||||
|
.*.swp
|
||||||
|
.editix
|
||||||
|
|
||||||
|
# https://github.com/github/gitignore/blob/master/Go.gitignore
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
# But DO include the actual tests.
|
||||||
|
!_test.go
|
||||||
|
!*_test.go
|
||||||
|
!*_test/
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
4
.idea/.gitignore
generated
vendored
4
.idea/.gitignore
generated
vendored
@@ -1,8 +1,8 @@
|
|||||||
# Default ignored files
|
# Default ignored files
|
||||||
/shelf/
|
/shelf/
|
||||||
/workspace.xml
|
/workspace.xml
|
||||||
# Editor-based HTTP Client requests
|
|
||||||
/httpRequests/
|
|
||||||
# Datasource local storage ignored files
|
# Datasource local storage ignored files
|
||||||
/dataSources/
|
/dataSources/
|
||||||
/dataSources.local.xml
|
/dataSources.local.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
|||||||
33
.idea/codeStyles/Project.xml
generated
33
.idea/codeStyles/Project.xml
generated
@@ -1,33 +0,0 @@
|
|||||||
<component name="ProjectCodeStyleConfiguration">
|
|
||||||
<code_scheme name="Project" version="173">
|
|
||||||
<option name="OTHER_INDENT_OPTIONS">
|
|
||||||
<value>
|
|
||||||
<option name="USE_TAB_CHARACTER" value="true" />
|
|
||||||
<option name="SMART_TABS" value="true" />
|
|
||||||
</value>
|
|
||||||
</option>
|
|
||||||
<GoCodeStyleSettings>
|
|
||||||
<option name="USE_BACK_QUOTES_FOR_IMPORTS" value="true" />
|
|
||||||
<option name="ADD_PARENTHESES_FOR_SINGLE_IMPORT" value="true" />
|
|
||||||
<option name="REMOVE_REDUNDANT_IMPORT_ALIASES" value="true" />
|
|
||||||
<option name="ADD_LEADING_SPACE_TO_COMMENTS" value="true" />
|
|
||||||
<option name="MOVE_ALL_STDLIB_IMPORTS_IN_ONE_GROUP" value="true" />
|
|
||||||
<option name="GROUP_STDLIB_IMPORTS" value="true" />
|
|
||||||
<option name="WRAP_COMP_LIT" value="5" />
|
|
||||||
<option name="WRAP_FUNC_PARAMS" value="5" />
|
|
||||||
<option name="WRAP_FUNC_RESULT" value="5" />
|
|
||||||
</GoCodeStyleSettings>
|
|
||||||
<XML>
|
|
||||||
<option name="XML_KEEP_WHITESPACES" value="true" />
|
|
||||||
<option name="XML_KEEP_WHITE_SPACES_INSIDE_CDATA" value="true" />
|
|
||||||
</XML>
|
|
||||||
<codeStyleSettings language="XML">
|
|
||||||
<option name="WRAP_ON_TYPING" value="1" />
|
|
||||||
</codeStyleSettings>
|
|
||||||
<codeStyleSettings language="go">
|
|
||||||
<option name="RIGHT_MARGIN" value="180" />
|
|
||||||
<option name="CALL_PARAMETERS_WRAP" value="5" />
|
|
||||||
<option name="WRAP_ON_TYPING" value="1" />
|
|
||||||
</codeStyleSettings>
|
|
||||||
</code_scheme>
|
|
||||||
</component>
|
|
||||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
5
.idea/codeStyles/codeStyleConfig.xml
generated
@@ -1,5 +0,0 @@
|
|||||||
<component name="ProjectCodeStyleConfiguration">
|
|
||||||
<state>
|
|
||||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="custom" />
|
|
||||||
</state>
|
|
||||||
</component>
|
|
||||||
2
.idea/discord.xml
generated
2
.idea/discord.xml
generated
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="DiscordProjectSettings">
|
<component name="DiscordProjectSettings">
|
||||||
<option name="show" value="PROJECT" />
|
<option name="show" value="PROJECT_FILES" />
|
||||||
<option name="description" value="" />
|
<option name="description" value="" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
14
.ref/URLs
Normal file
14
.ref/URLs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
https://freedesktop.org/wiki/Specifications/secret-storage-spec/secrets-api-0.1.html#eggdbus-interface-org.freedesktop.Secrets.Collection
|
||||||
|
|
||||||
|
Quick reference URLs:
|
||||||
|
|
||||||
|
Dbus paths: https://specifications.freedesktop.org/secret-service/latest/ch12.html
|
||||||
|
Message types: https://dbus.freedesktop.org/doc/dbus-specification.html#type-system
|
||||||
|
Use this for reference for errors like:
|
||||||
|
Type of message, “(ao)”, does not match expected type “(aoo)”
|
||||||
|
See dbus_types subdir for Golang structs that can be used in the future to improve these error messages.
|
||||||
116
.ref/dbus_types/structs.go
Normal file
116
.ref/dbus_types/structs.go
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
package dbus_types
|
||||||
|
|
||||||
|
// https://dbus.freedesktop.org/doc/dbus-specification.html#type-system
|
||||||
|
|
||||||
|
type DbusType struct {
|
||||||
|
TypeName string
|
||||||
|
Symbol rune
|
||||||
|
Desc string
|
||||||
|
ZeroValue interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
BASIC TYPES
|
||||||
|
*/
|
||||||
|
|
||||||
|
var DbusByte DbusType = DbusType{
|
||||||
|
TypeName: "BYTE",
|
||||||
|
Symbol: 'y',
|
||||||
|
Desc: "Unsigned 8-bit integer",
|
||||||
|
ZeroValue: byte(0x0),
|
||||||
|
}
|
||||||
|
|
||||||
|
var DbusBoolean DbusType = DbusType{
|
||||||
|
TypeName: "BOOLEAN",
|
||||||
|
Symbol: 'b',
|
||||||
|
Desc: "Boolean value: 0 is false, 1 is true, any other value allowed by the marshalling format is invalid",
|
||||||
|
ZeroValue: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
var DbusInt16 DbusType = DbusType{
|
||||||
|
TypeName: "INT16",
|
||||||
|
Symbol: 'n',
|
||||||
|
Desc: "Signed (two's complement) 16-bit integer",
|
||||||
|
ZeroValue: int16(0),
|
||||||
|
}
|
||||||
|
|
||||||
|
var DbusUint16 DbusType = DbusType{
|
||||||
|
TypeName: "UINT16",
|
||||||
|
Symbol: 'q',
|
||||||
|
Desc: "Unsigned 16-bit integer",
|
||||||
|
ZeroValue: uint16(0),
|
||||||
|
}
|
||||||
|
|
||||||
|
var DbusInt32 DbusType = DbusType{
|
||||||
|
TypeName: "INT32",
|
||||||
|
Symbol: 'i',
|
||||||
|
Desc: "Signed (two's complement) 32-bit integer",
|
||||||
|
ZeroValue: int32(0),
|
||||||
|
}
|
||||||
|
|
||||||
|
var DbusUint32 DbusType = DbusType{
|
||||||
|
TypeName: "UINT32",
|
||||||
|
Symbol: 'u',
|
||||||
|
Desc: "Unsigned 32-bit integer",
|
||||||
|
ZeroValue: uint32(0),
|
||||||
|
}
|
||||||
|
|
||||||
|
var DbusInt64 DbusType = DbusType{
|
||||||
|
TypeName: "INT64",
|
||||||
|
Symbol: 'x',
|
||||||
|
Desc: "Signed (two's complement) 64-bit integer (mnemonic: x and t are the first characters in \"sixty\" not already used for something more common)",
|
||||||
|
ZeroValue: int64(0),
|
||||||
|
}
|
||||||
|
|
||||||
|
var DbusUint64 DbusType = DbusType{
|
||||||
|
TypeName: "UINT64",
|
||||||
|
Symbol: 't',
|
||||||
|
Desc: "Unsigned 64-bit integer",
|
||||||
|
ZeroValue: uint64(0),
|
||||||
|
}
|
||||||
|
|
||||||
|
var DbusDoubleFloat DbusType = DbusType{
|
||||||
|
TypeName: "DOUBLE",
|
||||||
|
Symbol: 'd',
|
||||||
|
Desc: "IEEE 754 double-precision floating point",
|
||||||
|
ZeroValue: float64(0),
|
||||||
|
}
|
||||||
|
|
||||||
|
var DbusUnixFD DbusType = DbusType{
|
||||||
|
TypeName: "UNIX_FD",
|
||||||
|
Symbol: 'h',
|
||||||
|
Desc: "Unsigned 32-bit integer representing an index into an out-of-band array of file descriptors, transferred via some platform-specific mechanism (mnemonic: h for handle)",
|
||||||
|
ZeroValue: uint32(0), // See https://pkg.go.dev/github.com/godbus/dbus#UnixFDIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
var DbusString DbusType = DbusType{
|
||||||
|
TypeName: "STRING",
|
||||||
|
Symbol: 'o',
|
||||||
|
Desc: "No extra constraints",
|
||||||
|
ZeroValue: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
var DbusObjectPath DbusType = DbusType{
|
||||||
|
TypeName: "OBJECT_PATH",
|
||||||
|
Symbol: 'o',
|
||||||
|
Desc: "A syntactically valid Path for Dbus",
|
||||||
|
ZeroValue: nil, // ???
|
||||||
|
}
|
||||||
|
|
||||||
|
var DbusSignature DbusType = DbusType{
|
||||||
|
TypeName: "SIGNATURE",
|
||||||
|
Symbol: 'g',
|
||||||
|
Desc: "0 or more single complete types", // ???
|
||||||
|
ZeroValue: nil, // ???
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
CONTAINER TYPES
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
TODO: not sure how to struct this natively, but:
|
||||||
|
Dbus Struct: (<symbol(s)...>) // Note: structs can be nested e.g. (i(ii))
|
||||||
|
Dbus Array: a<symbol> // The symbol can be any type (even nested arrays, e.g. aai), but only one type is allowed. Arrays are like Golang slices; no fixed size.
|
||||||
|
Dbus Variant: v<symbol> // Dbus equivalent of interface{}, more or less. See https://dbus.freedesktop.org/doc/dbus-specification.html#container-types
|
||||||
|
Dbus Dict: [kv] // Where k is the key's type and v is the value's type.
|
||||||
|
*/
|
||||||
12
TODO
12
TODO
@@ -1 +1,11 @@
|
|||||||
- tests for V0
|
- 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/
|
||||||
|
- Benchmarking?
|
||||||
|
- 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)
|
||||||
|
|||||||
@@ -1,108 +1,83 @@
|
|||||||
package libsecret
|
package gosecret
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`github.com/godbus/dbus`
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/godbus/dbus/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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) {
|
NewCollection returns a pointer to a Collection based on a Service and a Dbus path.
|
||||||
|
You will almost always want to use Service.GetCollection instead.
|
||||||
|
*/
|
||||||
|
func NewCollection(service *Service, path dbus.ObjectPath) (coll *Collection, err error) {
|
||||||
|
|
||||||
|
var splitPath []string
|
||||||
|
|
||||||
|
if service == nil {
|
||||||
|
err = ErrNoDbusConn
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = validConnPath(service.Conn, path); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
coll = &Collection{
|
coll = &Collection{
|
||||||
Conn: conn,
|
DbusObject: &DbusObject{
|
||||||
Dbus: conn.Object(DBusServiceName, path),
|
Conn: service.Conn,
|
||||||
|
Dbus: service.Conn.Object(DbusService, path),
|
||||||
|
},
|
||||||
|
service: service,
|
||||||
|
// lastModified: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
splitPath = strings.Split(string(coll.Dbus.Path()), "/")
|
||||||
|
|
||||||
|
coll.name = splitPath[len(splitPath)-1]
|
||||||
|
|
||||||
|
_, _, err = coll.Modified()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path returns the dbus.ObjectPath of the Collection.
|
/*
|
||||||
func (c Collection) Path() (coll dbus.ObjectPath) {
|
CreateItem returns a pointer to an Item based on a label, some attributes, a Secret,
|
||||||
|
whether any existing secret with the same label should be replaced or not, and the optional itemType.
|
||||||
|
|
||||||
// Remove this method in V1. It's bloat since we now have an exported Dbus.
|
itemType is optional; if specified, it should be a Dbus interface (only the first element is used).
|
||||||
coll = c.Dbus.Path()
|
If not specified, the default DbusDefaultItemType will be used. The most common itemType is DbusDefaultItemType
|
||||||
|
and is the current recommendation.
|
||||||
|
Other types used are:
|
||||||
|
|
||||||
return
|
org.gnome.keyring.NetworkPassword
|
||||||
}
|
org.gnome.keyring.Note
|
||||||
|
|
||||||
// Items returns a slice of Item items in the Collection.
|
These are libsecret schemas as defined at
|
||||||
func (c *Collection) Items() (items []Item, err error) {
|
https://gitlab.gnome.org/GNOME/libsecret/-/blob/master/libsecret/secret-schemas.c (and bundled in with libsecret).
|
||||||
|
Support for adding custom schemas MAY come in the future but is unsupported currently.
|
||||||
var variant dbus.Variant
|
*/
|
||||||
var paths []dbus.ObjectPath
|
func (c *Collection) CreateItem(label string, attrs map[string]string, secret *Secret, replace bool, itemType ...string) (item *Item, err error) {
|
||||||
|
|
||||||
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 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)
|
var typeString string
|
||||||
|
|
||||||
attrs["profile"] = label
|
if itemType != nil && len(itemType) > 0 {
|
||||||
props["org.freedesktop.Secret.Item.Label"] = dbus.MakeVariant(label)
|
typeString = itemType[0]
|
||||||
props["org.freedesktop.Secret.Item.Attributes"] = dbus.MakeVariant(attrs)
|
} else {
|
||||||
|
typeString = DbusDefaultItemType
|
||||||
|
}
|
||||||
|
|
||||||
|
props[DbusItemLabel] = dbus.MakeVariant(label)
|
||||||
|
props[DbusItemType] = dbus.MakeVariant(typeString)
|
||||||
|
props[DbusItemAttributes] = 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
|
||||||
}
|
}
|
||||||
@@ -117,17 +92,91 @@ func (c *Collection) CreateItem(label string, secret *Secret, replace bool) (ite
|
|||||||
path = variant.Value().(dbus.ObjectPath)
|
path = variant.Value().(dbus.ObjectPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
item = NewItem(c.Conn, path)
|
item, err = NewItem(c, path)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locked indicates that a Collection is locked (true) or unlocked (false).
|
/*
|
||||||
|
Delete removes a Collection.
|
||||||
|
While *technically* not necessary, it is recommended that you iterate through
|
||||||
|
Collection.Items and do an Item.Delete for each item *before* calling Collection.Delete;
|
||||||
|
the item paths are cached as "orphaned paths" in Dbus otherwise if not deleted before deleting
|
||||||
|
their Collection. They should clear on a reboot or restart of Dbus (but rebooting Dbus on a system in use is... troublesome).
|
||||||
|
*/
|
||||||
|
func (c *Collection) Delete() (err error) {
|
||||||
|
|
||||||
|
var promptPath dbus.ObjectPath
|
||||||
|
var prompt *Prompt
|
||||||
|
|
||||||
|
if err = c.Dbus.Call(DbusCollectionDelete, 0).Store(&promptPath); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if isPrompt(promptPath) {
|
||||||
|
|
||||||
|
prompt = NewPrompt(c.Conn, promptPath)
|
||||||
|
if _, err = prompt.Prompt(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Items returns a slice of Item pointers in the Collection.
|
||||||
|
func (c *Collection) Items() (items []*Item, err error) {
|
||||||
|
|
||||||
|
var paths []dbus.ObjectPath
|
||||||
|
var item *Item
|
||||||
|
var variant dbus.Variant
|
||||||
|
var errs []error = make([]error, 0)
|
||||||
|
|
||||||
|
if variant, err = c.Dbus.GetProperty(DbusCollectionItems); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
paths = variant.Value().([]dbus.ObjectPath)
|
||||||
|
|
||||||
|
items = make([]*Item, len(paths))
|
||||||
|
|
||||||
|
for idx, path := range paths {
|
||||||
|
if item, err = NewItem(c, path); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
err = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
items[idx] = item
|
||||||
|
}
|
||||||
|
err = NewErrors(err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Label returns the Collection label (name).
|
||||||
|
func (c *Collection) Label() (label string, err error) {
|
||||||
|
|
||||||
|
var variant dbus.Variant
|
||||||
|
|
||||||
|
if variant, err = c.Dbus.GetProperty(DbusCollectionLabel); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
label = variant.Value().(string)
|
||||||
|
|
||||||
|
if label != c.name {
|
||||||
|
c.name = label
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locked indicates if a Collection is locked (true) or unlocked (false).
|
||||||
func (c *Collection) Locked() (isLocked bool, err error) {
|
func (c *Collection) Locked() (isLocked bool, err error) {
|
||||||
|
|
||||||
var variant dbus.Variant
|
var variant dbus.Variant
|
||||||
|
|
||||||
if variant, err = c.Dbus.GetProperty("org.freedesktop.Secret.Collection.Locked"); err != nil {
|
if variant, err = c.Dbus.GetProperty(DbusCollectionLocked); err != nil {
|
||||||
isLocked = true
|
isLocked = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -136,3 +185,128 @@ func (c *Collection) Locked() (isLocked bool, err error) {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Relabel modifies the Collection's label in Dbus.
|
||||||
|
func (c *Collection) Relabel(newLabel string) (err error) {
|
||||||
|
|
||||||
|
var variant dbus.Variant = dbus.MakeVariant(newLabel)
|
||||||
|
|
||||||
|
if err = c.Dbus.SetProperty(DbusCollectionLabel, variant); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
SearchItems searches a Collection for a matching profile string.
|
||||||
|
It's mostly a carry-over from go-libsecret, and is here for convenience. IT MAY BE REMOVED IN THE FUTURE.
|
||||||
|
|
||||||
|
I promise it's not useful for any other implementation/storage of SecretService whatsoever.
|
||||||
|
|
||||||
|
Deprecated: Use Service.SearchItems instead.
|
||||||
|
*/
|
||||||
|
func (c *Collection) SearchItems(profile string) (items []*Item, err error) {
|
||||||
|
|
||||||
|
var paths []dbus.ObjectPath
|
||||||
|
var errs []error = make([]error, 0)
|
||||||
|
var attrs map[string]string = make(map[string]string, 0)
|
||||||
|
|
||||||
|
attrs["profile"] = profile
|
||||||
|
|
||||||
|
if err = c.Dbus.Call(
|
||||||
|
DbusCollectionSearchItems, 0, attrs,
|
||||||
|
).Store(&paths); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
items = make([]*Item, len(paths))
|
||||||
|
|
||||||
|
for idx, path := range paths {
|
||||||
|
if items[idx], err = NewItem(c, path); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
err = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = NewErrors(err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Created returns the time.Time of when a Collection was created.
|
||||||
|
func (c *Collection) Created() (created time.Time, err error) {
|
||||||
|
|
||||||
|
var variant dbus.Variant
|
||||||
|
var timeInt uint64
|
||||||
|
|
||||||
|
if variant, err = c.Dbus.GetProperty(DbusCollectionCreated); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
timeInt = variant.Value().(uint64)
|
||||||
|
|
||||||
|
created = time.Unix(int64(timeInt), 0)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Modified returns the time.Time of when a Collection was last modified along with a boolean
|
||||||
|
that indicates if the collection has changed since the last call of Collection.Modified.
|
||||||
|
|
||||||
|
Note that when calling NewCollection, the internal library-tracked modification
|
||||||
|
time (Collection.lastModified) will be set to the latest modification time of the Collection
|
||||||
|
itself as reported by Dbus rather than the time that NewCollection was called.
|
||||||
|
*/
|
||||||
|
func (c *Collection) Modified() (modified time.Time, isChanged bool, err error) {
|
||||||
|
|
||||||
|
var variant dbus.Variant
|
||||||
|
var timeInt uint64
|
||||||
|
|
||||||
|
if variant, err = c.Dbus.GetProperty(DbusCollectionModified); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
timeInt = variant.Value().(uint64)
|
||||||
|
|
||||||
|
modified = time.Unix(int64(timeInt), 0)
|
||||||
|
|
||||||
|
if !c.lastModifiedSet {
|
||||||
|
// It's "nil", so set it to modified. We can't check for a zero-value in case Dbus has it as a zero-value.
|
||||||
|
c.lastModified = modified
|
||||||
|
c.lastModifiedSet = true
|
||||||
|
}
|
||||||
|
|
||||||
|
isChanged = modified.After(c.lastModified)
|
||||||
|
c.lastModified = modified
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
PathName returns the "real" name of a Collection.
|
||||||
|
In some cases, the Collection.Label may not be the actual *name* of the collection
|
||||||
|
(i.e. the label is different from the name used in the Dbus path).
|
||||||
|
This is a thin wrapper around simply extracting the last item from
|
||||||
|
the Collection.Dbus.Path().
|
||||||
|
*/
|
||||||
|
func (c *Collection) PathName() (realName string) {
|
||||||
|
|
||||||
|
var pathSplit []string = strings.Split(string(c.Dbus.Path()), "/")
|
||||||
|
|
||||||
|
realName = pathSplit[len(pathSplit)-1]
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
setModify updates the Collection's modification time (as specified by Collection.Modified).
|
||||||
|
It seems that this does not update automatically.
|
||||||
|
*/
|
||||||
|
func (c *Collection) setModify() (err error) {
|
||||||
|
|
||||||
|
err = c.Dbus.SetProperty(DbusCollectionModified, uint64(time.Now().Unix()))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
286
collection_funcs_test.go
Normal file
286
collection_funcs_test.go
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
package gosecret
|
||||||
|
|
||||||
|
import (
|
||||||
|
`testing`
|
||||||
|
|
||||||
|
`github.com/godbus/dbus/v5`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Some functions are covered in the Service tests.
|
||||||
|
|
||||||
|
/*
|
||||||
|
TestNewCollection tests the following internal functions/methods via nested calls:
|
||||||
|
|
||||||
|
(all calls in TestNewService)
|
||||||
|
NewService
|
||||||
|
NewCollection
|
||||||
|
|
||||||
|
*/
|
||||||
|
func TestNewCollection(t *testing.T) {
|
||||||
|
|
||||||
|
var svc *Service
|
||||||
|
var collection *Collection
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if svc, err = NewService(); err != nil {
|
||||||
|
t.Fatalf("NewService failed: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if collection, err = NewCollection(svc, dbus.ObjectPath(dbusDefaultCollectionPath)); err != nil {
|
||||||
|
t.Errorf(
|
||||||
|
"TestNewCollection failed when fetching collection at '%v': %v",
|
||||||
|
dbusDefaultCollectionPath, err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ = collection
|
||||||
|
|
||||||
|
if err = svc.Close(); err != nil {
|
||||||
|
t.Errorf("could not close Service.Session: %v", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TestCollection_Items tests the following internal functions/methods via nested calls:
|
||||||
|
|
||||||
|
(all calls in TestNewCollection)
|
||||||
|
Service.GetCollection
|
||||||
|
Collection.Items
|
||||||
|
NewSecret
|
||||||
|
Collection.CreateItem
|
||||||
|
Collection.SearchItems
|
||||||
|
Item.Delete
|
||||||
|
|
||||||
|
*/
|
||||||
|
func TestCollection_Items(t *testing.T) {
|
||||||
|
|
||||||
|
var svc *Service
|
||||||
|
var collection *Collection
|
||||||
|
var items []*Item
|
||||||
|
var item *Item
|
||||||
|
var searchItemResults []*Item
|
||||||
|
var secret *Secret
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if svc, err = NewService(); err != nil {
|
||||||
|
t.Fatalf("NewService failed: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if collection, err = svc.GetCollection(defaultCollection); err != nil {
|
||||||
|
t.Errorf("failed when fetching collection '%v': %v",
|
||||||
|
defaultCollection, err.Error(),
|
||||||
|
)
|
||||||
|
if err = svc.Close(); err != nil {
|
||||||
|
t.Fatalf("could not close Service.Session: %v", err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if items, err = collection.Items(); err != nil {
|
||||||
|
t.Errorf(
|
||||||
|
"failed fetching items for '%v' at '%v': %v",
|
||||||
|
defaultCollection, string(collection.Dbus.Path()), err.Error(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
t.Logf("found %v items in collection '%v' at '%v'", len(items), defaultCollection, string(collection.Dbus.Path()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This is almost always going to trigger the warning. See Item.idx for details why.
|
||||||
|
var label string
|
||||||
|
for idx, i := range items {
|
||||||
|
if label, err = i.Label(); err != nil {
|
||||||
|
t.Errorf("failed to get label of item '%v' in collection '%v': %v", string(i.Dbus.Path()), collectionName.String(), err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if i.idx != idx {
|
||||||
|
t.Logf(
|
||||||
|
"WARN: item '%v' ('%v') in collection '%v' internal IDX ('%v') does NOT match native slice IDX ('%v')",
|
||||||
|
string(i.Dbus.Path()), label, collectionName.String(), i.idx, idx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
secret = NewSecret(svc.Session, []byte{}, []byte(testSecretContent), "text/plain")
|
||||||
|
|
||||||
|
if item, err = collection.CreateItem(testItemLabel, itemAttrs, secret, false); err != nil {
|
||||||
|
t.Errorf(
|
||||||
|
"could not create item '%v' in collection '%v': %v",
|
||||||
|
testItemLabel, defaultCollection, err.Error(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if searchItemResults, err = collection.SearchItems(testItemLabel); err != nil {
|
||||||
|
t.Errorf("failed to find item '%v' via Collection.SearchItems: %v", string(item.Dbus.Path()), err.Error())
|
||||||
|
} else if len(searchItemResults) == 0 {
|
||||||
|
t.Errorf("failed to find item '%v' via Collection.SearchItems, returned 0 results (should be at least 1)", testItemLabel)
|
||||||
|
} else {
|
||||||
|
t.Logf("found %v results for Collection.SearchItems", len(searchItemResults))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = item.Delete(); err != nil {
|
||||||
|
t.Errorf("failed to delete created item '%v': %v", string(item.Dbus.Path()), err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = svc.Close(); err != nil {
|
||||||
|
t.Errorf("could not close Service.Session: %v", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TestCollection_Label tests the following internal functions/methods via nested calls:
|
||||||
|
|
||||||
|
(all calls in TestNewCollection)
|
||||||
|
Service.GetCollection
|
||||||
|
Collection.Label
|
||||||
|
Collection.PathName
|
||||||
|
|
||||||
|
*/
|
||||||
|
func TestCollection_Label(t *testing.T) {
|
||||||
|
|
||||||
|
var svc *Service
|
||||||
|
var collection *Collection
|
||||||
|
var collLabel string
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if svc, err = NewService(); err != nil {
|
||||||
|
t.Fatalf("NewService failed: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if collection, err = svc.GetCollection(defaultCollectionLabel); err != nil {
|
||||||
|
t.Errorf(
|
||||||
|
"failed when fetching collection '%v': %v",
|
||||||
|
defaultCollectionLabel, err.Error(),
|
||||||
|
)
|
||||||
|
if err = svc.Close(); err != nil {
|
||||||
|
t.Fatalf("could not close Service.Session: %v", err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if collLabel, err = collection.Label(); err != nil {
|
||||||
|
t.Errorf("cannot fetch label for '%v': %v", string(collection.Dbus.Path()), err.Error())
|
||||||
|
if err = svc.Close(); err != nil {
|
||||||
|
t.Fatalf("could not close Service.Session: %v", err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if defaultCollectionLabel != collLabel {
|
||||||
|
t.Errorf("fetched collection ('%v') does not match fetched collection label ('%v')", collLabel, defaultCollectionLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = svc.Close(); err != nil {
|
||||||
|
t.Errorf("could not close Service.Session: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TestCollection_Locked tests the following internal functions/methods via nested calls:
|
||||||
|
|
||||||
|
(all calls in TestNewCollection)
|
||||||
|
Collection.Locked
|
||||||
|
|
||||||
|
*/
|
||||||
|
func TestCollection_Locked(t *testing.T) {
|
||||||
|
|
||||||
|
var svc *Service
|
||||||
|
var collection *Collection
|
||||||
|
var isLocked bool
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if svc, err = NewService(); err != nil {
|
||||||
|
t.Fatalf("NewService failed: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if collection, err = svc.GetCollection(defaultCollection); err != nil {
|
||||||
|
t.Errorf(
|
||||||
|
"failed when fetching collection '%v': %v",
|
||||||
|
defaultCollectionLabel, err.Error(),
|
||||||
|
)
|
||||||
|
if err = svc.Close(); err != nil {
|
||||||
|
t.Fatalf("could not close Service.Session: %v", err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if isLocked, err = collection.Locked(); err != nil {
|
||||||
|
t.Errorf("failed to get lock status for collection '%v': %v", collection.PathName(), err.Error())
|
||||||
|
} else {
|
||||||
|
t.Logf("collection '%v' lock status: %v", collection.PathName(), isLocked)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = svc.Close(); err != nil {
|
||||||
|
t.Errorf("could not close Service.Session: %v", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TestCollection_Relabel tests the following internal functions/methods via nested calls:
|
||||||
|
|
||||||
|
(all calls in TestNewCollection)
|
||||||
|
Service.CreateCollection
|
||||||
|
Collection.Relabel
|
||||||
|
|
||||||
|
*/
|
||||||
|
func TestCollection_Relabel(t *testing.T) {
|
||||||
|
|
||||||
|
var svc *Service
|
||||||
|
var collection *Collection
|
||||||
|
var collLabel string
|
||||||
|
var newCollLabel string = collectionAlias.String()
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if svc, err = NewService(); err != nil {
|
||||||
|
t.Fatalf("NewService failed: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if collection, err = svc.CreateCollection(collectionName.String()); err != nil {
|
||||||
|
t.Errorf("could not create collection '%v': %v", collectionName.String(), err.Error())
|
||||||
|
if err = svc.Close(); err != nil {
|
||||||
|
t.Fatalf("could not close Service.Session: %v", err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
t.Logf("created collection '%v' at path '%v' successfully", collectionName.String(), string(collection.Dbus.Path()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if collLabel, err = collection.Label(); err != nil {
|
||||||
|
t.Errorf("could not fetch label for collection '%v': %v", string(collection.Dbus.Path()), err.Error())
|
||||||
|
if err = svc.Close(); err != nil {
|
||||||
|
t.Fatalf("could not close Service.Session: %v", err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = collection.Relabel(newCollLabel); err != nil {
|
||||||
|
t.Errorf("failed to relabel collection '%v' to '%v': %v", collLabel, newCollLabel, err.Error())
|
||||||
|
} else {
|
||||||
|
t.Logf("relabeled collection '%v' to '%v'", collLabel, newCollLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
if collLabel, err = collection.Label(); err != nil {
|
||||||
|
t.Errorf("could not fetch label for collection '%v': %v", string(collection.Dbus.Path()), err.Error())
|
||||||
|
if err = collection.Delete(); err != nil {
|
||||||
|
t.Errorf("failed to delete collection '%v': %v", string(collection.Dbus.Path()), err.Error())
|
||||||
|
}
|
||||||
|
if err = svc.Close(); err != nil {
|
||||||
|
t.Fatalf("could not close Service.Session: %v", err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
if collLabel != newCollLabel {
|
||||||
|
t.Errorf("collection did not relabel; new label '%v', actual label '%v'", newCollLabel, collLabel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = collection.Delete(); err != nil {
|
||||||
|
t.Errorf("failed to delete collection '%v': %v", string(collection.Dbus.Path()), err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = svc.Close(); err != nil {
|
||||||
|
t.Errorf("could not close Service.Session: %v", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
254
consts.go
254
consts.go
@@ -1,7 +1,253 @@
|
|||||||
package libsecret
|
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.
|
||||||
|
const (
|
||||||
|
// DbusService is the Dbus service bus identifier.
|
||||||
|
DbusService string = "org.freedesktop.secrets"
|
||||||
|
// DbusServiceBase is the base identifier used by interfaces.
|
||||||
|
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"
|
||||||
|
/*
|
||||||
|
DbusDefaultItemType is the default type to use for Item.Type/Collection.CreateItem.
|
||||||
|
*/
|
||||||
|
DbusDefaultItemType string = DbusServiceBase + ".Generic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Service interface.
|
||||||
|
const (
|
||||||
|
/*
|
||||||
|
DbusInterfaceService is the Dbus interface for working with a Service.
|
||||||
|
Found at /org/freedesktop/secrets/(DbusInterfaceService)
|
||||||
|
*/
|
||||||
|
DbusInterfaceService string = DbusServiceBase + ".Service"
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
|
||||||
|
/*
|
||||||
|
DbusServiceChangeLock has some references in the SecretService Dbus API but
|
||||||
|
it seems to be obsolete - undocumented, at the least.
|
||||||
|
So we don't implement it.
|
||||||
|
*/
|
||||||
|
// DbusServiceChangeLock string = DbusInterfaceService + ".ChangeLock"
|
||||||
|
|
||||||
|
// DbusServiceCreateCollection is used to create a new Collection via Service.CreateCollection.
|
||||||
|
DbusServiceCreateCollection string = DbusInterfaceService + ".CreateCollection"
|
||||||
|
|
||||||
|
// DbusServiceGetSecrets is used to fetch multiple Secret values from multiple Item items in a given Collection (via Service.GetSecrets).
|
||||||
|
DbusServiceGetSecrets string = DbusInterfaceService + ".GetSecrets"
|
||||||
|
|
||||||
|
// DbusServiceLock is used by Service.Lock.
|
||||||
|
DbusServiceLock string = DbusInterfaceService + ".Lock"
|
||||||
|
|
||||||
|
// DbusServiceLockService is [FUNCTION UNKNOWN/UNDOCUMENTED; TODO? NOT IMPLEMENTED.]
|
||||||
|
// DbusServiceLockService string = DbusInterfaceService + ".LockService"
|
||||||
|
|
||||||
|
// DbusServiceOpenSession is used by Service.OpenSession.
|
||||||
|
DbusServiceOpenSession string = DbusInterfaceService + ".OpenSession"
|
||||||
|
|
||||||
|
// DbusServiceReadAlias is used by Service.ReadAlias to return a Collection based on its aliased name.
|
||||||
|
DbusServiceReadAlias string = DbusInterfaceService + ".ReadAlias"
|
||||||
|
|
||||||
|
// DbusServiceSearchItems is used by Service.SearchItems to get arrays of locked and unlocked Item objects.
|
||||||
|
DbusServiceSearchItems string = DbusInterfaceService + ".SearchItems"
|
||||||
|
|
||||||
|
// DbusServiceSetAlias is used by Service.SetAlias to set an alias for a Collection.
|
||||||
|
DbusServiceSetAlias string = DbusInterfaceService + ".SetAlias"
|
||||||
|
|
||||||
|
// DbusServiceUnlock is used by Service.Unlock.
|
||||||
|
DbusServiceUnlock string = DbusInterfaceService + ".Unlock"
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
|
||||||
|
// DbusServiceCollections is used to get a Dbus array of Collection items (Service.Collections).
|
||||||
|
DbusServiceCollections string = DbusInterfaceService + ".Collections"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Session interface.
|
||||||
|
const (
|
||||||
|
/*
|
||||||
|
DbusInterfaceSession is the Dbus interface for working with a Session.
|
||||||
|
Found at /org/freedesktop/secrets/session/<session ID>/(DbusInterfaceSession)
|
||||||
|
*/
|
||||||
|
DbusInterfaceSession = DbusServiceBase + ".Session"
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
|
||||||
|
// DbusSessionClose is used for Session.Close.
|
||||||
|
DbusSessionClose string = DbusInterfaceSession + ".Close"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Collection interface.
|
||||||
|
const (
|
||||||
|
/*
|
||||||
|
DbusInterfaceCollection is the Dbus interface for working with a Collection.
|
||||||
|
Found at /org/freedesktop/secrets/collection/<collection name>/(DbusInterfaceCollection)
|
||||||
|
*/
|
||||||
|
DbusInterfaceCollection string = DbusServiceBase + ".Collection"
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
|
||||||
|
// DbusCollectionCreateItem is used for Collection.CreateItem.
|
||||||
|
DbusCollectionCreateItem string = DbusInterfaceCollection + ".CreateItem"
|
||||||
|
|
||||||
|
// DbusCollectionDelete is used for Collection.Delete.
|
||||||
|
DbusCollectionDelete string = DbusInterfaceCollection + ".Delete"
|
||||||
|
|
||||||
|
// DbusCollectionSearchItems is used for Collection.SearchItems.
|
||||||
|
DbusCollectionSearchItems string = DbusInterfaceCollection + ".SearchItems"
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
|
||||||
|
// DbusCollectionItems is a Dbus array of Item.
|
||||||
|
DbusCollectionItems string = DbusInterfaceCollection + ".Items"
|
||||||
|
|
||||||
|
// DbusCollectionLocked is a Dbus boolean for Collection.Locked.
|
||||||
|
DbusCollectionLocked string = DbusInterfaceCollection + ".Locked"
|
||||||
|
|
||||||
|
// DbusCollectionLabel is the name (label) for Collection.Label.
|
||||||
|
DbusCollectionLabel string = DbusInterfaceCollection + ".Label"
|
||||||
|
|
||||||
|
// DbusCollectionCreated is the time a Collection was created (in a UNIX Epoch uint64) for Collection.Created.
|
||||||
|
DbusCollectionCreated string = DbusInterfaceCollection + ".Created"
|
||||||
|
|
||||||
|
// DbusCollectionModified is the time a Collection was last modified (in a UNIX Epoch uint64) for Collection.Modified.
|
||||||
|
DbusCollectionModified string = DbusInterfaceCollection + ".Modified"
|
||||||
|
|
||||||
|
// TODO: Signals?
|
||||||
|
)
|
||||||
|
|
||||||
|
// Item interface.
|
||||||
|
const (
|
||||||
|
/*
|
||||||
|
DbusInterfaceItem is the Dbus interface for working with Item items.
|
||||||
|
Found at /org/freedesktop/secrets/collection/<collection name>/<item index>/(DbusInterfaceItem)
|
||||||
|
*/
|
||||||
|
DbusInterfaceItem string = DbusServiceBase + ".Item"
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
|
||||||
|
// DbusItemDelete is used by Item.Delete.
|
||||||
|
DbusItemDelete string = DbusInterfaceItem + ".Delete"
|
||||||
|
|
||||||
|
// DbusItemGetSecret is used by Item.GetSecret.
|
||||||
|
DbusItemGetSecret string = DbusInterfaceItem + ".GetSecret"
|
||||||
|
|
||||||
|
// 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, Item.ReplaceAttributes, and Item.ModifyAttributes.
|
||||||
|
*/
|
||||||
|
DbusItemAttributes string = DbusInterfaceItem + ".Attributes"
|
||||||
|
|
||||||
|
// DbusItemLabel is the name (label) for Item.Label.
|
||||||
|
DbusItemLabel string = DbusInterfaceItem + ".Label"
|
||||||
|
|
||||||
|
// DbusItemType is the type of Item (Item.ItemType).
|
||||||
|
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.
|
||||||
|
const (
|
||||||
|
// DbusPath is the path for DbusService.
|
||||||
|
DbusPath string = "/org/freedesktop/secrets"
|
||||||
|
// DbusPromptPrefix is the path used for prompts comparison.
|
||||||
|
DbusPromptPrefix string = DbusPath + "/prompt/"
|
||||||
|
// DbusNewCollectionPath is used to create a new Collection.
|
||||||
|
DbusNewCollectionPath string = DbusPath + "/collection/"
|
||||||
|
// DbusNewSessionPath is used to create a new Session.
|
||||||
|
DbusNewSessionPath string = DbusPath + "/session/"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FLAGS
|
||||||
|
// These are not currently used, but may be in the future.
|
||||||
|
|
||||||
|
// SERVICE
|
||||||
|
|
||||||
|
// ServiceInitFlag is a flag for Service.OpenSession.
|
||||||
|
type ServiceInitFlag int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DBusServiceName string = "org.freedesktop.secrets"
|
FlagServiceNone ServiceInitFlag = iota
|
||||||
DBusPath string = "/org/freedesktop/secrets"
|
FlagServiceOpenSession
|
||||||
PromptPrefix string = DBusPath + "/prompt/"
|
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
|
||||||
)
|
)
|
||||||
|
|||||||
32
consts_test.go
Normal file
32
consts_test.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package gosecret
|
||||||
|
|
||||||
|
import (
|
||||||
|
`github.com/google/uuid`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Paths.
|
||||||
|
const (
|
||||||
|
dbusCollectionPath string = DbusPath + "/collection"
|
||||||
|
dbusDefaultCollectionPath string = dbusCollectionPath + "/login"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Strings.
|
||||||
|
const (
|
||||||
|
defaultCollectionAlias string = "default" // SHOULD point to a collection named "login" (below); "default" is the alias.
|
||||||
|
defaultCollection string = "login"
|
||||||
|
defaultCollectionLabel string = "Login" // a display name; the label is lowercased and normalized for the path (per above).
|
||||||
|
testAlias string = "GOSECRET_TESTING_ALIAS"
|
||||||
|
testSecretContent string = "This is a test secret for gosecret."
|
||||||
|
testItemLabel string = "Gosecret Test Item"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Objects.
|
||||||
|
var (
|
||||||
|
collectionName uuid.UUID = uuid.New()
|
||||||
|
collectionAlias uuid.UUID = uuid.New()
|
||||||
|
itemAttrs map[string]string = map[string]string{
|
||||||
|
"GOSECRET": "yes",
|
||||||
|
"foo": "bar",
|
||||||
|
"profile": testItemLabel,
|
||||||
|
}
|
||||||
|
)
|
||||||
73
errs.go
Normal file
73
errs.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package gosecret
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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")
|
||||||
|
// ErrMissingPaths gets triggered if one or more Dbus object paths are expected but non/not enough are received.
|
||||||
|
ErrMissingPaths error = errors.New("one or more Dbus object paths were expected but an insufficient amount were received")
|
||||||
|
// ErrMissingAttrs gets triggered if attributes were expected but not passed.
|
||||||
|
ErrMissingAttrs error = errors.New("attributes must not be empty/nil")
|
||||||
|
// ErrDoesNotExist gets triggered if a Collection, Item, etc. is attempted to be fetched but none exists via the specified identifier.
|
||||||
|
ErrDoesNotExist error = errors.New("the object under that name/label/alias does not exist")
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
)
|
||||||
107
funcs.go
107
funcs.go
@@ -1,16 +1,117 @@
|
|||||||
package libsecret
|
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.
|
||||||
func isPrompt(path dbus.ObjectPath) (prompt bool) {
|
func isPrompt(path dbus.ObjectPath) (prompt bool) {
|
||||||
|
|
||||||
prompt = strings.HasPrefix(string(path), PromptPrefix)
|
prompt = strings.HasPrefix(string(path), DbusPromptPrefix)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// connIsValid returns a boolean if the dbus.conn named conn is active.
|
||||||
|
func connIsValid(conn *dbus.Conn) (ok bool, err error) {
|
||||||
|
|
||||||
|
// dbus.Conn.Names() will ALWAYS return a []string with at least ONE element.
|
||||||
|
if conn == nil || (conn.Names() == nil || len(conn.Names()) < 1) {
|
||||||
|
err = ErrNoDbusConn
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = true
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
pathIsValid implements path checking for valid Dbus paths. Currently it only checks to make sure path is not a blank string.
|
||||||
|
The path argument can be either a string or dbus.ObjectPath.
|
||||||
|
*/
|
||||||
|
func pathIsValid(path interface{}) (ok bool, err error) {
|
||||||
|
|
||||||
|
var realPath string
|
||||||
|
|
||||||
|
switch p := path.(type) {
|
||||||
|
case dbus.ObjectPath:
|
||||||
|
if !p.IsValid() {
|
||||||
|
err = ErrBadDbusPath
|
||||||
|
return
|
||||||
|
}
|
||||||
|
realPath = string(p)
|
||||||
|
case string:
|
||||||
|
realPath = p
|
||||||
|
default:
|
||||||
|
err = ErrBadDbusPath
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(realPath) == "" {
|
||||||
|
err = ErrBadDbusPath
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = true
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
validConnPath condenses the checks for connIsValid and pathIsValid into one func due to how frequently this check is done.
|
||||||
|
err is a MultiError, which can be treated as an error.error. (See https://pkg.go.dev/builtin#error)
|
||||||
|
*/
|
||||||
|
func validConnPath(conn *dbus.Conn, path interface{}) (cr *ConnPathCheckResult, err error) {
|
||||||
|
|
||||||
|
var connErr error
|
||||||
|
var pathErr error
|
||||||
|
|
||||||
|
cr = new(ConnPathCheckResult)
|
||||||
|
|
||||||
|
cr.ConnOK, connErr = connIsValid(conn)
|
||||||
|
cr.PathOK, pathErr = pathIsValid(path)
|
||||||
|
|
||||||
|
err = NewErrors(connErr, pathErr)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
pathsFromProp returns a slice of dbus.ObjectPath (paths) from a dbus.Variant (prop).
|
||||||
|
If prop cannot typeswitch to paths, an ErrInvalidProperty will be raised.
|
||||||
|
*/
|
||||||
|
func pathsFromProp(prop dbus.Variant) (paths []dbus.ObjectPath, err error) {
|
||||||
|
|
||||||
|
switch v := prop.Value().(type) {
|
||||||
|
case []dbus.ObjectPath:
|
||||||
|
paths = v
|
||||||
|
default:
|
||||||
|
err = ErrInvalidProperty
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
pathsFromPath returns a slice of dbus.ObjectPath based on an object given by path using the dbus.Conn specified by conn.
|
||||||
|
Internally it uses pathsFromProp.
|
||||||
|
*/
|
||||||
|
func pathsFromPath(bus dbus.BusObject, path string) (paths []dbus.ObjectPath, err error) {
|
||||||
|
|
||||||
|
var v dbus.Variant
|
||||||
|
|
||||||
|
if v, err = bus.GetProperty(path); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if paths, err = pathsFromProp(v); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
5
go.mod
5
go.mod
@@ -2,4 +2,7 @@ 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
|
||||||
|
github.com/google/uuid v1.3.0
|
||||||
|
)
|
||||||
|
|||||||
6
go.sum
6
go.sum
@@ -1,2 +1,4 @@
|
|||||||
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=
|
||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
|||||||
251
item_funcs.go
251
item_funcs.go
@@ -1,25 +1,103 @@
|
|||||||
package libsecret
|
package gosecret
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`github.com/godbus/dbus`
|
`strconv`
|
||||||
|
`strings`
|
||||||
|
`time`
|
||||||
|
|
||||||
|
`github.com/godbus/dbus/v5`
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewItem returns a pointer to a new Item based on a Dbus connection and a Dbus path.
|
// NewItem returns a pointer to an Item based on Collection and a Dbus path.
|
||||||
func NewItem(conn *dbus.Conn, path dbus.ObjectPath) (item *Item) {
|
func NewItem(collection *Collection, path dbus.ObjectPath) (item *Item, err error) {
|
||||||
|
|
||||||
|
var splitPath []string
|
||||||
|
|
||||||
|
if collection == nil {
|
||||||
|
err = ErrNoDbusConn
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = validConnPath(collection.Conn, path); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
item = &Item{
|
item = &Item{
|
||||||
Conn: conn,
|
DbusObject: &DbusObject{
|
||||||
Dbus: conn.Object(DBusServiceName, path),
|
Conn: collection.Conn,
|
||||||
|
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 returns the Item's attributes from Dbus.
|
||||||
|
func (i *Item) Attributes() (attrs map[string]string, err error) {
|
||||||
|
|
||||||
|
var variant dbus.Variant
|
||||||
|
|
||||||
|
if variant, err = i.Dbus.GetProperty(DbusItemAttributes); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs = variant.Value().(map[string]string)
|
||||||
|
|
||||||
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path returns the path of the underlying Dbus connection.
|
// GetSecret returns the Secret in an Item using a Session.
|
||||||
func (i Item) Path() (path dbus.ObjectPath) {
|
func (i *Item) GetSecret(session *Session) (secret *Secret, err error) {
|
||||||
|
|
||||||
// Remove this method in V1. It's bloat since we now have an exported Dbus.
|
if session == nil {
|
||||||
path = i.Dbus.Path()
|
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
|
||||||
}
|
}
|
||||||
@@ -29,7 +107,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,12 +116,112 @@ 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's attributes 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relabel modifies the Item's label in Dbus.
|
||||||
|
func (i *Item) Relabel(newLabel string) (err error) {
|
||||||
|
|
||||||
|
var variant dbus.Variant = dbus.MakeVariant(newLabel)
|
||||||
|
|
||||||
|
if err = i.Dbus.SetProperty(DbusItemLabel, variant); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplaceAttributes replaces the Item's attributes in Dbus.
|
||||||
|
func (i *Item) ReplaceAttributes(newAttrs map[string]string) (err error) {
|
||||||
|
|
||||||
|
var props dbus.Variant
|
||||||
|
|
||||||
|
props = dbus.MakeVariant(newAttrs)
|
||||||
|
|
||||||
|
if err = i.Dbus.SetProperty(DbusItemAttributes, props); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
itemType = variant.Value().(string)
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
@@ -53,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
|
||||||
}
|
}
|
||||||
|
|||||||
150
item_funcs_test.go
Normal file
150
item_funcs_test.go
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
package gosecret
|
||||||
|
|
||||||
|
import (
|
||||||
|
`reflect`
|
||||||
|
`testing`
|
||||||
|
)
|
||||||
|
|
||||||
|
// Some functions are covered in the Service tests and Collection tests.
|
||||||
|
|
||||||
|
/*
|
||||||
|
TestItem tests all remaining Item funcs (see Service and Collection funcs for the other tests.
|
||||||
|
|
||||||
|
*/
|
||||||
|
func TestItem(t *testing.T) {
|
||||||
|
|
||||||
|
var svc *Service
|
||||||
|
var collection *Collection
|
||||||
|
var item *Item
|
||||||
|
var secret *Secret
|
||||||
|
var newItemLabel string
|
||||||
|
var testLabel string
|
||||||
|
var attrs map[string]string
|
||||||
|
var modAttrs map[string]string
|
||||||
|
var newAttrs map[string]string
|
||||||
|
var newAttrsGnome map[string]string
|
||||||
|
var typeString string
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Setup.
|
||||||
|
if svc, err = NewService(); err != nil {
|
||||||
|
t.Fatalf("NewService failed: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if collection, err = svc.CreateCollection(collectionName.String()); err != nil {
|
||||||
|
t.Errorf("could not create collection '%v': %v", collectionName.String(), err.Error())
|
||||||
|
if err = svc.Close(); err != nil {
|
||||||
|
t.Fatalf("could not close Service.Session: %v", err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
t.Logf("created collection '%v' at path '%v' successfully", collectionName.String(), string(collection.Dbus.Path()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an Item/Secret.
|
||||||
|
secret = NewSecret(svc.Session, []byte{}, []byte(testSecretContent), "text/plain")
|
||||||
|
|
||||||
|
if item, err = collection.CreateItem(testItemLabel, itemAttrs, secret, true); err != nil {
|
||||||
|
t.Errorf("could not create item %v in collection '%v': %v", testItemLabel, collectionName.String(), err.Error())
|
||||||
|
if err = svc.Close(); err != nil {
|
||||||
|
t.Fatalf("could not close Service.Session: %v", err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch attributes
|
||||||
|
if attrs, err = item.Attributes(); err != nil {
|
||||||
|
t.Errorf("failed to fetch attributes for item %v in collection '%v': %v", testItemLabel, collectionName.String(), err.Error())
|
||||||
|
} else {
|
||||||
|
t.Logf(
|
||||||
|
"Fetch result; original attributes: %#v, fetched attributes: %#v for item '%v' in '%v'",
|
||||||
|
itemAttrs, attrs, testItemLabel, collectionName.String(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
newAttrs = map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"bar": "baz",
|
||||||
|
"baz": "quux",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace attributes.
|
||||||
|
if err = item.ReplaceAttributes(newAttrs); err != nil {
|
||||||
|
t.Errorf("could not replace attributes for item '%v' in collection '%v': %v", testItemLabel, collectionName.String(), err.Error())
|
||||||
|
} else {
|
||||||
|
// Modify attributes.
|
||||||
|
// "flat" modification.
|
||||||
|
modAttrs = map[string]string{
|
||||||
|
"foo": "quux",
|
||||||
|
}
|
||||||
|
if err = item.ModifyAttributes(modAttrs); err != nil {
|
||||||
|
t.Errorf(
|
||||||
|
"could not modify attributes for item '%v' in collection '%v' (%#v => %#v): %v",
|
||||||
|
testItemLabel, collectionName.String(), newAttrs, modAttrs, err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// "delete" modification.
|
||||||
|
newAttrs = map[string]string{
|
||||||
|
"foo": "quux",
|
||||||
|
"bar": "baz",
|
||||||
|
}
|
||||||
|
newAttrsGnome = make(map[string]string, 0)
|
||||||
|
for k, v := range newAttrs {
|
||||||
|
newAttrsGnome[k] = v
|
||||||
|
}
|
||||||
|
// Added via SecretService automatically? Seahorse? It appears sometimes and others it does not. Cause is unknown.
|
||||||
|
newAttrsGnome["xdg:schema"] = DbusDefaultItemType
|
||||||
|
modAttrs = map[string]string{
|
||||||
|
"baz": ExplicitAttrEmptyValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = item.ModifyAttributes(modAttrs); err != nil {
|
||||||
|
t.Errorf(
|
||||||
|
"could not modify (with deletion) attributes for item '%v' in collection '%v' (%#v => %#v): %v",
|
||||||
|
testItemLabel, collectionName.String(), newAttrs, modAttrs, err.Error(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
if attrs, err = item.Attributes(); err != nil {
|
||||||
|
t.Errorf("failed to fetch attributes for item %v in collection '%v': %v", testItemLabel, collectionName.String(), err.Error())
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(attrs, newAttrs) && !reflect.DeepEqual(attrs, newAttrsGnome) {
|
||||||
|
t.Errorf("newly-modified attributes (%#v) do not match expected attributes (%#v)", attrs, newAttrs)
|
||||||
|
} else {
|
||||||
|
t.Logf("modified attributes (%#v) match expected attributes (%#v)", attrs, newAttrs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Item.Relabel
|
||||||
|
newItemLabel = testItemLabel + "_RELABELED"
|
||||||
|
if err = item.Relabel(newItemLabel); err != nil {
|
||||||
|
t.Errorf("failed to relabel item '%v' to '%v': %v", testItemLabel, newItemLabel, err.Error())
|
||||||
|
}
|
||||||
|
if testLabel, err = item.Label(); err != nil {
|
||||||
|
t.Errorf("failed to fetch label for '%v': %v", string(item.Dbus.Path()), err.Error())
|
||||||
|
}
|
||||||
|
if newItemLabel != testLabel {
|
||||||
|
t.Errorf("new item label post-relabeling ('%v') does not match explicitly set label ('%v')", testLabel, newItemLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// And Item.Type.
|
||||||
|
if typeString, err = item.Type(); err != nil {
|
||||||
|
t.Errorf("failed to get Item.Type for '%v': %v", string(item.Dbus.Path()), err.Error())
|
||||||
|
} else {
|
||||||
|
if typeString != DbusDefaultItemType {
|
||||||
|
t.Errorf("Item.Type mismatch for '%v': '%v' (should be '%v')", string(item.Dbus.Path()), typeString, DbusDefaultItemType)
|
||||||
|
}
|
||||||
|
t.Logf("item type for '%v': %v", string(item.Dbus.Path()), typeString)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Teardown.
|
||||||
|
if err = item.Delete(); err != nil {
|
||||||
|
t.Errorf("failed to delete item '%v': %v", string(item.Dbus.Path()), err.Error())
|
||||||
|
}
|
||||||
|
if err = collection.Delete(); err != nil {
|
||||||
|
t.Errorf("failed to delete collection '%v': %v", collectionName.String(), err.Error())
|
||||||
|
}
|
||||||
|
if err = svc.Close(); err != nil {
|
||||||
|
t.Errorf("could not close Service.Session: %v", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
57
multierr_funcs.go
Normal file
57
multierr_funcs.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package gosecret
|
||||||
|
|
||||||
|
import (
|
||||||
|
`fmt`
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
NewErrors returns a new MultiError based on a slice of error.Error (errs).
|
||||||
|
Any nil errors are trimmed. If there are no actual errors after trimming, err will be nil.
|
||||||
|
*/
|
||||||
|
func NewErrors(errs ...error) (err error) {
|
||||||
|
|
||||||
|
if errs == nil || len(errs) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var realErrs []error = make([]error, 0)
|
||||||
|
|
||||||
|
for _, e := range errs {
|
||||||
|
if e == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
realErrs = append(realErrs, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(realErrs) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = &MultiError{
|
||||||
|
Errors: realErrs,
|
||||||
|
ErrorSep: "\n",
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *MultiError) Error() (errStr string) {
|
||||||
|
|
||||||
|
var numErrs int
|
||||||
|
|
||||||
|
if e == nil || len(e.Errors) == 0 {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
numErrs = len(e.Errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, err := range e.Errors {
|
||||||
|
if (idx + 1) < numErrs {
|
||||||
|
errStr += fmt.Sprintf(err.Error(), e.ErrorSep)
|
||||||
|
} else {
|
||||||
|
errStr += err.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -1,35 +1,30 @@
|
|||||||
package libsecret
|
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{
|
||||||
Conn: conn,
|
DbusObject: &DbusObject{
|
||||||
Dbus: conn.Object(DBusServiceName, path),
|
Conn: conn,
|
||||||
|
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) {
|
||||||
|
|
||||||
var c chan *dbus.Signal
|
var c chan *dbus.Signal
|
||||||
var result *dbus.Signal
|
var result *dbus.Signal
|
||||||
|
|
||||||
|
promptValue = new(dbus.Variant)
|
||||||
|
|
||||||
// Prompts are asynchronous; we connect to the signal and block with a channel until we get a response.
|
// Prompts are asynchronous; we connect to the signal and block with a channel until we get a response.
|
||||||
c = make(chan *dbus.Signal, 10)
|
c = make(chan *dbus.Signal, 10)
|
||||||
defer close(c)
|
defer close(c)
|
||||||
@@ -37,12 +32,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, "GoSecret.Prompt", // 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package libsecret
|
package gosecret
|
||||||
|
|
||||||
// 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.
|
// 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) {
|
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
|
||||||
|
}
|
||||||
427
service_funcs.go
427
service_funcs.go
@@ -1,74 +1,82 @@
|
|||||||
package libsecret
|
package gosecret
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`github.com/godbus/dbus`
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/godbus/dbus/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewService returns a pointer to a new Service.
|
// NewService returns a pointer to a new Service connection.
|
||||||
func NewService() (service *Service, err error) {
|
func NewService() (service *Service, err error) {
|
||||||
|
|
||||||
service = &Service{
|
var svc Service = Service{
|
||||||
Conn: nil,
|
DbusObject: &DbusObject{
|
||||||
Dbus: nil,
|
Conn: nil,
|
||||||
|
Dbus: nil,
|
||||||
|
},
|
||||||
|
Session: nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
if service.Conn, err = dbus.SessionBus(); err != nil {
|
if svc.Conn, err = dbus.SessionBus(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
service.Dbus = service.Conn.Object(DBusServiceName, dbus.ObjectPath(DBusPath))
|
svc.Dbus = svc.Conn.Object(DbusService, dbus.ObjectPath(DbusPath))
|
||||||
|
|
||||||
return
|
if svc.Session, err = svc.GetSession(); err != nil {
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
session = NewSession(s.Conn, path)
|
service = &svc
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collections returns a slice of Collection keyrings accessible to this Service.
|
// Close cleanly closes a Service and all its underlying connections (e.g. Service.Session).
|
||||||
func (s *Service) Collections() (collections []Collection, err error) {
|
func (s *Service) Close() (err error) {
|
||||||
|
|
||||||
|
err = s.Session.Close()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collections returns a slice of Collection items accessible to this Service.
|
||||||
|
func (s *Service) Collections() (collections []*Collection, err error) {
|
||||||
|
|
||||||
var paths []dbus.ObjectPath
|
var paths []dbus.ObjectPath
|
||||||
var variant dbus.Variant
|
var variant dbus.Variant
|
||||||
|
var coll *Collection
|
||||||
|
var errs []error = make([]error, 0)
|
||||||
|
|
||||||
if variant, err = s.Dbus.GetProperty("org.freedesktop.Secret.Service.Collections"); err != nil {
|
if variant, err = s.Dbus.GetProperty(DbusServiceCollections); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
paths = variant.Value().([]dbus.ObjectPath)
|
paths = variant.Value().([]dbus.ObjectPath)
|
||||||
|
|
||||||
collections = make([]Collection, len(paths))
|
collections = make([]*Collection, len(paths))
|
||||||
|
|
||||||
for idx, path := range paths {
|
for idx, path := range paths {
|
||||||
collections[idx] = *NewCollection(s.Conn, path)
|
if coll, err = NewCollection(s, path); err != nil {
|
||||||
|
// return
|
||||||
|
errs = append(errs, err)
|
||||||
|
err = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
collections[idx] = coll
|
||||||
}
|
}
|
||||||
|
err = NewErrors(err)
|
||||||
|
|
||||||
return
|
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) {
|
CreateAliasedCollection creates a new Collection (keyring) via a Service with the name specified by label,
|
||||||
|
aliased to the name specified by alias, and returns the new Collection.
|
||||||
|
*/
|
||||||
|
func (s *Service) CreateAliasedCollection(label, alias string) (collection *Collection, err error) {
|
||||||
|
|
||||||
var variant *dbus.Variant
|
var variant *dbus.Variant
|
||||||
var path dbus.ObjectPath
|
var path dbus.ObjectPath
|
||||||
@@ -76,16 +84,17 @@ func (s *Service) CreateCollection(label string) (collection *Collection, err er
|
|||||||
var prompt *Prompt
|
var prompt *Prompt
|
||||||
var props map[string]dbus.Variant = make(map[string]dbus.Variant)
|
var props map[string]dbus.Variant = make(map[string]dbus.Variant)
|
||||||
|
|
||||||
props["org.freedesktop.Secret.Collection.Label"] = dbus.MakeVariant(label)
|
props[DbusCollectionLabel] = dbus.MakeVariant(label)
|
||||||
|
|
||||||
if err = s.Dbus.Call("org.freedesktop.Secret.Service.CreateCollection", 0, props, "").Store(&path, &promptPath); err != nil {
|
if err = s.Dbus.Call(
|
||||||
|
DbusServiceCreateCollection, 0, props, alias,
|
||||||
|
).Store(&path, &promptPath); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isPrompt(promptPath) {
|
if isPrompt(promptPath) {
|
||||||
|
|
||||||
prompt = NewPrompt(s.Conn, promptPath)
|
prompt = NewPrompt(s.Conn, promptPath)
|
||||||
|
|
||||||
if variant, err = prompt.Prompt(); err != nil {
|
if variant, err = prompt.Prompt(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -93,44 +102,149 @@ func (s *Service) CreateCollection(label string) (collection *Collection, err er
|
|||||||
path = variant.Value().(dbus.ObjectPath)
|
path = variant.Value().(dbus.ObjectPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
collection = NewCollection(s.Conn, path)
|
collection, err = NewCollection(s, path)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unlock unlocks a Locked Service.
|
/*
|
||||||
func (s *Service) Unlock(object DBusObject) (err error) {
|
CreateCollection creates a new Collection (keyring) via a Service with the name specified by label and returns the new Collection.
|
||||||
|
It is a *very* thin wrapper around Service.CreateAliasedCollection, but with a blank alias.
|
||||||
|
*/
|
||||||
|
func (s *Service) CreateCollection(label string) (collection *Collection, err error) {
|
||||||
|
|
||||||
var unlocked []dbus.ObjectPath
|
collection, err = s.CreateAliasedCollection(label, "")
|
||||||
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
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
GetCollection returns a single Collection based on the name (name can also be an alias).
|
||||||
|
It's a helper function that avoids needing to make multiple calls in user code.
|
||||||
|
*/
|
||||||
|
func (s *Service) GetCollection(name string) (c *Collection, err error) {
|
||||||
|
|
||||||
|
var errs []error
|
||||||
|
var colls []*Collection
|
||||||
|
var collLabel string
|
||||||
|
|
||||||
|
// First check for an alias.
|
||||||
|
if c, err = s.ReadAlias(name); err != nil && err != ErrDoesNotExist {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if c != nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
c = nil
|
||||||
|
}
|
||||||
|
|
||||||
if isPrompt(promptPath) {
|
// We didn't get it by alias, so let's try by name...
|
||||||
|
if colls, err = s.Collections(); err != nil {
|
||||||
prompt = NewPrompt(s.Conn, promptPath)
|
return
|
||||||
|
}
|
||||||
if _, err = prompt.Prompt(); err != nil {
|
for _, i := range colls {
|
||||||
|
if i.name == name {
|
||||||
|
c = i
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Still nothing? Try by label.
|
||||||
|
for _, i := range colls {
|
||||||
|
if collLabel, err = i.Label(); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
err = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if collLabel == name {
|
||||||
|
c = i
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Couldn't find it by the given name.
|
||||||
|
if errs != nil || len(errs) > 0 {
|
||||||
|
errs = append([]error{ErrDoesNotExist}, errs...)
|
||||||
|
err = NewErrors(errs...)
|
||||||
|
} else {
|
||||||
|
err = ErrDoesNotExist
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock locks an Unlocked Service.
|
/*
|
||||||
func (s *Service) Lock(object DBusObject) (err error) {
|
GetSecrets allows you to fetch values (Secret) from multiple Item object paths using this Service's Session.
|
||||||
|
An ErrMissingPaths will be returned for err if itemPaths is nil or empty.
|
||||||
|
The returned secrets is a map with itemPaths as the keys and their corresponding Secret as the value.
|
||||||
|
*/
|
||||||
|
func (s *Service) GetSecrets(itemPaths ...dbus.ObjectPath) (secrets map[dbus.ObjectPath]*Secret, err error) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
Results are in the form of a map with the value consisting of:
|
||||||
|
[]interface {}{
|
||||||
|
"/org/freedesktop/secrets/session/sNNN", // 0, session path
|
||||||
|
[]uint8{}, // 1, "params"
|
||||||
|
[]uint8{0x0}, // 2, value
|
||||||
|
"text/plain", // 3, content type
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
var results map[dbus.ObjectPath][]interface{}
|
||||||
|
|
||||||
|
if itemPaths == nil || len(itemPaths) == 0 {
|
||||||
|
err = ErrMissingPaths
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
secrets = make(map[dbus.ObjectPath]*Secret, len(itemPaths))
|
||||||
|
results = make(map[dbus.ObjectPath][]interface{}, len(itemPaths))
|
||||||
|
|
||||||
|
// TODO: trigger a Service.Unlock for any locked items?
|
||||||
|
if err = s.Dbus.Call(
|
||||||
|
DbusServiceGetSecrets, 0, itemPaths, s.Session.Dbus.Path(),
|
||||||
|
).Store(&results); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for p, r := range results {
|
||||||
|
secrets[p] = NewSecret(
|
||||||
|
s.Session, r[1].([]byte), r[2].([]byte), r[3].(string),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
You can usually get objectPath for the object(s) to unlock via <object>.Dbus.Path().
|
||||||
|
If objectPaths is nil or empty, the Service's own path will be used.
|
||||||
|
*/
|
||||||
|
func (s *Service) Lock(objectPaths ...dbus.ObjectPath) (err error) {
|
||||||
|
|
||||||
|
// We only use these as destinations.
|
||||||
var locked []dbus.ObjectPath
|
var locked []dbus.ObjectPath
|
||||||
var prompt *Prompt
|
var prompt *Prompt
|
||||||
var promptPath dbus.ObjectPath
|
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 {
|
if objectPaths == nil || len(objectPaths) == 0 {
|
||||||
|
objectPaths = []dbus.ObjectPath{s.Dbus.Path()}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = s.Dbus.Call(
|
||||||
|
DbusServiceLock, 0, objectPaths,
|
||||||
|
).Store(&locked, &promptPath); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,3 +259,202 @@ func (s *Service) Lock(object DBusObject) (err error) {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
OpenSession returns a pointer to a Session from the Service.
|
||||||
|
It's a convenience function around NewSession.
|
||||||
|
*/
|
||||||
|
func (s *Service) OpenSession(algo, input string) (session *Session, output dbus.Variant, err error) {
|
||||||
|
|
||||||
|
var path dbus.ObjectPath
|
||||||
|
var inputVariant dbus.Variant
|
||||||
|
|
||||||
|
if strings.TrimSpace(algo) == "" {
|
||||||
|
algo = "plain"
|
||||||
|
}
|
||||||
|
|
||||||
|
inputVariant = dbus.MakeVariant(input)
|
||||||
|
|
||||||
|
// 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(
|
||||||
|
DbusServiceOpenSession, 0, algo, inputVariant,
|
||||||
|
).Store(&output, &path); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err = NewSession(s, path)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
ReadAlias 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) 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 {
|
||||||
|
err = ErrMissingAttrs
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.Dbus.Call(
|
||||||
|
DbusServiceSearchItems, 0, attributes,
|
||||||
|
).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
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
SetAlias sets an alias for an existing Collection.
|
||||||
|
To remove an alias, set objectPath to dbus.ObjectPath("/").
|
||||||
|
*/
|
||||||
|
func (s *Service) SetAlias(alias string, objectPath dbus.ObjectPath) (err error) {
|
||||||
|
|
||||||
|
var c *dbus.Call
|
||||||
|
|
||||||
|
c = s.Dbus.Call(
|
||||||
|
DbusServiceSetAlias, 0, alias, objectPath,
|
||||||
|
)
|
||||||
|
|
||||||
|
err = c.Err
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Unlock unlocks a Locked Service, Collection, etc.
|
||||||
|
You can usually get objectPath for the object(s) to unlock via <object>.Dbus.Path().
|
||||||
|
If objectPaths is nil or empty, the Service's own path will be used.
|
||||||
|
*/
|
||||||
|
func (s *Service) Unlock(objectPaths ...dbus.ObjectPath) (err error) {
|
||||||
|
|
||||||
|
var unlocked []dbus.ObjectPath
|
||||||
|
var prompt *Prompt
|
||||||
|
var resultPath dbus.ObjectPath
|
||||||
|
|
||||||
|
if objectPaths == nil || len(objectPaths) == 0 {
|
||||||
|
objectPaths = []dbus.ObjectPath{s.Dbus.Path()}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = s.Dbus.Call(
|
||||||
|
DbusServiceUnlock, 0, objectPaths,
|
||||||
|
).Store(&unlocked, &resultPath); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if isPrompt(resultPath) {
|
||||||
|
|
||||||
|
prompt = NewPrompt(s.Conn, resultPath)
|
||||||
|
|
||||||
|
if _, err = prompt.Prompt(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
442
service_funcs_test.go
Normal file
442
service_funcs_test.go
Normal file
@@ -0,0 +1,442 @@
|
|||||||
|
package gosecret
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
`time`
|
||||||
|
|
||||||
|
`github.com/godbus/dbus/v5`
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
TestNewService tests the following internal functions/methods via nested calls:
|
||||||
|
|
||||||
|
NewService
|
||||||
|
Service.GetSession
|
||||||
|
Service.OpenSession
|
||||||
|
NewSession
|
||||||
|
validConnPath
|
||||||
|
connIsValid
|
||||||
|
pathIsValid
|
||||||
|
Service.Close
|
||||||
|
Session.Close
|
||||||
|
|
||||||
|
*/
|
||||||
|
func TestNewService(t *testing.T) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var svc *Service
|
||||||
|
|
||||||
|
if svc, err = NewService(); err != nil {
|
||||||
|
t.Fatalf("could not get new Service via NewService: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = svc.Close(); err != nil {
|
||||||
|
t.Errorf("could not close Service.Session: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TestService_Collections tests the following internal functions/methods via nested calls:
|
||||||
|
|
||||||
|
(all calls in TestNewService)
|
||||||
|
Service.Collections
|
||||||
|
NewCollection
|
||||||
|
Collection.Modified
|
||||||
|
NewErrors
|
||||||
|
Collection.Created
|
||||||
|
*/
|
||||||
|
func TestService_Collections(t *testing.T) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var svc *Service
|
||||||
|
var colls []*Collection
|
||||||
|
var collLabel string
|
||||||
|
var created time.Time
|
||||||
|
var modified time.Time
|
||||||
|
|
||||||
|
if svc, err = NewService(); err != nil {
|
||||||
|
t.Fatalf("could not get new Service via NewService: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if colls, err = svc.Collections(); err != nil {
|
||||||
|
t.Errorf("could not get Service.Collections: %v", err.Error())
|
||||||
|
} else {
|
||||||
|
t.Logf("found %v collections via Service.Collections", len(colls))
|
||||||
|
|
||||||
|
for idx, c := range colls {
|
||||||
|
if collLabel, err = c.Label(); err != nil {
|
||||||
|
t.Errorf(
|
||||||
|
"failed to get label for collection '%v': %v",
|
||||||
|
string(c.Dbus.Path()), err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if created, err = c.Created(); err != nil {
|
||||||
|
t.Errorf(
|
||||||
|
"failed to get created time for collection '%v': %v",
|
||||||
|
string(c.Dbus.Path()), err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if modified, _, err = c.Modified(); err != nil {
|
||||||
|
t.Errorf(
|
||||||
|
"failed to get modified time for collection '%v': %v",
|
||||||
|
string(c.Dbus.Path()), err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
t.Logf(
|
||||||
|
"collection #%v (name '%v', label '%v'): created %v, last modified %v",
|
||||||
|
idx, c.PathName(), collLabel, created, modified,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = svc.Close(); err != nil {
|
||||||
|
t.Errorf("could not close Service.Session: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TestService_CreateAliasedCollection tests the following internal functions/methods via nested calls:
|
||||||
|
|
||||||
|
(all calls in TestNewService)
|
||||||
|
Service.CreateAliasedCollection
|
||||||
|
NewCollection
|
||||||
|
Collection.Modified
|
||||||
|
Collection.Delete
|
||||||
|
Service.SetAlias
|
||||||
|
|
||||||
|
(By extension, Service.CreateCollection is also tested as it's a very thin wrapper
|
||||||
|
around Service.CreateAliasedCollection).
|
||||||
|
*/
|
||||||
|
/* DISABLED. Currently (as of 0.20.4), *only* the alias "default" is allowed. TODO: revisit in future?
|
||||||
|
func TestService_CreateAliasedCollection(t *testing.T) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var svc *Service
|
||||||
|
var collection *Collection
|
||||||
|
|
||||||
|
if svc, err = NewService(); err != nil {
|
||||||
|
t.Fatalf("could not get new Service via NewService: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if collection, err = svc.CreateAliasedCollection(collectionName.String(), collectionAlias.String()); err != nil {
|
||||||
|
t.Errorf(
|
||||||
|
"error when creating aliased collection '%v' with alias '%v': %v",
|
||||||
|
collectionName.String(), collectionAlias.String(), err.Error(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
if err = svc.SetAlias(testAlias, collection.Dbus.Path()); err != nil {
|
||||||
|
t.Errorf(
|
||||||
|
"error when setting an alias '%v' for aliased collection '%v' (original alias '%v')",
|
||||||
|
testAlias, collectionName.String(), collectionAlias.String(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if err = collection.Delete(); err != nil {
|
||||||
|
t.Errorf(
|
||||||
|
"error when deleting aliased collection '%v' with alias '%v': %v",
|
||||||
|
collectionName.String(), collectionAlias.String(), err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
t.Logf("created collection '%v' at path '%v' successfully", collectionName.String(), string(collection.Dbus.Path()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = svc.Close(); err != nil {
|
||||||
|
t.Errorf("could not close Service.Session: %v", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
TestService_GetCollection tests the following internal functions/methods via nested calls:
|
||||||
|
|
||||||
|
(all calls in TestService_CreateAliasedCollection)
|
||||||
|
(all calls in TestService_Collections)
|
||||||
|
|
||||||
|
|
||||||
|
NewErrors
|
||||||
|
Collection.Created
|
||||||
|
Service.GetCollection
|
||||||
|
NewCollection
|
||||||
|
Collection.Modified
|
||||||
|
Service.ReadAlias
|
||||||
|
|
||||||
|
The default collection (login) is fetched instead of creating one as this collection should exist,
|
||||||
|
and thus this function tests fetching existing collections instead of newly-created ones.
|
||||||
|
*/
|
||||||
|
func TestService_GetCollection(t *testing.T) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var svc *Service
|
||||||
|
var coll *Collection
|
||||||
|
|
||||||
|
if svc, err = NewService(); err != nil {
|
||||||
|
t.Fatalf("could not get new Service via NewService: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if coll, err = svc.GetCollection(defaultCollection); err != nil {
|
||||||
|
t.Errorf("failed to get collection '%v' via Service.GetCollection: %v", defaultCollection, err.Error())
|
||||||
|
} else {
|
||||||
|
t.Logf("got collection '%v' via reference '%v'", coll.name, defaultCollection)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = svc.Close(); err != nil {
|
||||||
|
t.Errorf("could not close Service.Session: %v", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TestService_Secrets tests the following internal functions/methods via nested calls:
|
||||||
|
|
||||||
|
(all calls in TestNewService)
|
||||||
|
(all calls in TestService_CreateAliasedCollection)
|
||||||
|
Service.CreateCollection
|
||||||
|
Service.SearchItems
|
||||||
|
NewItem
|
||||||
|
NewErrors
|
||||||
|
Service.GetSecrets
|
||||||
|
NewSecret
|
||||||
|
Collection.CreateItem
|
||||||
|
Item.Label
|
||||||
|
Item.Delete
|
||||||
|
|
||||||
|
*/
|
||||||
|
func TestService_Secrets(t *testing.T) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var svc *Service
|
||||||
|
var collection *Collection
|
||||||
|
var itemResultsUnlocked []*Item
|
||||||
|
var itemResultsLocked []*Item
|
||||||
|
var itemName string
|
||||||
|
var resultItemName string
|
||||||
|
var testItem *Item
|
||||||
|
var testSecret *Secret
|
||||||
|
var secretsResult map[dbus.ObjectPath]*Secret
|
||||||
|
var itemPaths []dbus.ObjectPath
|
||||||
|
var created time.Time
|
||||||
|
var modified time.Time
|
||||||
|
var newModified time.Time
|
||||||
|
var wasModified bool
|
||||||
|
var isModified bool
|
||||||
|
|
||||||
|
if svc, err = NewService(); err != nil {
|
||||||
|
t.Fatalf("could not get new Service via NewService: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if collection, err = svc.CreateCollection(collectionName.String()); err != nil {
|
||||||
|
t.Errorf("could not create collection '%v': %v", collectionName.String(), err.Error())
|
||||||
|
if err = svc.Close(); err != nil {
|
||||||
|
t.Fatalf("could not close Service.Session: %v", err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
t.Logf("created collection '%v' at path '%v' successfully", collectionName.String(), string(collection.Dbus.Path()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if created, err = collection.Created(); err != nil {
|
||||||
|
t.Errorf(
|
||||||
|
"failed to get created time for '%v': %v",
|
||||||
|
collectionName.String(), err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if modified, wasModified, err = collection.Modified(); err != nil {
|
||||||
|
t.Errorf(
|
||||||
|
"failed to get modified time for '%v': %v",
|
||||||
|
collectionName.String(), err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
t.Logf(
|
||||||
|
"%v: collection '%v': created at %v, last modified at %v; was changed: %v",
|
||||||
|
time.Now(), collectionName.String(), created, modified, wasModified,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create a secret
|
||||||
|
testSecret = NewSecret(svc.Session, nil, []byte(testSecretContent), "text/plain")
|
||||||
|
if testItem, err = collection.CreateItem(testItemLabel, itemAttrs, testSecret, true); err != nil {
|
||||||
|
t.Errorf("could not create Item in collection '%v': %v", collectionName.String(), err.Error())
|
||||||
|
if err = svc.Close(); err != nil {
|
||||||
|
t.Fatalf("could not close Service.Session: %v", err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if itemName, err = testItem.Label(); err != nil {
|
||||||
|
t.Errorf(
|
||||||
|
"could not get label for newly-created item '%v' in collection '%v': %v",
|
||||||
|
string(testItem.Dbus.Path()), collectionName.String(), err.Error(),
|
||||||
|
)
|
||||||
|
itemName = testItemLabel
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search items
|
||||||
|
if itemResultsUnlocked, itemResultsLocked, err = svc.SearchItems(itemAttrs); err != nil {
|
||||||
|
t.Errorf("did not find Item '%v' in collection '%v' SearchItems: %v", itemName, collectionName.String(), err.Error())
|
||||||
|
} else {
|
||||||
|
if len(itemResultsLocked) != 0 && itemResultsUnlocked != nil {
|
||||||
|
t.Errorf("at least one locked item in collection '%v'", collectionName.String())
|
||||||
|
}
|
||||||
|
if len(itemResultsUnlocked) != 1 {
|
||||||
|
t.Errorf("number of unlocked items in collection '%v' is not equal to 1", collectionName.String())
|
||||||
|
}
|
||||||
|
if resultItemName, err = itemResultsUnlocked[0].Label(); err != nil {
|
||||||
|
t.Errorf("cannot fetch test Item name from collection '%v' in SearchItems: %v", collectionName.String(), err.Error())
|
||||||
|
} else {
|
||||||
|
if resultItemName != itemName {
|
||||||
|
t.Errorf("seem to have fetched an improper Item from collection '%v' in SearchItems", collectionName.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch secrets
|
||||||
|
itemPaths = make([]dbus.ObjectPath, len(itemResultsUnlocked))
|
||||||
|
if len(itemResultsUnlocked) >= 1 {
|
||||||
|
itemPaths[0] = itemResultsUnlocked[0].Dbus.Path()
|
||||||
|
if secretsResult, err = svc.GetSecrets(itemPaths...); err != nil {
|
||||||
|
t.Errorf("failed to fetch Item path '%v' via Service.GetSecrets: %v", string(itemPaths[0]), err.Error())
|
||||||
|
} else if len(secretsResult) != 1 {
|
||||||
|
t.Errorf("received %v secrets from Service.GetSecrets instead of 1", len(secretsResult))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirm the modification information changed.
|
||||||
|
_ = newModified
|
||||||
|
_ = isModified
|
||||||
|
/* TODO: Disabled for now; it *seems* the collection modification time doesn't auto-update? See collection.setModify if not.
|
||||||
|
if newModified, isModified, err = collection.Modified(); err != nil {
|
||||||
|
t.Errorf(
|
||||||
|
"failed to get modified time for '%v': %v",
|
||||||
|
collectionName.String(), err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
t.Logf(
|
||||||
|
"%v: (post-change) collection '%v': last modified at %v; was changed: %v",
|
||||||
|
time.Now(), collectionName.String(), newModified, isModified,
|
||||||
|
)
|
||||||
|
if !isModified {
|
||||||
|
t.Errorf(
|
||||||
|
"modification tracking for collection '%v' failed; expected true but got false",
|
||||||
|
collectionName.String(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
t.Logf("(modification check passed)")
|
||||||
|
}
|
||||||
|
if !newModified.After(modified) {
|
||||||
|
t.Errorf(
|
||||||
|
"modification timestamp update for '%v' failed: old %v, new %v",
|
||||||
|
collectionName.String(), modified, newModified,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Delete the item and collection to clean up.
|
||||||
|
*Technically* the item is deleted if the collection is, but its path is held in memory if not
|
||||||
|
manually removed, leading to a "ghost" item.
|
||||||
|
*/
|
||||||
|
if err = testItem.Delete(); err != nil {
|
||||||
|
t.Errorf("could not delete test item '%v' in collection '%v': %v",
|
||||||
|
string(testItem.Dbus.Path()), collectionName.String(), err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if err = collection.Delete(); err != nil {
|
||||||
|
t.Errorf(
|
||||||
|
"error when deleting collection '%v' when testing Service: %v",
|
||||||
|
collectionName.String(), err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = svc.Close(); err != nil {
|
||||||
|
t.Errorf("could not close Service.Session: %v", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TestService_Locking tests the following internal functions/methods via nested calls:
|
||||||
|
|
||||||
|
(all calls in TestNewService)
|
||||||
|
(all calls in TestService_CreateAliasedCollection)
|
||||||
|
Service.Lock
|
||||||
|
Service.Unlock
|
||||||
|
Collection.Locked
|
||||||
|
|
||||||
|
*/
|
||||||
|
func TestService_Locking(t *testing.T) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var isLocked bool
|
||||||
|
var stateChangeLock bool
|
||||||
|
var svc *Service
|
||||||
|
var collection *Collection
|
||||||
|
|
||||||
|
if svc, err = NewService(); err != nil {
|
||||||
|
t.Fatalf("could not get new Service via NewService: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if collection, err = svc.CreateCollection(collectionName.String()); err != nil {
|
||||||
|
if err = svc.Close(); err != nil {
|
||||||
|
t.Errorf("could not close Service.Session: %v", err.Error())
|
||||||
|
}
|
||||||
|
t.Errorf("could not create collection '%v': %v", collectionName.String(), err.Error())
|
||||||
|
} else {
|
||||||
|
t.Logf("created collection '%v' at path '%v' successfully", collectionName.String(), string(collection.Dbus.Path()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if isLocked, err = collection.Locked(); err != nil {
|
||||||
|
t.Errorf("received error when checking collection '%v' lock status: %v", collectionName.String(), err.Error())
|
||||||
|
if err = collection.Delete(); err != nil {
|
||||||
|
t.Errorf(
|
||||||
|
"error when deleting collection '%v' when testing Service: %v",
|
||||||
|
collectionName.String(), err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if err = svc.Close(); err != nil {
|
||||||
|
t.Errorf("could not close Service.Session: %v", err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
t.Logf("collection '%v' original lock status: %v", collectionName.String(), isLocked)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change the state.
|
||||||
|
if isLocked {
|
||||||
|
if err = svc.Unlock(collection.Dbus.Path()); err != nil {
|
||||||
|
t.Errorf("could not unlock collection '%v': %v", collectionName.String(), err.Error())
|
||||||
|
}
|
||||||
|
if stateChangeLock, err = collection.Locked(); err != nil {
|
||||||
|
t.Errorf("received error when checking collection '%v' lock status: %v", collectionName.String(), err.Error())
|
||||||
|
}
|
||||||
|
if err = svc.Lock(collection.Dbus.Path()); err != nil {
|
||||||
|
t.Errorf("could not lock collection '%v': %v", collectionName.String(), err.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err = svc.Lock(collection.Dbus.Path()); err != nil {
|
||||||
|
t.Errorf("could not lock collection '%v': %v", collectionName.String(), err.Error())
|
||||||
|
}
|
||||||
|
if stateChangeLock, err = collection.Locked(); err != nil {
|
||||||
|
t.Errorf("received error when checking collection '%v' lock status: %v", collectionName.String(), err.Error())
|
||||||
|
}
|
||||||
|
if err = svc.Unlock(collection.Dbus.Path()); err != nil {
|
||||||
|
t.Errorf("could not unlock collection '%v': %v", collectionName.String(), err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if stateChangeLock != !isLocked {
|
||||||
|
t.Errorf(
|
||||||
|
"flipped lock state for collection '%v' (locked: %v) is not opposite of original lock state (locked: %v)",
|
||||||
|
collectionName.String(), stateChangeLock, isLocked,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the collection to clean up.
|
||||||
|
if err = collection.Delete(); err != nil {
|
||||||
|
t.Errorf(
|
||||||
|
"error when deleting collection '%v' when testing Service: %v",
|
||||||
|
collectionName.String(), err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = svc.Close(); err != nil {
|
||||||
|
t.Errorf("could not close Service.Session: %v", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +1,44 @@
|
|||||||
package libsecret
|
package gosecret
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`github.com/godbus/dbus`
|
"github.com/godbus/dbus/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewSession returns a pointer to a new Session based on a Dbus connection and a Dbus path.
|
// I'm still not 100% certain what Sessions are used for, aside from getting Secrets from Items.
|
||||||
func NewSession(conn *dbus.Conn, path dbus.ObjectPath) (session *Session) {
|
|
||||||
|
|
||||||
session = &Session{
|
/*
|
||||||
Conn: conn,
|
NewSession returns a pointer to a new Session based on a Service and a dbus.ObjectPath.
|
||||||
Dbus: conn.Object(DBusServiceName, path),
|
You will almost always want to use Service.GetSession or Service.OpenSession instead.
|
||||||
|
*/
|
||||||
|
func NewSession(service *Service, path dbus.ObjectPath) (session *Session, err error) {
|
||||||
|
|
||||||
|
if _, err = validConnPath(service.Conn, path); err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
var ssn Session = Session{
|
||||||
}
|
DbusObject: &DbusObject{
|
||||||
|
Conn: service.Conn,
|
||||||
|
},
|
||||||
|
service: service,
|
||||||
|
}
|
||||||
|
ssn.Dbus = ssn.Conn.Object(DbusInterfaceSession, path)
|
||||||
|
|
||||||
// Path returns the path of the underlying Dbus connection.
|
session = &ssn
|
||||||
func (s Session) Path() (path dbus.ObjectPath) {
|
|
||||||
|
return
|
||||||
// Remove this method in V1. It's bloat since we now have an exported Dbus.
|
}
|
||||||
path = s.Dbus.Path()
|
|
||||||
|
// Close cleanly closes a Session.
|
||||||
|
func (s *Session) Close() (err error) {
|
||||||
|
|
||||||
|
var c *dbus.Call
|
||||||
|
|
||||||
|
c = s.Dbus.Call(
|
||||||
|
DbusSessionClose, 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
_ = c
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
31
sserror_funcs.go
Normal file
31
sserror_funcs.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package gosecret
|
||||||
|
|
||||||
|
// This currently is not used.
|
||||||
|
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns the string format of the error; this is necessary to be considered a valid error interface.
|
||||||
|
func (e SecretServiceError) Error() (errStr string) {
|
||||||
|
|
||||||
|
errStr = e.ErrDesc
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
169
types.go
169
types.go
@@ -1,12 +1,83 @@
|
|||||||
package libsecret
|
package gosecret
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`github.com/godbus/dbus`
|
"time"
|
||||||
|
|
||||||
|
"github.com/godbus/dbus/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DBusObject is any type that has a Path method that returns a dbus.ObjectPath.
|
/*
|
||||||
type DBusObject interface {
|
MultiError is a type of error.Error that can contain multiple error.Errors. Confused? Don't worry about it.
|
||||||
Path() dbus.ObjectPath
|
*/
|
||||||
|
type MultiError struct {
|
||||||
|
// Errors is a slice of errors to combine/concatenate when .Error() is called.
|
||||||
|
Errors []error `json:"errors"`
|
||||||
|
// ErrorSep is a string to use to separate errors for .Error(). The default is "\n".
|
||||||
|
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 `json:"conn"`
|
||||||
|
// PathOK is true if the Dbus path given is a valid type and value.
|
||||||
|
PathOK bool `json:"path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 is a default Session initiated automatically.
|
||||||
|
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
|
||||||
|
// collection tracks the Service this Session was created from.
|
||||||
|
service *Service
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -16,10 +87,15 @@ type DBusObject interface {
|
|||||||
https://specifications.freedesktop.org/secret-service/latest/ch03.html
|
https://specifications.freedesktop.org/secret-service/latest/ch03.html
|
||||||
*/
|
*/
|
||||||
type Collection struct {
|
type Collection struct {
|
||||||
// Conn is an active connection to the Dbus.
|
*DbusObject
|
||||||
Conn *dbus.Conn
|
// lastModified is unexported because it's important that API users don't change it; it's used by Collection.Modified.
|
||||||
// Dbus is the Dbus bus object.
|
lastModified time.Time
|
||||||
Dbus dbus.BusObject
|
// lastModifiedSet is unexported; it's only used to determine if this is a first-initialization of the modification time or not.
|
||||||
|
lastModifiedSet bool
|
||||||
|
// name is used for the Collection's name/label so the Dbus path doesn't need to be parsed all the time.
|
||||||
|
name string
|
||||||
|
// service tracks the Service this Collection was created from.
|
||||||
|
service *Service
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -28,22 +104,21 @@ type Collection struct {
|
|||||||
https://specifications.freedesktop.org/secret-service/latest/re03.html
|
https://specifications.freedesktop.org/secret-service/latest/re03.html
|
||||||
*/
|
*/
|
||||||
type Item struct {
|
type Item struct {
|
||||||
// Conn is an active connection to the Dbus.
|
*DbusObject
|
||||||
Conn *dbus.Conn
|
// Secret is the corresponding Secret object.
|
||||||
// Dbus is the Dbus bus object.
|
Secret *Secret `json:"secret"`
|
||||||
Dbus dbus.BusObject
|
// 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
|
||||||
Prompt is an interface to handling unlocking prompts.
|
/*
|
||||||
https://developer-old.gnome.org/libsecret/0.18/SecretPrompt.html
|
idx is the index identifier of the Item.
|
||||||
https://specifications.freedesktop.org/secret-service/latest/ch09.html
|
It is almost guaranteed to not match the index in Collection.Items (unless you have like, only one item)
|
||||||
*/
|
as those indices are static and do not determine the order that Dbus returns the list of item paths.
|
||||||
type Prompt struct {
|
*/
|
||||||
// Conn is an active connection to the Dbus.
|
idx int
|
||||||
Conn *dbus.Conn
|
// collection tracks the Collection this Item is in.
|
||||||
// Dbus is the Dbus bus object.
|
collection *Collection
|
||||||
Dbus dbus.BusObject
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -53,36 +128,22 @@ type Prompt 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
|
Session dbus.ObjectPath `json:"session_path"`
|
||||||
// Parameters are "algorithm dependent parameters for secret value encoding" - likely this will just be an empty byteslice.
|
/*
|
||||||
Parameters []byte
|
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"`
|
||||||
// Value is the secret's content in []byte format.
|
// Value is the secret's content in []byte format.
|
||||||
Value []byte
|
Value SecretValue `json:"value"`
|
||||||
// ContentType is the MIME type of Value.
|
// ContentType is the MIME type of Value.
|
||||||
ContentType string
|
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.
|
||||||
Service is a general SecretService interface, sort of handler for Dbus - it's used for fetching a Session, Collections, etc.
|
type SecretValue []byte
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user