Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
df86089517
|
|||
|
55e740793a
|
|||
|
b96f17fb19
|
39
.gitignore
vendored
39
.gitignore
vendored
@@ -1,39 +0,0 @@
|
|||||||
*.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
Normal file
33
.idea/codeStyles/Project.xml
generated
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<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
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<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_FILES" />
|
<option name="show" value="PROJECT" />
|
||||||
<option name="description" value="" />
|
<option name="description" value="" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
14
.ref/URLs
14
.ref/URLs
@@ -1,14 +0,0 @@
|
|||||||
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.
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
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,11 +1 @@
|
|||||||
- TEST CASES
|
- tests for V0
|
||||||
-- 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,106 +1,57 @@
|
|||||||
package gosecret
|
package libsecret
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`strings`
|
`github.com/godbus/dbus`
|
||||||
"time"
|
|
||||||
|
|
||||||
`github.com/godbus/dbus/v5`
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
// NewCollection returns a pointer to a new Collection based on a Dbus connection and a Dbus path.
|
||||||
NewCollection returns a pointer to a Collection based on a Service and a Dbus path.
|
func NewCollection(conn *dbus.Conn, path dbus.ObjectPath) (coll *Collection) {
|
||||||
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{
|
||||||
DbusObject: &DbusObject{
|
Conn: conn,
|
||||||
Conn: service.Conn,
|
Dbus: conn.Object(DBusServiceName, path),
|
||||||
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.
|
||||||
CreateItem returns a pointer to an Item based on a label, some attributes, a Secret,
|
func (c Collection) Path() (coll dbus.ObjectPath) {
|
||||||
whether any existing secret with the same label should be replaced or not, and the optional itemType.
|
|
||||||
|
|
||||||
itemType is optional; if specified, it should be a Dbus interface (only the first element is used).
|
// Remove this method in V1. It's bloat since we now have an exported Dbus.
|
||||||
If not specified, the default DbusDefaultItemType will be used.
|
coll = c.Dbus.Path()
|
||||||
*/
|
|
||||||
func (c *Collection) CreateItem(label string, attrs map[string]string, secret *Secret, replace bool, itemType ...string) (item *Item, err error) {
|
|
||||||
|
|
||||||
var prompt *Prompt
|
|
||||||
var path dbus.ObjectPath
|
|
||||||
var promptPath dbus.ObjectPath
|
|
||||||
var variant *dbus.Variant
|
|
||||||
var props map[string]dbus.Variant = make(map[string]dbus.Variant)
|
|
||||||
var typeString string
|
|
||||||
|
|
||||||
if itemType != nil && len(itemType) > 0 {
|
|
||||||
typeString = itemType[0]
|
|
||||||
} else {
|
|
||||||
typeString = DbusDefaultItemType
|
|
||||||
}
|
|
||||||
|
|
||||||
props[DbusItemLabel] = dbus.MakeVariant(label)
|
|
||||||
props[DbusItemType] = dbus.MakeVariant(typeString)
|
|
||||||
props[DbusItemAttributes] = dbus.MakeVariant(attrs)
|
|
||||||
|
|
||||||
if err = c.Dbus.Call(
|
|
||||||
DbusCollectionCreateItem, 0, props, secret, replace,
|
|
||||||
).Store(&path, &promptPath); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if isPrompt(promptPath) {
|
|
||||||
prompt = NewPrompt(c.Conn, promptPath)
|
|
||||||
|
|
||||||
if variant, err = prompt.Prompt(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
path = variant.Value().(dbus.ObjectPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
item, err = NewItem(c, path)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Items returns a slice of Item items in the Collection.
|
||||||
Delete removes a Collection.
|
func (c *Collection) Items() (items []Item, err error) {
|
||||||
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;
|
var variant dbus.Variant
|
||||||
the item paths are cached as "orphaned paths" in Dbus otherwise if not deleted before deleting
|
var paths []dbus.ObjectPath
|
||||||
their Collection. They should clear on a reboot or restart of Dbus (but rebooting Dbus on a system in use is... troublesome).
|
|
||||||
*/
|
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) {
|
func (c *Collection) Delete() (err error) {
|
||||||
|
|
||||||
var promptPath dbus.ObjectPath
|
var promptPath dbus.ObjectPath
|
||||||
var prompt *Prompt
|
var prompt *Prompt
|
||||||
|
|
||||||
if err = c.Dbus.Call(DbusCollectionDelete, 0).Store(&promptPath); err != nil {
|
if err = c.Dbus.Call("org.freedesktop.Secret.Collection.Delete", 0).Store(&promptPath); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,59 +66,68 @@ func (c *Collection) Delete() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Items returns a slice of Item pointers in the Collection.
|
// SearchItems searches a Collection for a matching profile string.
|
||||||
func (c *Collection) Items() (items []*Item, err error) {
|
func (c *Collection) SearchItems(profile string) (items []Item, err error) {
|
||||||
|
|
||||||
var paths []dbus.ObjectPath
|
var paths []dbus.ObjectPath
|
||||||
var item *Item
|
var attrs map[string]string = make(map[string]string, 0)
|
||||||
var variant dbus.Variant
|
|
||||||
var errs []error = make([]error, 0)
|
|
||||||
|
|
||||||
if variant, err = c.Dbus.GetProperty(DbusCollectionItems); err != nil {
|
attrs["profile"] = profile
|
||||||
|
|
||||||
|
if err = c.Dbus.Call("org.freedesktop.Secret.Collection.SearchItems", 0, attrs).Store(&paths); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
paths = variant.Value().([]dbus.ObjectPath)
|
items = make([]Item, len(paths))
|
||||||
|
|
||||||
items = make([]*Item, len(paths))
|
|
||||||
|
|
||||||
for idx, path := range paths {
|
for idx, path := range paths {
|
||||||
if item, err = NewItem(c, path); err != nil {
|
items[idx] = *NewItem(c.Conn, path)
|
||||||
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locked indicates if a Collection is locked (true) or unlocked (false).
|
// CreateItem returns a pointer to an Item based on a label, a Secret, and whether any existing secret should be replaced or not.
|
||||||
|
func (c *Collection) CreateItem(label string, secret *Secret, replace bool) (item *Item, err error) {
|
||||||
|
|
||||||
|
var prompt *Prompt
|
||||||
|
var path dbus.ObjectPath
|
||||||
|
var promptPath dbus.ObjectPath
|
||||||
|
var variant *dbus.Variant
|
||||||
|
var props map[string]dbus.Variant = make(map[string]dbus.Variant)
|
||||||
|
var attrs map[string]string = make(map[string]string)
|
||||||
|
|
||||||
|
attrs["profile"] = label
|
||||||
|
props["org.freedesktop.Secret.Item.Label"] = dbus.MakeVariant(label)
|
||||||
|
props["org.freedesktop.Secret.Item.Attributes"] = dbus.MakeVariant(attrs)
|
||||||
|
|
||||||
|
if err = c.Dbus.Call(
|
||||||
|
"org.freedesktop.Secret.Collection.CreateItem", 0, props, secret, replace,
|
||||||
|
).Store(&path, &promptPath); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if isPrompt(promptPath) {
|
||||||
|
prompt = NewPrompt(c.Conn, promptPath)
|
||||||
|
|
||||||
|
if variant, err = prompt.Prompt(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
path = variant.Value().(dbus.ObjectPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
item = NewItem(c.Conn, path)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locked indicates that a Collection is locked (true) or unlocked (false).
|
||||||
func (c *Collection) Locked() (isLocked bool, err error) {
|
func (c *Collection) Locked() (isLocked bool, err error) {
|
||||||
|
|
||||||
var variant dbus.Variant
|
var variant dbus.Variant
|
||||||
|
|
||||||
if variant, err = c.Dbus.GetProperty(DbusCollectionLocked); err != nil {
|
if variant, err = c.Dbus.GetProperty("org.freedesktop.Secret.Collection.Locked"); err != nil {
|
||||||
isLocked = true
|
isLocked = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -176,128 +136,3 @@ 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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,286 +0,0 @@
|
|||||||
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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
257
consts.go
257
consts.go
@@ -1,256 +1,7 @@
|
|||||||
package gosecret
|
package libsecret
|
||||||
|
|
||||||
// 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.
|
|
||||||
I've only ever seen "org.gnome.keyring.NetworkPassword" in the wild
|
|
||||||
aside from the below. It may be legacy (gnome-keyring is obsoleted by SecretService).
|
|
||||||
If in doubt, the below is considered the "proper" interface.
|
|
||||||
*/
|
|
||||||
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 (
|
||||||
FlagServiceNone ServiceInitFlag = iota
|
DBusServiceName string = "org.freedesktop.secrets"
|
||||||
FlagServiceOpenSession
|
DBusPath string = "/org/freedesktop/secrets"
|
||||||
FlagServiceLoadCollections
|
PromptPrefix string = DBusPath + "/prompt/"
|
||||||
)
|
|
||||||
|
|
||||||
// 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
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
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
73
errs.go
@@ -1,73 +0,0 @@
|
|||||||
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,117 +1,16 @@
|
|||||||
package gosecret
|
package libsecret
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`strings`
|
`strings`
|
||||||
|
|
||||||
`github.com/godbus/dbus/v5`
|
`github.com/godbus/dbus`
|
||||||
)
|
)
|
||||||
|
|
||||||
// isPrompt returns a boolean that is true if path is/requires a prompt(ed path) and false if it is/does not.
|
// 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), DbusPromptPrefix)
|
prompt = strings.HasPrefix(string(path), PromptPrefix)
|
||||||
|
|
||||||
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,7 +2,4 @@ module r00t2.io/gosecret
|
|||||||
|
|
||||||
go 1.17
|
go 1.17
|
||||||
|
|
||||||
require (
|
require github.com/godbus/dbus v4.1.0+incompatible
|
||||||
github.com/godbus/dbus/v5 v5.0.6
|
|
||||||
github.com/google/uuid v1.3.0
|
|
||||||
)
|
|
||||||
|
|||||||
6
go.sum
6
go.sum
@@ -1,4 +1,2 @@
|
|||||||
github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro=
|
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
|
||||||
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||||
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,103 +1,25 @@
|
|||||||
package gosecret
|
package libsecret
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`strconv`
|
`github.com/godbus/dbus`
|
||||||
`strings`
|
|
||||||
`time`
|
|
||||||
|
|
||||||
`github.com/godbus/dbus/v5`
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewItem returns a pointer to an Item based on Collection and a Dbus path.
|
// NewItem returns a pointer to a new Item based on a Dbus connection and a Dbus path.
|
||||||
func NewItem(collection *Collection, path dbus.ObjectPath) (item *Item, err error) {
|
func NewItem(conn *dbus.Conn, path dbus.ObjectPath) (item *Item) {
|
||||||
|
|
||||||
var splitPath []string
|
|
||||||
|
|
||||||
if collection == nil {
|
|
||||||
err = ErrNoDbusConn
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = validConnPath(collection.Conn, path); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
item = &Item{
|
item = &Item{
|
||||||
DbusObject: &DbusObject{
|
Conn: conn,
|
||||||
Conn: collection.Conn,
|
Dbus: conn.Object(DBusServiceName, path),
|
||||||
Dbus: collection.Conn.Object(DbusService, path),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
splitPath = strings.Split(string(item.Dbus.Path()), "/")
|
|
||||||
|
|
||||||
item.idx, err = strconv.Atoi(splitPath[len(splitPath)-1])
|
|
||||||
item.collection = collection
|
|
||||||
if _, err = item.Attributes(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if _, err = item.Type(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err = item.Modified()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attributes 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSecret returns the Secret in an Item using a Session.
|
// Path returns the path of the underlying Dbus connection.
|
||||||
func (i *Item) GetSecret(session *Session) (secret *Secret, err error) {
|
func (i Item) Path() (path dbus.ObjectPath) {
|
||||||
|
|
||||||
if session == nil {
|
// Remove this method in V1. It's bloat since we now have an exported Dbus.
|
||||||
err = ErrNoDbusConn
|
path = i.Dbus.Path()
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
@@ -107,7 +29,7 @@ func (i *Item) Label() (label string, err error) {
|
|||||||
|
|
||||||
var variant dbus.Variant
|
var variant dbus.Variant
|
||||||
|
|
||||||
if variant, err = i.Dbus.GetProperty(DbusItemLabel); err != nil {
|
if variant, err = i.Dbus.GetProperty("org.freedesktop.Secret.Item.Label"); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,112 +38,12 @@ 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(DbusItemLocked); err != nil {
|
if variant, err = i.Dbus.GetProperty("org.freedesktop.Secret.Item.Locked"); err != nil {
|
||||||
isLocked = true
|
isLocked = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -231,52 +53,37 @@ func (i *Item) Locked() (isLocked bool, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Created returns the time.Time of when an Item was created.
|
// GetSecret returns the Secret in an Item using a Session.
|
||||||
func (i *Item) Created() (created time.Time, err error) {
|
func (i *Item) GetSecret(session *Session) (secret *Secret, err error) {
|
||||||
|
|
||||||
var variant dbus.Variant
|
secret = new(Secret)
|
||||||
var timeInt uint64
|
|
||||||
|
|
||||||
if variant, err = i.Dbus.GetProperty(DbusItemCreated); err != nil {
|
if err = i.Dbus.Call(
|
||||||
|
"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.
|
||||||
Modified returns the time.Time of when an Item was last modified along with a boolean
|
func (i *Item) Delete() (err error) {
|
||||||
that indicates if the collection has changed since the last call of Item.Modified.
|
|
||||||
|
|
||||||
Note that when calling NewItem, the internal library-tracked modification
|
var prompt *Prompt
|
||||||
time (Item.lastModified) will be set to the latest modification time of the Item
|
var promptPath dbus.ObjectPath
|
||||||
itself as reported by Dbus rather than the time that NewItem was called.
|
|
||||||
*/
|
|
||||||
func (i *Item) Modified() (modified time.Time, isChanged bool, err error) {
|
|
||||||
|
|
||||||
var variant dbus.Variant
|
if err = i.Dbus.Call("org.freedesktop.Secret.Item.Delete", 0).Store(&promptPath); err != nil {
|
||||||
var timeInt uint64
|
|
||||||
|
|
||||||
if variant, err = i.Dbus.GetProperty(DbusItemModified); err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
timeInt = variant.Value().(uint64)
|
if isPrompt(promptPath) {
|
||||||
|
prompt = NewPrompt(i.Conn, promptPath)
|
||||||
|
|
||||||
modified = time.Unix(int64(timeInt), 0)
|
if _, err = prompt.Prompt(); err != nil {
|
||||||
|
return
|
||||||
if !i.lastModifiedSet {
|
}
|
||||||
// It's "nil", so set it to modified. We can't check for a zero-value in case Dbus has it as a zero-value.
|
|
||||||
i.lastModified = modified
|
|
||||||
i.lastModifiedSet = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isChanged = modified.After(i.lastModified)
|
|
||||||
i.lastModified = modified
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,150 +0,0 @@
|
|||||||
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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
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,30 +1,35 @@
|
|||||||
package gosecret
|
package libsecret
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`github.com/godbus/dbus/v5`
|
`github.com/godbus/dbus`
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewPrompt returns a pointer to a new Prompt based on a Dbus connection and a Dbus path.
|
// NewPrompt returns a pointer to a new Prompt based on a Dbus connection and a Dbus path.
|
||||||
func NewPrompt(conn *dbus.Conn, path dbus.ObjectPath) (prompt *Prompt) {
|
func NewPrompt(conn *dbus.Conn, path dbus.ObjectPath) (prompt *Prompt) {
|
||||||
|
|
||||||
prompt = &Prompt{
|
prompt = &Prompt{
|
||||||
DbusObject: &DbusObject{
|
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
Dbus: conn.Object(DbusService, path),
|
Dbus: conn.Object(DBusServiceName, 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)
|
||||||
@@ -32,14 +37,12 @@ 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(
|
if err = p.Dbus.Call("org.freedesktop.Secret.Prompt.Prompt", 0, "").Store(); err != nil {
|
||||||
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.Dbus.Path() {
|
if result = <-c; result.Path == p.Path() {
|
||||||
*promptValue = result.Body[1].(dbus.Variant)
|
*promptValue = result.Body[1].(dbus.Variant)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package gosecret
|
package libsecret
|
||||||
|
|
||||||
// NewSecret returns a pointer to a new Secret based on a Session, parameters, (likely an empty byte slice), a value, and the MIME content type.
|
// 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.Dbus.Path(),
|
Session: session.Path(),
|
||||||
Parameters: params,
|
Parameters: params,
|
||||||
Value: value,
|
Value: value,
|
||||||
ContentType: contentType,
|
ContentType: contentType,
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
413
service_funcs.go
413
service_funcs.go
@@ -1,82 +1,74 @@
|
|||||||
package gosecret
|
package libsecret
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
`github.com/godbus/dbus`
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/godbus/dbus/v5"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewService returns a pointer to a new Service connection.
|
// NewService returns a pointer to a new Service.
|
||||||
func NewService() (service *Service, err error) {
|
func NewService() (service *Service, err error) {
|
||||||
|
|
||||||
var svc Service = Service{
|
service = &Service{
|
||||||
DbusObject: &DbusObject{
|
|
||||||
Conn: nil,
|
Conn: nil,
|
||||||
Dbus: nil,
|
Dbus: nil,
|
||||||
},
|
|
||||||
Session: nil,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if svc.Conn, err = dbus.SessionBus(); err != nil {
|
if service.Conn, err = dbus.SessionBus(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
svc.Dbus = svc.Conn.Object(DbusService, dbus.ObjectPath(DbusPath))
|
service.Dbus = service.Conn.Object(DBusServiceName, dbus.ObjectPath(DBusPath))
|
||||||
|
|
||||||
if svc.Session, err = svc.GetSession(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
service = &svc
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close cleanly closes a Service and all its underlying connections (e.g. Service.Session).
|
// Path returns the path of the underlying Dbus connection.
|
||||||
func (s *Service) Close() (err error) {
|
func (s Service) Path() (path dbus.ObjectPath) {
|
||||||
|
|
||||||
err = s.Session.Close()
|
// Remove this method in V1. It's bloat since we now have an exported Dbus.
|
||||||
|
path = s.Dbus.Path()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collections returns a slice of Collection items accessible to this Service.
|
// Open returns a pointer to a Session from the Service.
|
||||||
func (s *Service) Collections() (collections []*Collection, err error) {
|
func (s *Service) Open() (session *Session, err error) {
|
||||||
|
|
||||||
|
var output dbus.Variant
|
||||||
|
var path dbus.ObjectPath
|
||||||
|
|
||||||
|
if err = s.Dbus.Call(
|
||||||
|
"org.freedesktop.Secret.Service.OpenSession", 0, "plain", dbus.MakeVariant(""),
|
||||||
|
).Store(&output, &path); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session = NewSession(s.Conn, path)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collections returns a slice of Collection keyrings accessible to this Service.
|
||||||
|
func (s *Service) Collections() (collections []Collection, err error) {
|
||||||
|
|
||||||
var paths []dbus.ObjectPath
|
var 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(DbusServiceCollections); err != nil {
|
if variant, err = s.Dbus.GetProperty("org.freedesktop.Secret.Service.Collections"); 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 {
|
||||||
if coll, err = NewCollection(s, path); err != nil {
|
collections[idx] = *NewCollection(s.Conn, path)
|
||||||
// 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.
|
||||||
CreateAliasedCollection creates a new Collection (keyring) via a Service with the name specified by label,
|
func (s *Service) CreateCollection(label string) (collection *Collection, err error) {
|
||||||
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
|
||||||
@@ -84,17 +76,16 @@ func (s *Service) CreateAliasedCollection(label, alias string) (collection *Coll
|
|||||||
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[DbusCollectionLabel] = dbus.MakeVariant(label)
|
props["org.freedesktop.Secret.Collection.Label"] = dbus.MakeVariant(label)
|
||||||
|
|
||||||
if err = s.Dbus.Call(
|
if err = s.Dbus.Call("org.freedesktop.Secret.Service.CreateCollection", 0, props, "").Store(&path, &promptPath); err != nil {
|
||||||
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
|
||||||
}
|
}
|
||||||
@@ -102,149 +93,20 @@ func (s *Service) CreateAliasedCollection(label, alias string) (collection *Coll
|
|||||||
path = variant.Value().(dbus.ObjectPath)
|
path = variant.Value().(dbus.ObjectPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
collection, err = NewCollection(s, path)
|
collection = NewCollection(s.Conn, path)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Unlock unlocks a Locked Service.
|
||||||
CreateCollection creates a new Collection (keyring) via a Service with the name specified by label and returns the new Collection.
|
func (s *Service) Unlock(object DBusObject) (err error) {
|
||||||
It is a *very* thin wrapper around Service.CreateAliasedCollection, but with a blank alias.
|
|
||||||
*/
|
|
||||||
func (s *Service) CreateCollection(label string) (collection *Collection, err error) {
|
|
||||||
|
|
||||||
collection, err = s.CreateAliasedCollection(label, "")
|
var unlocked []dbus.ObjectPath
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
if c != nil {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
c = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// We didn't get it by alias, so let's try by name...
|
|
||||||
if colls, err = s.Collections(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, i := range colls {
|
|
||||||
if i.name == name {
|
|
||||||
c = i
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
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 prompt *Prompt
|
var prompt *Prompt
|
||||||
var promptPath dbus.ObjectPath
|
var promptPath dbus.ObjectPath
|
||||||
|
var paths []dbus.ObjectPath = []dbus.ObjectPath{object.Path()}
|
||||||
|
|
||||||
if objectPaths == nil || len(objectPaths) == 0 {
|
if err = s.Dbus.Call("org.freedesktop.Secret.Service.Unlock", 0, paths).Store(&unlocked, &promptPath); err != nil {
|
||||||
objectPaths = []dbus.ObjectPath{s.Dbus.Path()}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = s.Dbus.Call(
|
|
||||||
DbusServiceLock, 0, objectPaths,
|
|
||||||
).Store(&locked, &promptPath); err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,196 +122,21 @@ func (s *Service) Lock(objectPaths ...dbus.ObjectPath) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Lock locks an Unlocked Service.
|
||||||
OpenSession returns a pointer to a Session from the Service.
|
func (s *Service) Lock(object DBusObject) (err error) {
|
||||||
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 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 prompt *Prompt
|
||||||
var resultPath dbus.ObjectPath
|
var promptPath dbus.ObjectPath
|
||||||
|
var paths []dbus.ObjectPath = []dbus.ObjectPath{object.Path()}
|
||||||
|
|
||||||
if objectPaths == nil || len(objectPaths) == 0 {
|
if err = s.Dbus.Call("org.freedesktop.Secret.Service.Lock", 0, paths).Store(&locked, &promptPath); err != nil {
|
||||||
objectPaths = []dbus.ObjectPath{s.Dbus.Path()}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = s.Dbus.Call(
|
|
||||||
DbusServiceUnlock, 0, objectPaths,
|
|
||||||
).Store(&unlocked, &resultPath); err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isPrompt(resultPath) {
|
if isPrompt(promptPath) {
|
||||||
|
|
||||||
prompt = NewPrompt(s.Conn, resultPath)
|
prompt = NewPrompt(s.Conn, promptPath)
|
||||||
|
|
||||||
if _, err = prompt.Prompt(); err != nil {
|
if _, err = prompt.Prompt(); err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,442 +0,0 @@
|
|||||||
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,44 +1,25 @@
|
|||||||
package gosecret
|
package libsecret
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/godbus/dbus/v5"
|
`github.com/godbus/dbus`
|
||||||
)
|
)
|
||||||
|
|
||||||
// I'm still not 100% certain what Sessions are used for, aside from getting Secrets from Items.
|
// NewSession returns a pointer to a new Session based on a Dbus connection and a Dbus path.
|
||||||
|
func NewSession(conn *dbus.Conn, path dbus.ObjectPath) (session *Session) {
|
||||||
|
|
||||||
/*
|
session = &Session{
|
||||||
NewSession returns a pointer to a new Session based on a Service and a dbus.ObjectPath.
|
Conn: conn,
|
||||||
You will almost always want to use Service.GetSession or Service.OpenSession instead.
|
Dbus: conn.Object(DBusServiceName, path),
|
||||||
*/
|
|
||||||
func NewSession(service *Service, path dbus.ObjectPath) (session *Session, err error) {
|
|
||||||
|
|
||||||
if _, err = validConnPath(service.Conn, path); err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var ssn Session = Session{
|
|
||||||
DbusObject: &DbusObject{
|
|
||||||
Conn: service.Conn,
|
|
||||||
},
|
|
||||||
service: service,
|
|
||||||
}
|
|
||||||
ssn.Dbus = ssn.Conn.Object(DbusInterfaceSession, path)
|
|
||||||
|
|
||||||
session = &ssn
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close cleanly closes a Session.
|
// Path returns the path of the underlying Dbus connection.
|
||||||
func (s *Session) Close() (err error) {
|
func (s Session) Path() (path dbus.ObjectPath) {
|
||||||
|
|
||||||
var c *dbus.Call
|
// Remove this method in V1. It's bloat since we now have an exported Dbus.
|
||||||
|
path = s.Dbus.Path()
|
||||||
c = s.Dbus.Call(
|
|
||||||
DbusSessionClose, 0,
|
|
||||||
)
|
|
||||||
|
|
||||||
_ = c
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
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,83 +1,12 @@
|
|||||||
package gosecret
|
package libsecret
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
`github.com/godbus/dbus`
|
||||||
|
|
||||||
"github.com/godbus/dbus/v5"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
// DBusObject is any type that has a Path method that returns a dbus.ObjectPath.
|
||||||
MultiError is a type of error.Error that can contain multiple error.Errors. Confused? Don't worry about it.
|
type DBusObject interface {
|
||||||
*/
|
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -87,15 +16,10 @@ type Session struct {
|
|||||||
https://specifications.freedesktop.org/secret-service/latest/ch03.html
|
https://specifications.freedesktop.org/secret-service/latest/ch03.html
|
||||||
*/
|
*/
|
||||||
type Collection struct {
|
type Collection struct {
|
||||||
*DbusObject
|
// Conn is an active connection to the Dbus.
|
||||||
// lastModified is unexported because it's important that API users don't change it; it's used by Collection.Modified.
|
Conn *dbus.Conn
|
||||||
lastModified time.Time
|
// Dbus is the Dbus bus object.
|
||||||
// lastModifiedSet is unexported; it's only used to determine if this is a first-initialization of the modification time or not.
|
Dbus dbus.BusObject
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -104,21 +28,22 @@ 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 {
|
||||||
*DbusObject
|
// Conn is an active connection to the Dbus.
|
||||||
// Secret is the corresponding Secret object.
|
Conn *dbus.Conn
|
||||||
Secret *Secret `json:"secret"`
|
// Dbus is the Dbus bus object.
|
||||||
// lastModified is unexported because it's important that API users don't change it; it's used by Collection.Modified.
|
Dbus dbus.BusObject
|
||||||
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.
|
||||||
idx is the index identifier of the Item.
|
https://developer-old.gnome.org/libsecret/0.18/SecretPrompt.html
|
||||||
It is almost guaranteed to not match the index in Collection.Items (unless you have like, only one item)
|
https://specifications.freedesktop.org/secret-service/latest/ch09.html
|
||||||
as those indices are static and do not determine the order that Dbus returns the list of item paths.
|
*/
|
||||||
*/
|
type Prompt struct {
|
||||||
idx int
|
// Conn is an active connection to the Dbus.
|
||||||
// collection tracks the Collection this Item is in.
|
Conn *dbus.Conn
|
||||||
collection *Collection
|
// Dbus is the Dbus bus object.
|
||||||
|
Dbus dbus.BusObject
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -128,22 +53,36 @@ type Item struct {
|
|||||||
https://specifications.freedesktop.org/secret-service/latest/ch14.html#type-Secret
|
https://specifications.freedesktop.org/secret-service/latest/ch14.html#type-Secret
|
||||||
*/
|
*/
|
||||||
type Secret struct {
|
type Secret struct {
|
||||||
// Session is a Dbus object path for the associated Session (the actual Session is stored in an unexported field).
|
// Session is a Dbus object path for the associated Session.
|
||||||
Session dbus.ObjectPath `json:"session_path"`
|
Session dbus.ObjectPath
|
||||||
/*
|
// Parameters are "algorithm dependent parameters for secret value encoding" - likely this will just be an empty byteslice.
|
||||||
Parameters are "algorithm dependent parameters for secret value encoding" - likely this will just be an empty byteslice.
|
Parameters []byte
|
||||||
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 SecretValue `json:"value"`
|
Value []byte
|
||||||
// ContentType is the MIME type of Value.
|
// ContentType is the MIME type of Value.
|
||||||
ContentType string `json:"content_type"`
|
ContentType string
|
||||||
// item is the Item this Secret belongs to.
|
|
||||||
item *Item
|
|
||||||
// session is the Session used to decode/decrypt this Secret.
|
|
||||||
session *Session
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SecretValue is a custom type that handles JSON encoding/decoding a little more easily.
|
/*
|
||||||
type SecretValue []byte
|
Service is a general SecretService interface, sort of handler for Dbus - it's used for fetching a Session, Collections, etc.
|
||||||
|
https://developer-old.gnome.org/libsecret/0.18/SecretService.html
|
||||||
|
https://specifications.freedesktop.org/secret-service/latest/re01.html
|
||||||
|
*/
|
||||||
|
type Service struct {
|
||||||
|
// Conn is an active connection to the Dbus.
|
||||||
|
Conn *dbus.Conn
|
||||||
|
// Dbus is the Dbus bus object.
|
||||||
|
Dbus dbus.BusObject
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Session is a session/instance/connection to SecretService.
|
||||||
|
https://developer-old.gnome.org/libsecret/0.18/SecretService.html
|
||||||
|
https://specifications.freedesktop.org/secret-service/latest/ch06.html
|
||||||
|
*/
|
||||||
|
type Session struct {
|
||||||
|
// Conn is an active connection to the Dbus.
|
||||||
|
Conn *dbus.Conn
|
||||||
|
// Dbus is the Dbus bus object.
|
||||||
|
Dbus dbus.BusObject
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user