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
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/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"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="PROJECT_FILES" />
|
||||
<option name="show" value="PROJECT" />
|
||||
<option name="description" value="" />
|
||||
</component>
|
||||
</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
|
||||
-- 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)
|
||||
- tests for V0
|
||||
|
||||
@@ -1,106 +1,57 @@
|
||||
package gosecret
|
||||
package libsecret
|
||||
|
||||
import (
|
||||
`strings`
|
||||
"time"
|
||||
|
||||
`github.com/godbus/dbus/v5`
|
||||
`github.com/godbus/dbus`
|
||||
)
|
||||
|
||||
/*
|
||||
NewCollection returns a pointer to a Collection based on a Service and a Dbus path.
|
||||
You will almost always want to use Service.GetCollection instead.
|
||||
*/
|
||||
func NewCollection(service *Service, path dbus.ObjectPath) (coll *Collection, err error) {
|
||||
|
||||
var splitPath []string
|
||||
|
||||
if service == nil {
|
||||
err = ErrNoDbusConn
|
||||
}
|
||||
|
||||
if _, err = validConnPath(service.Conn, path); err != nil {
|
||||
return
|
||||
}
|
||||
// NewCollection returns a pointer to a new Collection based on a Dbus connection and a Dbus path.
|
||||
func NewCollection(conn *dbus.Conn, path dbus.ObjectPath) (coll *Collection) {
|
||||
|
||||
coll = &Collection{
|
||||
DbusObject: &DbusObject{
|
||||
Conn: service.Conn,
|
||||
Dbus: service.Conn.Object(DbusService, path),
|
||||
},
|
||||
service: service,
|
||||
// lastModified: time.Now(),
|
||||
Conn: conn,
|
||||
Dbus: conn.Object(DBusServiceName, path),
|
||||
}
|
||||
|
||||
splitPath = strings.Split(string(coll.Dbus.Path()), "/")
|
||||
|
||||
coll.name = splitPath[len(splitPath)-1]
|
||||
|
||||
_, _, err = coll.Modified()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
CreateItem returns a pointer to an Item based on a label, some attributes, a Secret,
|
||||
whether any existing secret with the same label should be replaced or not, and the optional itemType.
|
||||
// Path returns the dbus.ObjectPath of the Collection.
|
||||
func (c Collection) Path() (coll dbus.ObjectPath) {
|
||||
|
||||
itemType is optional; if specified, it should be a Dbus interface (only the first element is used).
|
||||
If not specified, the default DbusDefaultItemType will be used.
|
||||
*/
|
||||
func (c *Collection) CreateItem(label string, attrs map[string]string, secret *Secret, replace bool, itemType ...string) (item *Item, err error) {
|
||||
// Remove this method in V1. It's bloat since we now have an exported Dbus.
|
||||
coll = c.Dbus.Path()
|
||||
|
||||
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
|
||||
return
|
||||
}
|
||||
|
||||
if itemType != nil && len(itemType) > 0 {
|
||||
typeString = itemType[0]
|
||||
} else {
|
||||
typeString = DbusDefaultItemType
|
||||
}
|
||||
// Items returns a slice of Item items in the Collection.
|
||||
func (c *Collection) Items() (items []Item, err error) {
|
||||
|
||||
props[DbusItemLabel] = dbus.MakeVariant(label)
|
||||
props[DbusItemType] = dbus.MakeVariant(typeString)
|
||||
props[DbusItemAttributes] = dbus.MakeVariant(attrs)
|
||||
var variant dbus.Variant
|
||||
var paths []dbus.ObjectPath
|
||||
|
||||
if err = c.Dbus.Call(
|
||||
DbusCollectionCreateItem, 0, props, secret, replace,
|
||||
).Store(&path, &promptPath); err != nil {
|
||||
if variant, err = c.Dbus.GetProperty("org.freedesktop.Secret.Collection.Items"); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if isPrompt(promptPath) {
|
||||
prompt = NewPrompt(c.Conn, promptPath)
|
||||
paths = variant.Value().([]dbus.ObjectPath)
|
||||
|
||||
if variant, err = prompt.Prompt(); err != nil {
|
||||
return
|
||||
}
|
||||
items = make([]Item, len(paths))
|
||||
|
||||
path = variant.Value().(dbus.ObjectPath)
|
||||
for idx, path := range paths {
|
||||
items[idx] = *NewItem(c.Conn, path)
|
||||
}
|
||||
|
||||
item, err = NewItem(c, path)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Delete removes a Collection.
|
||||
While *technically* not necessary, it is recommended that you iterate through
|
||||
Collection.Items and do an Item.Delete for each item *before* calling Collection.Delete;
|
||||
the item paths are cached as "orphaned paths" in Dbus otherwise if not deleted before deleting
|
||||
their Collection. They should clear on a reboot or restart of Dbus (but rebooting Dbus on a system in use is... troublesome).
|
||||
*/
|
||||
// Delete removes a Collection.
|
||||
func (c *Collection) Delete() (err error) {
|
||||
|
||||
var promptPath dbus.ObjectPath
|
||||
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
|
||||
}
|
||||
|
||||
@@ -115,59 +66,68 @@ func (c *Collection) Delete() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Items returns a slice of Item pointers in the Collection.
|
||||
func (c *Collection) Items() (items []*Item, err error) {
|
||||
// SearchItems searches a Collection for a matching profile string.
|
||||
func (c *Collection) SearchItems(profile string) (items []Item, err error) {
|
||||
|
||||
var paths []dbus.ObjectPath
|
||||
var item *Item
|
||||
var variant dbus.Variant
|
||||
var errs []error = make([]error, 0)
|
||||
var attrs map[string]string = make(map[string]string, 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
|
||||
}
|
||||
|
||||
paths = variant.Value().([]dbus.ObjectPath)
|
||||
|
||||
items = make([]*Item, len(paths))
|
||||
items = make([]Item, len(paths))
|
||||
|
||||
for idx, path := range paths {
|
||||
if item, err = NewItem(c, path); err != nil {
|
||||
errs = append(errs, err)
|
||||
err = nil
|
||||
continue
|
||||
}
|
||||
items[idx] = item
|
||||
items[idx] = *NewItem(c.Conn, path)
|
||||
}
|
||||
err = NewErrors(err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Label returns the Collection label (name).
|
||||
func (c *Collection) Label() (label string, err error) {
|
||||
// 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 variant dbus.Variant
|
||||
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)
|
||||
|
||||
if variant, err = c.Dbus.GetProperty(DbusCollectionLabel); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
label = variant.Value().(string)
|
||||
if isPrompt(promptPath) {
|
||||
prompt = NewPrompt(c.Conn, promptPath)
|
||||
|
||||
if label != c.name {
|
||||
c.name = label
|
||||
if variant, err = prompt.Prompt(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
path = variant.Value().(dbus.ObjectPath)
|
||||
}
|
||||
|
||||
item = NewItem(c.Conn, path)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Locked indicates if a Collection is locked (true) or unlocked (false).
|
||||
// Locked indicates that a Collection is locked (true) or unlocked (false).
|
||||
func (c *Collection) Locked() (isLocked bool, err error) {
|
||||
|
||||
var variant dbus.Variant
|
||||
|
||||
if variant, err = c.Dbus.GetProperty(DbusCollectionLocked); err != nil {
|
||||
if variant, err = c.Dbus.GetProperty("org.freedesktop.Secret.Collection.Locked"); err != nil {
|
||||
isLocked = true
|
||||
return
|
||||
}
|
||||
@@ -176,128 +136,3 @@ func (c *Collection) Locked() (isLocked bool, err error) {
|
||||
|
||||
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
|
||||
|
||||
// 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
|
||||
package libsecret
|
||||
|
||||
const (
|
||||
FlagServiceNone ServiceInitFlag = iota
|
||||
FlagServiceOpenSession
|
||||
FlagServiceLoadCollections
|
||||
)
|
||||
|
||||
// ServiceSearchFlag is a flag for Service.SearchItems.
|
||||
type ServiceSearchFlag int
|
||||
|
||||
const (
|
||||
FlagServiceSearchNone ServiceSearchFlag = iota
|
||||
FlagServiceSearchAll
|
||||
FlagServiceSearchUnlock
|
||||
FlagServiceSearchLoadSecrets
|
||||
)
|
||||
|
||||
// COLLECTION
|
||||
|
||||
// CollectionInitFlag is a flag for Collection.SearchItems and Collection.Items.
|
||||
type CollectionInitFlag int
|
||||
|
||||
const (
|
||||
FlagCollectionNone CollectionInitFlag = iota
|
||||
FlagCollectionLoadItems
|
||||
)
|
||||
|
||||
// ITEM
|
||||
|
||||
// ItemInitFlag are flags for Collection.SearchItems and Collection.Items.
|
||||
type ItemInitFlag int
|
||||
|
||||
const (
|
||||
FlagItemNone ItemInitFlag = iota
|
||||
FlagItemLoadSecret
|
||||
)
|
||||
|
||||
// ItemSearchFlag are flags for Collection.CreateItem.
|
||||
type ItemSearchFlag int
|
||||
|
||||
const (
|
||||
FlagItemCreateNone ItemSearchFlag = iota
|
||||
FlatItemCreateReplace
|
||||
)
|
||||
|
||||
// ERRORS
|
||||
|
||||
/*
|
||||
SecretServiceErrEnum are just constants for the enum'd errors;
|
||||
see SecretServiceError type and ErrSecretService* vars for what
|
||||
actually gets returned.
|
||||
They're used for finding the appropriate matching error.
|
||||
*/
|
||||
type SecretServiceErrEnum int
|
||||
|
||||
const (
|
||||
EnumErrProtocol SecretServiceErrEnum = iota
|
||||
EnumErrIsLocked
|
||||
EnumErrNoSuchObject
|
||||
EnumErrAlreadyExists
|
||||
EnumErrInvalidFileFormat
|
||||
DBusServiceName string = "org.freedesktop.secrets"
|
||||
DBusPath string = "/org/freedesktop/secrets"
|
||||
PromptPrefix string = DBusPath + "/prompt/"
|
||||
)
|
||||
|
||||
@@ -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 (
|
||||
`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.
|
||||
func isPrompt(path dbus.ObjectPath) (prompt bool) {
|
||||
|
||||
prompt = strings.HasPrefix(string(path), DbusPromptPrefix)
|
||||
prompt = strings.HasPrefix(string(path), PromptPrefix)
|
||||
|
||||
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
|
||||
|
||||
require (
|
||||
github.com/godbus/dbus/v5 v5.0.6
|
||||
github.com/google/uuid v1.3.0
|
||||
)
|
||||
require github.com/godbus/dbus v4.1.0+incompatible
|
||||
|
||||
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/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
|
||||
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||
|
||||
251
item_funcs.go
251
item_funcs.go
@@ -1,103 +1,25 @@
|
||||
package gosecret
|
||||
package libsecret
|
||||
|
||||
import (
|
||||
`strconv`
|
||||
`strings`
|
||||
`time`
|
||||
|
||||
`github.com/godbus/dbus/v5`
|
||||
`github.com/godbus/dbus`
|
||||
)
|
||||
|
||||
// NewItem returns a pointer to an Item based on Collection and a Dbus path.
|
||||
func NewItem(collection *Collection, path dbus.ObjectPath) (item *Item, err error) {
|
||||
|
||||
var splitPath []string
|
||||
|
||||
if collection == nil {
|
||||
err = ErrNoDbusConn
|
||||
}
|
||||
|
||||
if _, err = validConnPath(collection.Conn, path); err != nil {
|
||||
return
|
||||
}
|
||||
// NewItem returns a pointer to a new Item based on a Dbus connection and a Dbus path.
|
||||
func NewItem(conn *dbus.Conn, path dbus.ObjectPath) (item *Item) {
|
||||
|
||||
item = &Item{
|
||||
DbusObject: &DbusObject{
|
||||
Conn: collection.Conn,
|
||||
Dbus: collection.Conn.Object(DbusService, path),
|
||||
},
|
||||
}
|
||||
|
||||
splitPath = strings.Split(string(item.Dbus.Path()), "/")
|
||||
|
||||
item.idx, err = strconv.Atoi(splitPath[len(splitPath)-1])
|
||||
item.collection = collection
|
||||
if _, err = item.Attributes(); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = item.Type(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, _, err = item.Modified()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Attributes returns the Item's attributes from Dbus.
|
||||
func (i *Item) Attributes() (attrs map[string]string, err error) {
|
||||
|
||||
var variant dbus.Variant
|
||||
|
||||
if variant, err = i.Dbus.GetProperty(DbusItemAttributes); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
attrs = variant.Value().(map[string]string)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Delete removes an Item from a Collection.
|
||||
func (i *Item) Delete() (err error) {
|
||||
|
||||
var promptPath dbus.ObjectPath
|
||||
var prompt *Prompt
|
||||
|
||||
if err = i.Dbus.Call(DbusItemDelete, 0).Store(&promptPath); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if isPrompt(promptPath) {
|
||||
|
||||
prompt = NewPrompt(i.Conn, promptPath)
|
||||
if _, err = prompt.Prompt(); err != nil {
|
||||
return
|
||||
}
|
||||
Conn: conn,
|
||||
Dbus: conn.Object(DBusServiceName, path),
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetSecret returns the Secret in an Item using a Session.
|
||||
func (i *Item) GetSecret(session *Session) (secret *Secret, err error) {
|
||||
// Path returns the path of the underlying Dbus connection.
|
||||
func (i Item) Path() (path dbus.ObjectPath) {
|
||||
|
||||
if session == nil {
|
||||
err = ErrNoDbusConn
|
||||
}
|
||||
|
||||
if _, err = connIsValid(session.Conn); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = i.Dbus.Call(
|
||||
DbusItemGetSecret, 0, session.Dbus.Path(),
|
||||
).Store(&secret); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
secret.session = session
|
||||
secret.item = i
|
||||
// Remove this method in V1. It's bloat since we now have an exported Dbus.
|
||||
path = i.Dbus.Path()
|
||||
|
||||
return
|
||||
}
|
||||
@@ -107,7 +29,7 @@ func (i *Item) Label() (label string, err error) {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -116,112 +38,12 @@ func (i *Item) Label() (label string, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
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).
|
||||
// Locked indicates that an Item is locked (true) or unlocked (false).
|
||||
func (i *Item) Locked() (isLocked bool, err error) {
|
||||
|
||||
var variant dbus.Variant
|
||||
|
||||
if variant, err = i.Dbus.GetProperty(DbusItemLocked); err != nil {
|
||||
if variant, err = i.Dbus.GetProperty("org.freedesktop.Secret.Item.Locked"); err != nil {
|
||||
isLocked = true
|
||||
return
|
||||
}
|
||||
@@ -231,52 +53,37 @@ func (i *Item) Locked() (isLocked bool, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Created returns the time.Time of when an Item was created.
|
||||
func (i *Item) Created() (created time.Time, err error) {
|
||||
// GetSecret returns the Secret in an Item using a Session.
|
||||
func (i *Item) GetSecret(session *Session) (secret *Secret, err error) {
|
||||
|
||||
var variant dbus.Variant
|
||||
var timeInt uint64
|
||||
secret = new(Secret)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
timeInt = variant.Value().(uint64)
|
||||
|
||||
created = time.Unix(int64(timeInt), 0)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Modified returns the time.Time of when an Item was last modified along with a boolean
|
||||
that indicates if the collection has changed since the last call of Item.Modified.
|
||||
// Delete removes an Item from a Collection.
|
||||
func (i *Item) Delete() (err error) {
|
||||
|
||||
Note that when calling NewItem, the internal library-tracked modification
|
||||
time (Item.lastModified) will be set to the latest modification time of the Item
|
||||
itself as reported by Dbus rather than the time that NewItem was called.
|
||||
*/
|
||||
func (i *Item) Modified() (modified time.Time, isChanged bool, err error) {
|
||||
var prompt *Prompt
|
||||
var promptPath dbus.ObjectPath
|
||||
|
||||
var variant dbus.Variant
|
||||
var timeInt uint64
|
||||
|
||||
if variant, err = i.Dbus.GetProperty(DbusItemModified); err != nil {
|
||||
if err = i.Dbus.Call("org.freedesktop.Secret.Item.Delete", 0).Store(&promptPath); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
timeInt = variant.Value().(uint64)
|
||||
if isPrompt(promptPath) {
|
||||
prompt = NewPrompt(i.Conn, promptPath)
|
||||
|
||||
modified = time.Unix(int64(timeInt), 0)
|
||||
|
||||
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
|
||||
if _, err = prompt.Prompt(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
isChanged = modified.After(i.lastModified)
|
||||
i.lastModified = modified
|
||||
|
||||
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 (
|
||||
`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.
|
||||
func NewPrompt(conn *dbus.Conn, path dbus.ObjectPath) (prompt *Prompt) {
|
||||
|
||||
prompt = &Prompt{
|
||||
DbusObject: &DbusObject{
|
||||
Conn: conn,
|
||||
Dbus: conn.Object(DbusService, path),
|
||||
},
|
||||
Conn: conn,
|
||||
Dbus: conn.Object(DBusServiceName, path),
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Path returns the path of the underlying Dbus connection.
|
||||
func (p Prompt) Path() (path dbus.ObjectPath) {
|
||||
|
||||
// Remove this method in V1. It's bloat since we now have an exported Dbus.
|
||||
path = p.Dbus.Path()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Prompt issues/waits for a prompt for unlocking a Locked Collection or Secret / Item.
|
||||
func (p *Prompt) Prompt() (promptValue *dbus.Variant, err error) {
|
||||
|
||||
var c chan *dbus.Signal
|
||||
var result *dbus.Signal
|
||||
|
||||
promptValue = new(dbus.Variant)
|
||||
|
||||
// Prompts are asynchronous; we connect to the signal and block with a channel until we get a response.
|
||||
c = make(chan *dbus.Signal, 10)
|
||||
defer close(c)
|
||||
@@ -32,14 +37,12 @@ func (p *Prompt) Prompt() (promptValue *dbus.Variant, err error) {
|
||||
p.Conn.Signal(c)
|
||||
defer p.Conn.RemoveSignal(c)
|
||||
|
||||
if err = p.Dbus.Call(
|
||||
DbusPrompterInterface, 0, "GoSecret.Prompt", // TODO: This last argument, the string, is for "window ID". I'm unclear what for.
|
||||
).Store(); err != nil {
|
||||
if err = p.Dbus.Call("org.freedesktop.Secret.Prompt.Prompt", 0, "").Store(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
if result = <-c; result.Path == p.Dbus.Path() {
|
||||
if result = <-c; result.Path == p.Path() {
|
||||
*promptValue = result.Body[1].(dbus.Variant)
|
||||
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.
|
||||
func NewSecret(session *Session, params []byte, value []byte, contentType string) (secret *Secret) {
|
||||
|
||||
secret = &Secret{
|
||||
Session: session.Dbus.Path(),
|
||||
Session: session.Path(),
|
||||
Parameters: params,
|
||||
Value: value,
|
||||
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
|
||||
}
|
||||
417
service_funcs.go
417
service_funcs.go
@@ -1,82 +1,74 @@
|
||||
package gosecret
|
||||
package libsecret
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
`github.com/godbus/dbus`
|
||||
)
|
||||
|
||||
// NewService returns a pointer to a new Service connection.
|
||||
// NewService returns a pointer to a new Service.
|
||||
func NewService() (service *Service, err error) {
|
||||
|
||||
var svc Service = Service{
|
||||
DbusObject: &DbusObject{
|
||||
Conn: nil,
|
||||
Dbus: nil,
|
||||
},
|
||||
Session: nil,
|
||||
service = &Service{
|
||||
Conn: nil,
|
||||
Dbus: nil,
|
||||
}
|
||||
|
||||
if svc.Conn, err = dbus.SessionBus(); err != nil {
|
||||
if service.Conn, err = dbus.SessionBus(); err != nil {
|
||||
return
|
||||
}
|
||||
svc.Dbus = svc.Conn.Object(DbusService, dbus.ObjectPath(DbusPath))
|
||||
|
||||
if svc.Session, err = svc.GetSession(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
service = &svc
|
||||
service.Dbus = service.Conn.Object(DBusServiceName, dbus.ObjectPath(DBusPath))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Close cleanly closes a Service and all its underlying connections (e.g. Service.Session).
|
||||
func (s *Service) Close() (err error) {
|
||||
// Path returns the path of the underlying Dbus connection.
|
||||
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
|
||||
}
|
||||
|
||||
// Collections returns a slice of Collection items accessible to this Service.
|
||||
func (s *Service) Collections() (collections []*Collection, err error) {
|
||||
// Open returns a pointer to a Session from the Service.
|
||||
func (s *Service) Open() (session *Session, err error) {
|
||||
|
||||
var output dbus.Variant
|
||||
var path dbus.ObjectPath
|
||||
|
||||
if err = s.Dbus.Call(
|
||||
"org.freedesktop.Secret.Service.OpenSession", 0, "plain", dbus.MakeVariant(""),
|
||||
).Store(&output, &path); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
session = NewSession(s.Conn, path)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Collections returns a slice of Collection keyrings accessible to this Service.
|
||||
func (s *Service) Collections() (collections []Collection, err error) {
|
||||
|
||||
var paths []dbus.ObjectPath
|
||||
var variant dbus.Variant
|
||||
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
|
||||
}
|
||||
|
||||
paths = variant.Value().([]dbus.ObjectPath)
|
||||
|
||||
collections = make([]*Collection, len(paths))
|
||||
collections = make([]Collection, len(paths))
|
||||
|
||||
for idx, path := range paths {
|
||||
if coll, err = NewCollection(s, path); err != nil {
|
||||
// return
|
||||
errs = append(errs, err)
|
||||
err = nil
|
||||
continue
|
||||
}
|
||||
collections[idx] = coll
|
||||
collections[idx] = *NewCollection(s.Conn, path)
|
||||
}
|
||||
err = NewErrors(err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
CreateAliasedCollection creates a new Collection (keyring) via a Service with the name specified by label,
|
||||
aliased to the name specified by alias, and returns the new Collection.
|
||||
*/
|
||||
func (s *Service) CreateAliasedCollection(label, alias string) (collection *Collection, err error) {
|
||||
// CreateCollection creates a new Collection (keyring) via a Service with the name specified by label.
|
||||
func (s *Service) CreateCollection(label string) (collection *Collection, err error) {
|
||||
|
||||
var variant *dbus.Variant
|
||||
var path dbus.ObjectPath
|
||||
@@ -84,17 +76,16 @@ func (s *Service) CreateAliasedCollection(label, alias string) (collection *Coll
|
||||
var prompt *Prompt
|
||||
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(
|
||||
DbusServiceCreateCollection, 0, props, alias,
|
||||
).Store(&path, &promptPath); err != nil {
|
||||
if err = s.Dbus.Call("org.freedesktop.Secret.Service.CreateCollection", 0, props, "").Store(&path, &promptPath); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if isPrompt(promptPath) {
|
||||
|
||||
prompt = NewPrompt(s.Conn, promptPath)
|
||||
|
||||
if variant, err = prompt.Prompt(); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -102,149 +93,20 @@ func (s *Service) CreateAliasedCollection(label, alias string) (collection *Coll
|
||||
path = variant.Value().(dbus.ObjectPath)
|
||||
}
|
||||
|
||||
collection, err = NewCollection(s, path)
|
||||
collection = NewCollection(s.Conn, path)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
CreateCollection creates a new Collection (keyring) via a Service with the name specified by label and returns the new Collection.
|
||||
It is a *very* thin wrapper around Service.CreateAliasedCollection, but with a blank alias.
|
||||
*/
|
||||
func (s *Service) CreateCollection(label string) (collection *Collection, err error) {
|
||||
// Unlock unlocks a Locked Service.
|
||||
func (s *Service) Unlock(object DBusObject) (err error) {
|
||||
|
||||
collection, err = s.CreateAliasedCollection(label, "")
|
||||
|
||||
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 unlocked []dbus.ObjectPath
|
||||
var prompt *Prompt
|
||||
var promptPath dbus.ObjectPath
|
||||
var paths []dbus.ObjectPath = []dbus.ObjectPath{object.Path()}
|
||||
|
||||
if objectPaths == nil || len(objectPaths) == 0 {
|
||||
objectPaths = []dbus.ObjectPath{s.Dbus.Path()}
|
||||
}
|
||||
|
||||
if err = s.Dbus.Call(
|
||||
DbusServiceLock, 0, objectPaths,
|
||||
).Store(&locked, &promptPath); err != nil {
|
||||
if err = s.Dbus.Call("org.freedesktop.Secret.Service.Unlock", 0, paths).Store(&unlocked, &promptPath); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -260,196 +122,21 @@ func (s *Service) Lock(objectPaths ...dbus.ObjectPath) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
OpenSession returns a pointer to a Session from the Service.
|
||||
It's a convenience function around NewSession.
|
||||
*/
|
||||
func (s *Service) OpenSession(algo, input string) (session *Session, output dbus.Variant, err error) {
|
||||
|
||||
var path dbus.ObjectPath
|
||||
var inputVariant dbus.Variant
|
||||
|
||||
if strings.TrimSpace(algo) == "" {
|
||||
algo = "plain"
|
||||
}
|
||||
|
||||
inputVariant = dbus.MakeVariant(input)
|
||||
|
||||
// In *theory*, SecretService supports multiple "algorithms" for encryption in-transit, but I don't think it's implemented (yet)?
|
||||
// TODO: confirm this.
|
||||
// Possible flags are dbus.Flags consts: https://pkg.go.dev/github.com/godbus/dbus#Flags
|
||||
// Oddly, there is no "None" flag. So it's explicitly specified as a null byte.
|
||||
if err = s.Dbus.Call(
|
||||
DbusServiceOpenSession, 0, algo, inputVariant,
|
||||
).Store(&output, &path); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
session, err = NewSession(s, path)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
ReadAlias allows one to fetch a Collection based on an alias name.
|
||||
An ErrDoesNotExist will be raised if the alias does not exist.
|
||||
You will almost assuredly want to use Service.GetCollection instead; it works for both alias names and real names.
|
||||
*/
|
||||
func (s *Service) ReadAlias(alias string) (collection *Collection, err error) {
|
||||
|
||||
var objectPath dbus.ObjectPath
|
||||
|
||||
err = s.Dbus.Call(
|
||||
DbusServiceReadAlias, 0, alias,
|
||||
).Store(&objectPath)
|
||||
|
||||
/*
|
||||
TODO: Confirm that a nonexistent alias will NOT cause an error to return.
|
||||
If it does, alter the below logic.
|
||||
*/
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// If the alias does not exist, objectPath will be dbus.ObjectPath("/").
|
||||
if objectPath == dbus.ObjectPath("/") {
|
||||
err = ErrDoesNotExist
|
||||
return
|
||||
}
|
||||
|
||||
if collection, err = NewCollection(s, objectPath); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
SearchItems searches all Collection objects and returns all matches based on the map of attributes.
|
||||
*/
|
||||
func (s *Service) SearchItems(attributes map[string]string) (unlockedItems []*Item, lockedItems []*Item, err error) {
|
||||
// Lock locks an Unlocked Service.
|
||||
func (s *Service) Lock(object DBusObject) (err error) {
|
||||
|
||||
var locked []dbus.ObjectPath
|
||||
var unlocked []dbus.ObjectPath
|
||||
var collectionObjs []*Collection
|
||||
var collections map[dbus.ObjectPath]*Collection = make(map[dbus.ObjectPath]*Collection, 0)
|
||||
var ok bool
|
||||
var c *Collection
|
||||
var cPath dbus.ObjectPath
|
||||
var errs []error = make([]error, 0)
|
||||
|
||||
if attributes == nil || len(attributes) == 0 {
|
||||
err = ErrMissingAttrs
|
||||
return
|
||||
}
|
||||
|
||||
err = s.Dbus.Call(
|
||||
DbusServiceSearchItems, 0, attributes,
|
||||
).Store(&unlocked, &locked)
|
||||
|
||||
lockedItems = make([]*Item, len(locked))
|
||||
unlockedItems = make([]*Item, len(unlocked))
|
||||
|
||||
if collectionObjs, err = s.Collections(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, c = range collectionObjs {
|
||||
if _, ok = collections[c.Dbus.Path()]; !ok {
|
||||
collections[c.Dbus.Path()] = c
|
||||
}
|
||||
}
|
||||
|
||||
// Locked items
|
||||
for idx, i := range locked {
|
||||
|
||||
cPath = dbus.ObjectPath(filepath.Dir(string(i)))
|
||||
|
||||
if c, ok = collections[cPath]; !ok {
|
||||
errs = append(errs, errors.New(fmt.Sprintf(
|
||||
"could not find matching Collection for locked item %v", string(i),
|
||||
)))
|
||||
continue
|
||||
}
|
||||
|
||||
if lockedItems[idx], err = NewItem(c, i); err != nil {
|
||||
errs = append(errs, errors.New(fmt.Sprintf(
|
||||
"could not create Item for locked item %v", string(i),
|
||||
)))
|
||||
err = nil
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Unlocked items
|
||||
for idx, i := range unlocked {
|
||||
|
||||
cPath = dbus.ObjectPath(filepath.Dir(string(i)))
|
||||
|
||||
if c, ok = collections[cPath]; !ok {
|
||||
errs = append(errs, errors.New(fmt.Sprintf(
|
||||
"could not find matching Collection for unlocked item %v", string(i),
|
||||
)))
|
||||
continue
|
||||
}
|
||||
|
||||
if unlockedItems[idx], err = NewItem(c, i); err != nil {
|
||||
errs = append(errs, errors.New(fmt.Sprintf(
|
||||
"could not create Item for unlocked item %v", string(i),
|
||||
)))
|
||||
err = nil
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if errs != nil && len(errs) > 0 {
|
||||
err = NewErrors(errs...)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
SetAlias sets an alias for an existing Collection.
|
||||
To remove an alias, set objectPath to dbus.ObjectPath("/").
|
||||
*/
|
||||
func (s *Service) SetAlias(alias string, objectPath dbus.ObjectPath) (err error) {
|
||||
|
||||
var c *dbus.Call
|
||||
|
||||
c = s.Dbus.Call(
|
||||
DbusServiceSetAlias, 0, alias, objectPath,
|
||||
)
|
||||
|
||||
err = c.Err
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Unlock unlocks a Locked Service, Collection, etc.
|
||||
You can usually get objectPath for the object(s) to unlock via <object>.Dbus.Path().
|
||||
If objectPaths is nil or empty, the Service's own path will be used.
|
||||
*/
|
||||
func (s *Service) Unlock(objectPaths ...dbus.ObjectPath) (err error) {
|
||||
|
||||
var unlocked []dbus.ObjectPath
|
||||
var prompt *Prompt
|
||||
var resultPath dbus.ObjectPath
|
||||
var promptPath dbus.ObjectPath
|
||||
var paths []dbus.ObjectPath = []dbus.ObjectPath{object.Path()}
|
||||
|
||||
if objectPaths == nil || len(objectPaths) == 0 {
|
||||
objectPaths = []dbus.ObjectPath{s.Dbus.Path()}
|
||||
}
|
||||
|
||||
if err = s.Dbus.Call(
|
||||
DbusServiceUnlock, 0, objectPaths,
|
||||
).Store(&unlocked, &resultPath); err != nil {
|
||||
if err = s.Dbus.Call("org.freedesktop.Secret.Service.Lock", 0, paths).Store(&locked, &promptPath); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if isPrompt(resultPath) {
|
||||
if isPrompt(promptPath) {
|
||||
|
||||
prompt = NewPrompt(s.Conn, resultPath)
|
||||
prompt = NewPrompt(s.Conn, promptPath)
|
||||
|
||||
if _, err = prompt.Prompt(); err != nil {
|
||||
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 (
|
||||
"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) {
|
||||
|
||||
/*
|
||||
NewSession returns a pointer to a new Session based on a Service and a dbus.ObjectPath.
|
||||
You will almost always want to use Service.GetSession or Service.OpenSession instead.
|
||||
*/
|
||||
func NewSession(service *Service, path dbus.ObjectPath) (session *Session, err error) {
|
||||
|
||||
if _, err = validConnPath(service.Conn, path); err != nil {
|
||||
return
|
||||
session = &Session{
|
||||
Conn: conn,
|
||||
Dbus: conn.Object(DBusServiceName, path),
|
||||
}
|
||||
|
||||
var ssn Session = Session{
|
||||
DbusObject: &DbusObject{
|
||||
Conn: service.Conn,
|
||||
},
|
||||
service: service,
|
||||
}
|
||||
ssn.Dbus = ssn.Conn.Object(DbusInterfaceSession, path)
|
||||
|
||||
session = &ssn
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Close cleanly closes a Session.
|
||||
func (s *Session) Close() (err error) {
|
||||
// Path returns the path of the underlying Dbus connection.
|
||||
func (s Session) Path() (path dbus.ObjectPath) {
|
||||
|
||||
var c *dbus.Call
|
||||
|
||||
c = s.Dbus.Call(
|
||||
DbusSessionClose, 0,
|
||||
)
|
||||
|
||||
_ = c
|
||||
// Remove this method in V1. It's bloat since we now have an exported Dbus.
|
||||
path = s.Dbus.Path()
|
||||
|
||||
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 (
|
||||
"time"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
`github.com/godbus/dbus`
|
||||
)
|
||||
|
||||
/*
|
||||
MultiError is a type of error.Error that can contain multiple error.Errors. Confused? Don't worry about it.
|
||||
*/
|
||||
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
|
||||
// DBusObject is any type that has a Path method that returns a dbus.ObjectPath.
|
||||
type DBusObject interface {
|
||||
Path() dbus.ObjectPath
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -87,15 +16,10 @@ type Session struct {
|
||||
https://specifications.freedesktop.org/secret-service/latest/ch03.html
|
||||
*/
|
||||
type Collection struct {
|
||||
*DbusObject
|
||||
// lastModified is unexported because it's important that API users don't change it; it's used by Collection.Modified.
|
||||
lastModified time.Time
|
||||
// lastModifiedSet is unexported; it's only used to determine if this is a first-initialization of the modification time or not.
|
||||
lastModifiedSet bool
|
||||
// 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
|
||||
// Conn is an active connection to the Dbus.
|
||||
Conn *dbus.Conn
|
||||
// Dbus is the Dbus bus object.
|
||||
Dbus dbus.BusObject
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -104,21 +28,22 @@ type Collection struct {
|
||||
https://specifications.freedesktop.org/secret-service/latest/re03.html
|
||||
*/
|
||||
type Item struct {
|
||||
*DbusObject
|
||||
// Secret is the corresponding Secret object.
|
||||
Secret *Secret `json:"secret"`
|
||||
// lastModified is unexported because it's important that API users don't change it; it's used by Collection.Modified.
|
||||
lastModified time.Time
|
||||
// lastModifiedSet is unexported; it's only used to determine if this is a first-initialization of the modification time or not.
|
||||
lastModifiedSet bool
|
||||
/*
|
||||
idx is the index identifier of the Item.
|
||||
It is almost guaranteed to not match the index in Collection.Items (unless you have like, only one item)
|
||||
as those indices are static and do not determine the order that Dbus returns the list of item paths.
|
||||
*/
|
||||
idx int
|
||||
// collection tracks the Collection this Item is in.
|
||||
collection *Collection
|
||||
// Conn is an active connection to the Dbus.
|
||||
Conn *dbus.Conn
|
||||
// Dbus is the Dbus bus object.
|
||||
Dbus dbus.BusObject
|
||||
}
|
||||
|
||||
/*
|
||||
Prompt is an interface to handling unlocking prompts.
|
||||
https://developer-old.gnome.org/libsecret/0.18/SecretPrompt.html
|
||||
https://specifications.freedesktop.org/secret-service/latest/ch09.html
|
||||
*/
|
||||
type Prompt struct {
|
||||
// Conn is an active connection to the Dbus.
|
||||
Conn *dbus.Conn
|
||||
// Dbus is the Dbus bus object.
|
||||
Dbus dbus.BusObject
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -128,22 +53,36 @@ type Item struct {
|
||||
https://specifications.freedesktop.org/secret-service/latest/ch14.html#type-Secret
|
||||
*/
|
||||
type Secret struct {
|
||||
// Session is a Dbus object path for the associated Session (the actual Session is stored in an unexported field).
|
||||
Session dbus.ObjectPath `json:"session_path"`
|
||||
/*
|
||||
Parameters are "algorithm dependent parameters for secret value encoding" - likely this will just be an empty byteslice.
|
||||
Refer to Session for more information.
|
||||
*/
|
||||
Parameters []byte `json:"params"`
|
||||
// Session is a Dbus object path for the associated Session.
|
||||
Session dbus.ObjectPath
|
||||
// Parameters are "algorithm dependent parameters for secret value encoding" - likely this will just be an empty byteslice.
|
||||
Parameters []byte
|
||||
// Value is the secret's content in []byte format.
|
||||
Value SecretValue `json:"value"`
|
||||
Value []byte
|
||||
// ContentType is the MIME type of Value.
|
||||
ContentType string `json:"content_type"`
|
||||
// item is the Item this Secret belongs to.
|
||||
item *Item
|
||||
// session is the Session used to decode/decrypt this Secret.
|
||||
session *Session
|
||||
ContentType string
|
||||
}
|
||||
|
||||
// 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