Compare commits
4 Commits
FORK_ORIGI
...
v0.1.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
df86089517
|
|||
|
55e740793a
|
|||
|
b96f17fb19
|
|||
|
|
5975b5ed5c
|
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
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>
|
||||
7
.idea/discord.xml
generated
Normal file
7
.idea/discord.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="PROJECT" />
|
||||
<option name="description" value="" />
|
||||
</component>
|
||||
</project>
|
||||
9
.idea/gosecret.iml
generated
Normal file
9
.idea/gosecret.iml
generated
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/gosecret.iml" filepath="$PROJECT_DIR$/.idea/gosecret.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
3
LICENSE
3
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Goran Sterjov
|
||||
Copyright (c) 2021 Brent Saner
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -19,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
237
README.adoc
Normal file
237
README.adoc
Normal file
@@ -0,0 +1,237 @@
|
||||
= libsecret/gosecret
|
||||
Brent Saner <bts@square-r00t.net>
|
||||
:doctype: book
|
||||
:docinfo: shared
|
||||
:data-uri:
|
||||
:imagesdir: images
|
||||
:sectlinks:
|
||||
:sectnums:
|
||||
:sectnumlevels: 7
|
||||
:toc: preamble
|
||||
:toc2: left
|
||||
:idprefix:
|
||||
:toclevels: 7
|
||||
:source-highlighter: rouge
|
||||
|
||||
image::https://pkg.go.dev/badge/r00t2.io/gosecret.svg[link="https://pkg.go.dev/r00t2.io/gosecret"]
|
||||
|
||||
This project is originally forked from https://github.com/gsterjov/go-libsecret[go-libsecret^] due to:
|
||||
|
||||
* Lack of response from the developer
|
||||
* Complete lack of documentation
|
||||
* Poor, ineffecient, or just plain antipattern design
|
||||
* Missing functionality
|
||||
|
||||
and as such, hopefully this library should serve as a more effective libsecret/SecretService interface.
|
||||
|
||||
== Backwards Compatability/Drop-In Replacement Support
|
||||
|
||||
Version series `v0.X.X` of this library promises full and non-breaking backwards support of API interaction with the original project. The only changes should be internal optimizations, adding documentation, some file reorganizing, adding Golang module support, etc. -- all transparent from the library API itself.
|
||||
|
||||
To use this library as a replacement without significantly modifying your code, you can simply use a `replace` directive:
|
||||
|
||||
// TODO: did I do this correctly? I never really use replacements so someone PR if this is incorrect.
|
||||
.go.mod
|
||||
[source]
|
||||
----
|
||||
// ...
|
||||
replace (
|
||||
github.com/gsterjov/go-libsecret dev => r00t2.io/gosecret v0
|
||||
)
|
||||
----
|
||||
|
||||
and then run `go mod tidy`.
|
||||
|
||||
== New Developer API
|
||||
|
||||
Starting from `v1.0.0` onwards, entirely breaking changes can be assumed from the original project.
|
||||
|
||||
To use the new version,
|
||||
|
||||
[source,go]
|
||||
----
|
||||
import (
|
||||
`r00t2.io/gosecret/v1`
|
||||
)
|
||||
----
|
||||
|
||||
To reflect the absolute breaking changes, the module name changes as well from `libsecret` to `gosecret`.
|
||||
|
||||
=== Status
|
||||
|
||||
The new API is underway, and all functionality in V0 is present. However, it's not "complete". https://github.com/johnnybubonic/gosecret/pulls[PRs^] welcome, of course, but this will be an ongoing effort for a bit of time.
|
||||
|
||||
== SecretService Concepts
|
||||
|
||||
For reference:
|
||||
|
||||
* A `*Service*` allows one to retrieve and operate on/with `*Session*` and `*Collection*` objects.
|
||||
* A `*Session*` allows one to operate on/with `*Item*` objects (e.g. parsing/decoding/decrypting them).
|
||||
* A `*Collection*` allows one to retrieve and operate on/with `*Item*` objects.
|
||||
* An `*Item*` allows one to retrieve and operate on/with `*Secret*` objects.
|
||||
|
||||
(`*Secrets*` are considered "terminating objects" in this model, and contain
|
||||
actual secret value(s) and metadata).
|
||||
|
||||
Various interactions are handled by `*Prompts*`.
|
||||
|
||||
So the object hierarchy in *theory* looks kind of like this:
|
||||
|
||||
----
|
||||
Service
|
||||
├─ Session "A"
|
||||
├─ Session "B"
|
||||
├─ Collection "A"
|
||||
│ ├─ Item "A.1"
|
||||
│ │ ├─ Secret "A_1_a"
|
||||
│ │ └─ Secret "A_1_b"
|
||||
│ └─ Item "A.2"
|
||||
│ ├─ Secret "A_2_a"
|
||||
│ └─ Secret "A_2_b"
|
||||
└─ Collection "B"
|
||||
├─ Item "B.1"
|
||||
│ ├─ Secret "B_1_a"
|
||||
│ └─ Secret "B_1_b"
|
||||
└─ Item "B.2"
|
||||
├─ Secret "B_2_a"
|
||||
└─ Secret "B_2_b"
|
||||
----
|
||||
|
||||
And so on.
|
||||
|
||||
In *practice*, however, most users will only have two ``Collection``s:
|
||||
|
||||
* a default "system" one named `login` (usually unlocked upon login), and
|
||||
* a temporary one that may or may not exist, running in memory for the current login session named `session`
|
||||
|
||||
== Usage
|
||||
|
||||
Full documentation can be found via inline documentation. Either via the https://pkg.go.dev/r00t2.io/gosecret[pkg.go.dev documentation^] or https://pkg.go.dev/golang.org/x/tools/cmd/godoc[`godoc`^] (or `go doc`) in the source root.
|
||||
|
||||
However, here's a quick demonstration.
|
||||
|
||||
[source,go]
|
||||
----
|
||||
package main
|
||||
|
||||
import (
|
||||
`fmt`
|
||||
`log`
|
||||
|
||||
// "github.com/johnnybubonic/gosecret" // GitHub mirror
|
||||
"r00t2.io/gosecret" // real upstream; recommended
|
||||
)
|
||||
|
||||
const (
|
||||
// The default collection; it should be available on all SecretService implementations.
|
||||
collectionName string = "login"
|
||||
// A label for an Item used in examples below.
|
||||
exampleLabel string = "Some Website Credentials"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
var err error
|
||||
var service *gosecret.Service
|
||||
var collection *gosecret.Collection
|
||||
var item *gosecret.Item
|
||||
var itemAttrs map[string]string
|
||||
var itemLabel string
|
||||
var secret *gosecret.Secret
|
||||
|
||||
// All interactions with SecretService start with initiating a Service connection.
|
||||
if service, err = gosecret.NewService(); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
defer service.Close()
|
||||
|
||||
// And unless operating directly on a Service via its methods, you probably need a Collection as well.
|
||||
if collection, err = service.GetCollection(collectionName); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
/*
|
||||
Create a Secret which gets stored in an Item which gets stored in a Collection.
|
||||
See the documentation for details.
|
||||
*/
|
||||
// Incidentally, I believe this is the only exported function/method that does not return an error returner.
|
||||
secret = gosecret.NewSecret(
|
||||
service.Session, // The session associated with this Secret. You're likely fine with the automatically-created *(Service).Session.
|
||||
[]byte{}, // The "parameters". Likely this is an empty byteslice.
|
||||
[]byte("a super secret password"), // The actual secret value.
|
||||
"text/plain", // The content type (MIME type/media type). See https://www.iana.org/assignments/media-types/media-types.xhtml.
|
||||
)
|
||||
|
||||
/*
|
||||
Item attributes are a map[string]string of *metadata* about a Secret/Item.
|
||||
Do *NOT* store sensitive information in these.
|
||||
They're primarily used for searching for Items.
|
||||
*/
|
||||
itemAttrs = map[string]string{
|
||||
"Use": "an example secret",
|
||||
"note": "These keys can be anything you want!",
|
||||
"url": "https://somewebsite.tld/login",
|
||||
"username": "user.name",
|
||||
}
|
||||
|
||||
// And create the Item (and add it to SecretService).
|
||||
if item, err = collection.CreateItem(
|
||||
exampleLabel, // The label of the item. This should also be considered not secret.
|
||||
itemAttrs, // Attributes for the item; see above.
|
||||
secret, // The actual secret.
|
||||
true, // Whether to replace an existing item with the same label or not.
|
||||
); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
/*
|
||||
Now let's fetch the same Item via its attributes.
|
||||
The results are split into locked items and unlocked items.
|
||||
*/
|
||||
var unlockedItems []*gosecret.Item
|
||||
var lockedItems []*gosecret.Item
|
||||
|
||||
if unlockedItems, lockedItems, err = service.SearchItems(itemAttrs); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
// We should only have one Item that matches the search attributes, and unless the item or collection is locked, ...
|
||||
item = unlockedItems[0]
|
||||
if itemLabel, err = item.Label(); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
fmt.Printf("Found item: %v\n", itemLabel)
|
||||
|
||||
// Alternatively if you are unsure of the attributes but know the label of the item you want, you can iterate through them.
|
||||
var itemResults []*gosecret.Item
|
||||
|
||||
if itemResults, err = collection.Items(); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
for idx, i := range itemResults {
|
||||
if itemLabel, err = i.Label(); err != nil {
|
||||
fmt.Printf("Cannot read label for item at path '%v'\n", i.Dbus.Path())
|
||||
continue
|
||||
}
|
||||
if itemLabel != exampleLabel { // Matching against a desired label - exampleLabel, in this case.
|
||||
continue
|
||||
}
|
||||
fmt.Printf("Found item labeled '%v'! Index number %v at path '%v'\n", itemLabel, idx, i.Dbus.Path())
|
||||
fmt.Printf("Password: %v\n", string(i.Secret.Value))
|
||||
break
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
== Library Hacking
|
||||
|
||||
=== Tests
|
||||
|
||||
Many functions are consolidated into a single test due to how dependent certain processes are on other objects. However, all functionality should be covered by test cases and the error string will always be passed through the stack to `go test -v` output.
|
||||
|
||||
Obviously since this library interacts directly with Dbus (and I don't want to spend the time to mock up an entire Dbus-like interface to test), all tests are integration tests rather than unit tests. Therefore in the event of a failed run, you will need to open e.g. Seahorse or d-feet or some other Dbus/SecretService browser and manually delete the created Secret Service collection. It/they should be easily identified; they use a generated UUID4 string as the collection name and it is highly unlikely that you will see any other collections named as such. If running `go test` with the verbose flag (`-v`), the name and path of the collection will be printed out. If all tests pass, the test collection should be removed automatically.
|
||||
|
||||
The same UUID is used for all tests in a test run.
|
||||
|
||||
You may be prompted during a test run for a password; you can simply use a blank password for this as it is the password used to protect a collection. This prompt pops up during the creation of a Collection.
|
||||
124
collection.go
124
collection.go
@@ -1,124 +0,0 @@
|
||||
package libsecret
|
||||
|
||||
import "github.com/godbus/dbus"
|
||||
|
||||
|
||||
type Collection struct {
|
||||
conn *dbus.Conn
|
||||
dbus dbus.BusObject
|
||||
}
|
||||
|
||||
|
||||
func NewCollection(conn *dbus.Conn, path dbus.ObjectPath) *Collection {
|
||||
return &Collection{
|
||||
conn: conn,
|
||||
dbus: conn.Object(DBusServiceName, path),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func (collection Collection) Path() dbus.ObjectPath {
|
||||
return collection.dbus.Path()
|
||||
}
|
||||
|
||||
|
||||
// READ Array<ObjectPath> Items;
|
||||
func (collection *Collection) Items() ([]Item, error) {
|
||||
val, err := collection.dbus.GetProperty("org.freedesktop.Secret.Collection.Items")
|
||||
if err != nil {
|
||||
return []Item{}, err
|
||||
}
|
||||
|
||||
items := []Item{}
|
||||
for _, path := range val.Value().([]dbus.ObjectPath) {
|
||||
items = append(items, *NewItem(collection.conn, path))
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
|
||||
// Delete (OUT ObjectPath prompt);
|
||||
func (collection *Collection) Delete() error {
|
||||
var prompt dbus.ObjectPath
|
||||
|
||||
err := collection.dbus.Call("org.freedesktop.Secret.Collection.Delete", 0).Store(&prompt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isPrompt(prompt) {
|
||||
prompt := NewPrompt(collection.conn, prompt)
|
||||
|
||||
_, err := prompt.Prompt()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// SearchItems (IN Dict<String,String> attributes, OUT Array<ObjectPath> results);
|
||||
func (collection *Collection) SearchItems(profile string) ([]Item, error) {
|
||||
attributes := make(map[string]string)
|
||||
attributes["profile"] = profile
|
||||
|
||||
var paths []dbus.ObjectPath
|
||||
|
||||
err := collection.dbus.Call("org.freedesktop.Secret.Collection.SearchItems", 0, attributes).Store(&paths)
|
||||
if err != nil {
|
||||
return []Item{}, err
|
||||
}
|
||||
|
||||
items := []Item{}
|
||||
for _, path := range paths {
|
||||
items = append(items, *NewItem(collection.conn, path))
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
|
||||
// CreateItem (IN Dict<String,Variant> properties, IN Secret secret, IN Boolean replace, OUT ObjectPath item, OUT ObjectPath prompt);
|
||||
func (collection *Collection) CreateItem(label string, secret *Secret, replace bool) (*Item, error) {
|
||||
properties := make(map[string]dbus.Variant)
|
||||
attributes := make(map[string]string)
|
||||
|
||||
attributes["profile"] = label
|
||||
properties["org.freedesktop.Secret.Item.Label"] = dbus.MakeVariant(label)
|
||||
properties["org.freedesktop.Secret.Item.Attributes"] = dbus.MakeVariant(attributes)
|
||||
|
||||
var path dbus.ObjectPath
|
||||
var prompt dbus.ObjectPath
|
||||
|
||||
err := collection.dbus.Call("org.freedesktop.Secret.Collection.CreateItem", 0, properties, secret, replace).Store(&path, &prompt)
|
||||
if err != nil {
|
||||
return &Item{}, err
|
||||
}
|
||||
|
||||
if isPrompt(prompt) {
|
||||
prompt := NewPrompt(collection.conn, prompt)
|
||||
|
||||
result, err := prompt.Prompt()
|
||||
if err != nil {
|
||||
return &Item{}, err
|
||||
}
|
||||
|
||||
path = result.Value().(dbus.ObjectPath)
|
||||
}
|
||||
|
||||
return NewItem(collection.conn, path), nil
|
||||
}
|
||||
|
||||
|
||||
// READ Boolean Locked;
|
||||
func (collection *Collection) Locked() (bool, error) {
|
||||
val, err := collection.dbus.GetProperty("org.freedesktop.Secret.Collection.Locked")
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
return val.Value().(bool), nil
|
||||
}
|
||||
138
collection_funcs.go
Normal file
138
collection_funcs.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package libsecret
|
||||
|
||||
import (
|
||||
`github.com/godbus/dbus`
|
||||
)
|
||||
|
||||
// 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{
|
||||
Conn: conn,
|
||||
Dbus: conn.Object(DBusServiceName, path),
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Path returns the dbus.ObjectPath of the Collection.
|
||||
func (c Collection) Path() (coll dbus.ObjectPath) {
|
||||
|
||||
// Remove this method in V1. It's bloat since we now have an exported Dbus.
|
||||
coll = c.Dbus.Path()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Items returns a slice of Item items in the Collection.
|
||||
func (c *Collection) Items() (items []Item, err error) {
|
||||
|
||||
var variant dbus.Variant
|
||||
var paths []dbus.ObjectPath
|
||||
|
||||
if variant, err = c.Dbus.GetProperty("org.freedesktop.Secret.Collection.Items"); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
paths = variant.Value().([]dbus.ObjectPath)
|
||||
|
||||
items = make([]Item, len(paths))
|
||||
|
||||
for idx, path := range paths {
|
||||
items[idx] = *NewItem(c.Conn, path)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Delete removes a Collection.
|
||||
func (c *Collection) Delete() (err error) {
|
||||
|
||||
var promptPath dbus.ObjectPath
|
||||
var prompt *Prompt
|
||||
|
||||
if err = c.Dbus.Call("org.freedesktop.Secret.Collection.Delete", 0).Store(&promptPath); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if isPrompt(promptPath) {
|
||||
|
||||
prompt = NewPrompt(c.Conn, promptPath)
|
||||
if _, err = prompt.Prompt(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SearchItems searches a Collection for a matching profile string.
|
||||
func (c *Collection) SearchItems(profile string) (items []Item, err error) {
|
||||
|
||||
var paths []dbus.ObjectPath
|
||||
var attrs map[string]string = make(map[string]string, 0)
|
||||
|
||||
attrs["profile"] = profile
|
||||
|
||||
if err = c.Dbus.Call("org.freedesktop.Secret.Collection.SearchItems", 0, attrs).Store(&paths); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
items = make([]Item, len(paths))
|
||||
|
||||
for idx, path := range paths {
|
||||
items[idx] = *NewItem(c.Conn, path)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CreateItem returns a pointer to an Item based on a label, a Secret, and whether any existing secret should be replaced or not.
|
||||
func (c *Collection) CreateItem(label string, secret *Secret, replace bool) (item *Item, err error) {
|
||||
|
||||
var prompt *Prompt
|
||||
var path dbus.ObjectPath
|
||||
var promptPath dbus.ObjectPath
|
||||
var variant *dbus.Variant
|
||||
var props map[string]dbus.Variant = make(map[string]dbus.Variant)
|
||||
var attrs map[string]string = make(map[string]string)
|
||||
|
||||
attrs["profile"] = label
|
||||
props["org.freedesktop.Secret.Item.Label"] = dbus.MakeVariant(label)
|
||||
props["org.freedesktop.Secret.Item.Attributes"] = dbus.MakeVariant(attrs)
|
||||
|
||||
if err = c.Dbus.Call(
|
||||
"org.freedesktop.Secret.Collection.CreateItem", 0, props, secret, replace,
|
||||
).Store(&path, &promptPath); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if isPrompt(promptPath) {
|
||||
prompt = NewPrompt(c.Conn, promptPath)
|
||||
|
||||
if variant, err = prompt.Prompt(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
path = variant.Value().(dbus.ObjectPath)
|
||||
}
|
||||
|
||||
item = NewItem(c.Conn, path)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Locked indicates that a Collection is locked (true) or unlocked (false).
|
||||
func (c *Collection) Locked() (isLocked bool, err error) {
|
||||
|
||||
var variant dbus.Variant
|
||||
|
||||
if variant, err = c.Dbus.GetProperty("org.freedesktop.Secret.Collection.Locked"); err != nil {
|
||||
isLocked = true
|
||||
return
|
||||
}
|
||||
|
||||
isLocked = variant.Value().(bool)
|
||||
|
||||
return
|
||||
}
|
||||
7
consts.go
Normal file
7
consts.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package libsecret
|
||||
|
||||
const (
|
||||
DBusServiceName string = "org.freedesktop.secrets"
|
||||
DBusPath string = "/org/freedesktop/secrets"
|
||||
PromptPrefix string = DBusPath + "/prompt/"
|
||||
)
|
||||
88
doc.go
Normal file
88
doc.go
Normal file
@@ -0,0 +1,88 @@
|
||||
// See LICENSE in source root directory for copyright and licensing information.
|
||||
|
||||
/*
|
||||
Package gosecret is(/was originally) a fork of go-libsecret (see https://github.com/gsterjov/go-libsecret
|
||||
and https://pkg.go.dev/github.com/gsterjov/go-libsecret).
|
||||
|
||||
It was forked in order to present bugfixes, actually document the library, conform to more Go-like patterns, and
|
||||
provide missing functionality (as the original seems to be unmaintained).
|
||||
As such, hopefully this library should serve as a more effective libsecret/SecretService interface.
|
||||
|
||||
Backwards Compatibility
|
||||
|
||||
Version series `v0.X.X` of this library promises full and non-breaking backwards compatibility/drop-in
|
||||
support of API interaction with the original project.
|
||||
The only changes should be internal optimizations, adding documentation, some file reorganizing, adding Golang module support,
|
||||
etc. -- all transparent from the library API itself.
|
||||
|
||||
To use this library as a replacement without significantly modifying your code,
|
||||
you can simply use a `replace` directive in your go.mod file:
|
||||
|
||||
// ...
|
||||
replace (
|
||||
github.com/gsterjov/go-libsecret dev => r00t2.io/gosecret v0
|
||||
)
|
||||
|
||||
and then run `go mod tidy`.
|
||||
|
||||
Do NOT use the master branch. For anything. I make no promises on the stability of that branch at any given time.
|
||||
New features will be added to V1 branch, and stable releases will be tagged. V0 branch is reserved only for optimization and bug fixes.
|
||||
|
||||
New Developer API
|
||||
|
||||
Starting from `v1.0.0` onwards, entirely breaking changes can be assumed from the original project.
|
||||
To use the new version,
|
||||
|
||||
import (
|
||||
`r00t2.io/gosecret/v1`
|
||||
)
|
||||
|
||||
To reflect the absolute breaking changes, the module name changes as well from `libsecret` to `gosecret`.
|
||||
|
||||
SecretService Concepts
|
||||
|
||||
For reference:
|
||||
|
||||
- A Service allows one to retrieve and operate on/with Session and Collection objects.
|
||||
|
||||
- A Session allows one to operate on/with Item objects (e.g. parsing/decoding/decrypting them).
|
||||
|
||||
- A Collection allows one to retrieve and operate on/with Item objects.
|
||||
|
||||
- An Item allows one to retrieve and operate on/with Secret objects.
|
||||
|
||||
(Secrets are considered "terminating objects" in this model, and contain actual secret value(s) and metadata).
|
||||
|
||||
Various interactions are handled by Prompts.
|
||||
|
||||
So the object hierarchy in THEORY looks kind of like this:
|
||||
|
||||
Service
|
||||
├─ Session "A"
|
||||
├─ Session "B"
|
||||
├─ Collection "A"
|
||||
│ ├─ Item "A.1"
|
||||
│ │ ├─ Secret "A_1_a"
|
||||
│ │ └─ Secret "A_1_b"
|
||||
│ └─ Item "A.2"
|
||||
│ ├─ Secret "A_2_a"
|
||||
│ └─ Secret "A_2_b"
|
||||
└─ Collection "B"
|
||||
├─ Item "B.1"
|
||||
│ ├─ Secret "B_1_a"
|
||||
│ └─ Secret "B_1_b"
|
||||
└─ Item "B.2"
|
||||
├─ Secret "B_2_a"
|
||||
└─ Secret "B_2_b"
|
||||
|
||||
And so on.
|
||||
In PRACTICE, however, most users will only have two Collection items
|
||||
(a default "system" one named "login", which usually is unlocked upon login,
|
||||
and a temporary one that may or may not exist, running in memory for the current login session named `session`).
|
||||
|
||||
Usage
|
||||
|
||||
Full documentation can be found via inline documentation.
|
||||
Additionally, use either https://pkg.go.dev/r00t2.io/gosecret or https://pkg.go.dev/golang.org/x/tools/cmd/godoc (or `go doc`) in the source root.
|
||||
*/
|
||||
package gosecret
|
||||
16
funcs.go
Normal file
16
funcs.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package libsecret
|
||||
|
||||
import (
|
||||
`strings`
|
||||
|
||||
`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), PromptPrefix)
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
5
go.mod
Normal file
5
go.mod
Normal file
@@ -0,0 +1,5 @@
|
||||
module r00t2.io/gosecret
|
||||
|
||||
go 1.17
|
||||
|
||||
require github.com/godbus/dbus v4.1.0+incompatible
|
||||
2
go.sum
Normal file
2
go.sum
Normal file
@@ -0,0 +1,2 @@
|
||||
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
|
||||
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||
77
item.go
77
item.go
@@ -1,77 +0,0 @@
|
||||
package libsecret
|
||||
|
||||
import "github.com/godbus/dbus"
|
||||
|
||||
|
||||
type Item struct {
|
||||
conn *dbus.Conn
|
||||
dbus dbus.BusObject
|
||||
}
|
||||
|
||||
|
||||
func NewItem(conn *dbus.Conn, path dbus.ObjectPath) *Item {
|
||||
return &Item{
|
||||
conn: conn,
|
||||
dbus: conn.Object(DBusServiceName, path),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func (item Item) Path() dbus.ObjectPath {
|
||||
return item.dbus.Path()
|
||||
}
|
||||
|
||||
|
||||
// READWRITE String Label;
|
||||
func (item *Item) Label() (string, error) {
|
||||
val, err := item.dbus.GetProperty("org.freedesktop.Secret.Item.Label")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return val.Value().(string), nil
|
||||
}
|
||||
|
||||
|
||||
// READ Boolean Locked;
|
||||
func (item *Item) Locked() (bool, error) {
|
||||
val, err := item.dbus.GetProperty("org.freedesktop.Secret.Item.Locked")
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
return val.Value().(bool), nil
|
||||
}
|
||||
|
||||
|
||||
// GetSecret (IN ObjectPath session, OUT Secret secret);
|
||||
func (item *Item) GetSecret(session *Session) (*Secret, error) {
|
||||
secret := Secret{}
|
||||
|
||||
err := item.dbus.Call("org.freedesktop.Secret.Item.GetSecret", 0, session.Path()).Store(&secret)
|
||||
if err != nil {
|
||||
return &Secret{}, err
|
||||
}
|
||||
|
||||
return &secret, nil
|
||||
}
|
||||
|
||||
|
||||
// Delete (OUT ObjectPath Prompt);
|
||||
func (item *Item) Delete() error {
|
||||
var prompt dbus.ObjectPath
|
||||
|
||||
err := item.dbus.Call("org.freedesktop.Secret.Item.Delete", 0).Store(&prompt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isPrompt(prompt) {
|
||||
prompt := NewPrompt(item.conn, prompt)
|
||||
if _, err := prompt.Prompt(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
89
item_funcs.go
Normal file
89
item_funcs.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package libsecret
|
||||
|
||||
import (
|
||||
`github.com/godbus/dbus`
|
||||
)
|
||||
|
||||
// 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{
|
||||
Conn: conn,
|
||||
Dbus: conn.Object(DBusServiceName, path),
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Path returns the path of the underlying Dbus connection.
|
||||
func (i Item) Path() (path dbus.ObjectPath) {
|
||||
|
||||
// Remove this method in V1. It's bloat since we now have an exported Dbus.
|
||||
path = i.Dbus.Path()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Label returns the label ("name") of an Item.
|
||||
func (i *Item) Label() (label string, err error) {
|
||||
|
||||
var variant dbus.Variant
|
||||
|
||||
if variant, err = i.Dbus.GetProperty("org.freedesktop.Secret.Item.Label"); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
label = variant.Value().(string)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 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("org.freedesktop.Secret.Item.Locked"); err != nil {
|
||||
isLocked = true
|
||||
return
|
||||
}
|
||||
|
||||
isLocked = variant.Value().(bool)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetSecret returns the Secret in an Item using a Session.
|
||||
func (i *Item) GetSecret(session *Session) (secret *Secret, err error) {
|
||||
|
||||
secret = new(Secret)
|
||||
|
||||
if err = i.Dbus.Call(
|
||||
"org.freedesktop.Secret.Item.GetSecret", 0, session.Path(),
|
||||
).Store(&secret); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Delete removes an Item from a Collection.
|
||||
func (i *Item) Delete() (err error) {
|
||||
|
||||
var prompt *Prompt
|
||||
var promptPath dbus.ObjectPath
|
||||
|
||||
if err = i.Dbus.Call("org.freedesktop.Secret.Item.Delete", 0).Store(&promptPath); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if isPrompt(promptPath) {
|
||||
prompt = NewPrompt(i.Conn, promptPath)
|
||||
|
||||
if _, err = prompt.Prompt(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
55
prompt.go
55
prompt.go
@@ -1,55 +0,0 @@
|
||||
package libsecret
|
||||
|
||||
import (
|
||||
"github.com/godbus/dbus"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
||||
type Prompt struct {
|
||||
conn *dbus.Conn
|
||||
dbus dbus.BusObject
|
||||
}
|
||||
|
||||
|
||||
func NewPrompt(conn *dbus.Conn, path dbus.ObjectPath) *Prompt {
|
||||
return &Prompt{
|
||||
conn: conn,
|
||||
dbus: conn.Object(DBusServiceName, path),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func (prompt Prompt) Path() dbus.ObjectPath {
|
||||
return prompt.dbus.Path()
|
||||
}
|
||||
|
||||
|
||||
func isPrompt(path dbus.ObjectPath) bool {
|
||||
promptPath := DBusPath + "/prompt/"
|
||||
return strings.HasPrefix(string(path), promptPath)
|
||||
}
|
||||
|
||||
|
||||
// Prompt (IN String window-id);
|
||||
func (prompt *Prompt) Prompt() (*dbus.Variant, error) {
|
||||
// prompts are asynchronous so we connect to the signal
|
||||
// and block with a channel until we get a response
|
||||
c := make(chan *dbus.Signal, 10)
|
||||
defer close(c)
|
||||
|
||||
prompt.conn.Signal(c)
|
||||
defer prompt.conn.RemoveSignal(c)
|
||||
|
||||
err := prompt.dbus.Call("org.freedesktop.Secret.Prompt.Prompt", 0, "").Store()
|
||||
if err != nil {
|
||||
return &dbus.Variant{}, err
|
||||
}
|
||||
|
||||
for {
|
||||
if result := <-c; result.Path == prompt.Path() {
|
||||
value := result.Body[1].(dbus.Variant)
|
||||
return &value, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
50
prompt_funcs.go
Normal file
50
prompt_funcs.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package libsecret
|
||||
|
||||
import (
|
||||
`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{
|
||||
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
|
||||
|
||||
// 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)
|
||||
|
||||
p.Conn.Signal(c)
|
||||
defer p.Conn.RemoveSignal(c)
|
||||
|
||||
if err = p.Dbus.Call("org.freedesktop.Secret.Prompt.Prompt", 0, "").Store(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
if result = <-c; result.Path == p.Path() {
|
||||
*promptValue = result.Body[1].(dbus.Variant)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
21
secret.go
21
secret.go
@@ -1,21 +0,0 @@
|
||||
package libsecret
|
||||
|
||||
import "github.com/godbus/dbus"
|
||||
|
||||
|
||||
type Secret struct {
|
||||
Session dbus.ObjectPath
|
||||
Parameters []byte
|
||||
Value []byte
|
||||
ContentType string
|
||||
}
|
||||
|
||||
|
||||
func NewSecret(session *Session, params []byte, value []byte, contentType string) *Secret {
|
||||
return &Secret{
|
||||
Session: session.Path(),
|
||||
Parameters: params,
|
||||
Value: value,
|
||||
ContentType: contentType,
|
||||
}
|
||||
}
|
||||
14
secret_funcs.go
Normal file
14
secret_funcs.go
Normal file
@@ -0,0 +1,14 @@
|
||||
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.Path(),
|
||||
Parameters: params,
|
||||
Value: value,
|
||||
ContentType: contentType,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
141
service.go
141
service.go
@@ -1,141 +0,0 @@
|
||||
package libsecret
|
||||
|
||||
import "github.com/godbus/dbus"
|
||||
|
||||
|
||||
const (
|
||||
DBusServiceName = "org.freedesktop.secrets"
|
||||
DBusPath = "/org/freedesktop/secrets"
|
||||
)
|
||||
|
||||
type DBusObject interface {
|
||||
Path() dbus.ObjectPath
|
||||
}
|
||||
|
||||
|
||||
type Service struct {
|
||||
conn *dbus.Conn
|
||||
dbus dbus.BusObject
|
||||
}
|
||||
|
||||
|
||||
func NewService() (*Service, error) {
|
||||
conn, err := dbus.SessionBus()
|
||||
if err != nil {
|
||||
return &Service{}, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
conn: conn,
|
||||
dbus: conn.Object(DBusServiceName, DBusPath),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
func (service Service) Path() dbus.ObjectPath {
|
||||
return service.dbus.Path()
|
||||
}
|
||||
|
||||
|
||||
// OpenSession (IN String algorithm, IN Variant input, OUT Variant output, OUT ObjectPath result);
|
||||
func (service *Service) Open() (*Session, error) {
|
||||
var output dbus.Variant
|
||||
var path dbus.ObjectPath
|
||||
|
||||
err := service.dbus.Call("org.freedesktop.Secret.Service.OpenSession", 0, "plain", dbus.MakeVariant("")).Store(&output, &path)
|
||||
if err != nil {
|
||||
return &Session{}, err
|
||||
}
|
||||
|
||||
return NewSession(service.conn, path), nil
|
||||
}
|
||||
|
||||
|
||||
// READ Array<ObjectPath> Collections;
|
||||
func (service *Service) Collections() ([]Collection, error) {
|
||||
val, err := service.dbus.GetProperty("org.freedesktop.Secret.Service.Collections")
|
||||
if err != nil {
|
||||
return []Collection{}, err
|
||||
}
|
||||
|
||||
collections := []Collection{}
|
||||
for _, path := range val.Value().([]dbus.ObjectPath) {
|
||||
collections = append(collections, *NewCollection(service.conn, path))
|
||||
}
|
||||
|
||||
return collections, nil
|
||||
}
|
||||
|
||||
|
||||
// CreateCollection (IN Dict<String,Variant> properties, IN String alias, OUT ObjectPath collection, OUT ObjectPath prompt);
|
||||
func (service *Service) CreateCollection(label string) (*Collection, error) {
|
||||
properties := make(map[string]dbus.Variant)
|
||||
properties["org.freedesktop.Secret.Collection.Label"] = dbus.MakeVariant(label)
|
||||
|
||||
var path dbus.ObjectPath
|
||||
var prompt dbus.ObjectPath
|
||||
|
||||
err := service.dbus.Call("org.freedesktop.Secret.Service.CreateCollection", 0, properties, "").Store(&path, &prompt)
|
||||
if err != nil {
|
||||
return &Collection{}, err
|
||||
}
|
||||
|
||||
if isPrompt(prompt) {
|
||||
prompt := NewPrompt(service.conn, prompt)
|
||||
|
||||
result, err := prompt.Prompt()
|
||||
if err != nil {
|
||||
return &Collection{}, err
|
||||
}
|
||||
|
||||
path = result.Value().(dbus.ObjectPath)
|
||||
}
|
||||
|
||||
return NewCollection(service.conn, path), nil
|
||||
}
|
||||
|
||||
|
||||
// Unlock (IN Array<ObjectPath> objects, OUT Array<ObjectPath> unlocked, OUT ObjectPath prompt);
|
||||
func (service *Service) Unlock(object DBusObject) error {
|
||||
objects := []dbus.ObjectPath{object.Path()}
|
||||
|
||||
var unlocked []dbus.ObjectPath
|
||||
var prompt dbus.ObjectPath
|
||||
|
||||
err := service.dbus.Call("org.freedesktop.Secret.Service.Unlock", 0, objects).Store(&unlocked, &prompt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isPrompt(prompt) {
|
||||
prompt := NewPrompt(service.conn, prompt)
|
||||
if _, err := prompt.Prompt(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// Lock (IN Array<ObjectPath> objects, OUT Array<ObjectPath> locked, OUT ObjectPath Prompt);
|
||||
func (service *Service) Lock(object DBusObject) error {
|
||||
objects := []dbus.ObjectPath{object.Path()}
|
||||
|
||||
var locked []dbus.ObjectPath
|
||||
var prompt dbus.ObjectPath
|
||||
|
||||
err := service.dbus.Call("org.freedesktop.Secret.Service.Lock", 0, objects).Store(&locked, &prompt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isPrompt(prompt) {
|
||||
prompt := NewPrompt(service.conn, prompt)
|
||||
if _, err := prompt.Prompt(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
147
service_funcs.go
Normal file
147
service_funcs.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package libsecret
|
||||
|
||||
import (
|
||||
`github.com/godbus/dbus`
|
||||
)
|
||||
|
||||
// NewService returns a pointer to a new Service.
|
||||
func NewService() (service *Service, err error) {
|
||||
|
||||
service = &Service{
|
||||
Conn: nil,
|
||||
Dbus: nil,
|
||||
}
|
||||
|
||||
if service.Conn, err = dbus.SessionBus(); err != nil {
|
||||
return
|
||||
}
|
||||
service.Dbus = service.Conn.Object(DBusServiceName, dbus.ObjectPath(DBusPath))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Path returns the path of the underlying Dbus connection.
|
||||
func (s Service) Path() (path dbus.ObjectPath) {
|
||||
|
||||
// Remove this method in V1. It's bloat since we now have an exported Dbus.
|
||||
path = s.Dbus.Path()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Open returns a pointer to a Session from the Service.
|
||||
func (s *Service) Open() (session *Session, err error) {
|
||||
|
||||
var output dbus.Variant
|
||||
var path dbus.ObjectPath
|
||||
|
||||
if err = s.Dbus.Call(
|
||||
"org.freedesktop.Secret.Service.OpenSession", 0, "plain", dbus.MakeVariant(""),
|
||||
).Store(&output, &path); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
session = NewSession(s.Conn, path)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Collections returns a slice of Collection keyrings accessible to this Service.
|
||||
func (s *Service) Collections() (collections []Collection, err error) {
|
||||
|
||||
var paths []dbus.ObjectPath
|
||||
var variant dbus.Variant
|
||||
|
||||
if variant, err = s.Dbus.GetProperty("org.freedesktop.Secret.Service.Collections"); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
paths = variant.Value().([]dbus.ObjectPath)
|
||||
|
||||
collections = make([]Collection, len(paths))
|
||||
|
||||
for idx, path := range paths {
|
||||
collections[idx] = *NewCollection(s.Conn, path)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CreateCollection creates a new Collection (keyring) via a Service with the name specified by label.
|
||||
func (s *Service) CreateCollection(label string) (collection *Collection, err error) {
|
||||
|
||||
var variant *dbus.Variant
|
||||
var path dbus.ObjectPath
|
||||
var promptPath dbus.ObjectPath
|
||||
var prompt *Prompt
|
||||
var props map[string]dbus.Variant = make(map[string]dbus.Variant)
|
||||
|
||||
props["org.freedesktop.Secret.Collection.Label"] = dbus.MakeVariant(label)
|
||||
|
||||
if err = s.Dbus.Call("org.freedesktop.Secret.Service.CreateCollection", 0, props, "").Store(&path, &promptPath); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if isPrompt(promptPath) {
|
||||
|
||||
prompt = NewPrompt(s.Conn, promptPath)
|
||||
|
||||
if variant, err = prompt.Prompt(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
path = variant.Value().(dbus.ObjectPath)
|
||||
}
|
||||
|
||||
collection = NewCollection(s.Conn, path)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Unlock unlocks a Locked Service.
|
||||
func (s *Service) Unlock(object DBusObject) (err error) {
|
||||
|
||||
var unlocked []dbus.ObjectPath
|
||||
var prompt *Prompt
|
||||
var promptPath dbus.ObjectPath
|
||||
var paths []dbus.ObjectPath = []dbus.ObjectPath{object.Path()}
|
||||
|
||||
if err = s.Dbus.Call("org.freedesktop.Secret.Service.Unlock", 0, paths).Store(&unlocked, &promptPath); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if isPrompt(promptPath) {
|
||||
|
||||
prompt = NewPrompt(s.Conn, promptPath)
|
||||
|
||||
if _, err = prompt.Prompt(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Lock locks an Unlocked Service.
|
||||
func (s *Service) Lock(object DBusObject) (err error) {
|
||||
|
||||
var locked []dbus.ObjectPath
|
||||
var prompt *Prompt
|
||||
var promptPath dbus.ObjectPath
|
||||
var paths []dbus.ObjectPath = []dbus.ObjectPath{object.Path()}
|
||||
|
||||
if err = s.Dbus.Call("org.freedesktop.Secret.Service.Lock", 0, paths).Store(&locked, &promptPath); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if isPrompt(promptPath) {
|
||||
|
||||
prompt = NewPrompt(s.Conn, promptPath)
|
||||
|
||||
if _, err = prompt.Prompt(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
22
session.go
22
session.go
@@ -1,22 +0,0 @@
|
||||
package libsecret
|
||||
|
||||
import "github.com/godbus/dbus"
|
||||
|
||||
|
||||
type Session struct {
|
||||
conn *dbus.Conn
|
||||
dbus dbus.BusObject
|
||||
}
|
||||
|
||||
|
||||
func NewSession(conn *dbus.Conn, path dbus.ObjectPath) *Session {
|
||||
return &Session{
|
||||
conn: conn,
|
||||
dbus: conn.Object(DBusServiceName, path),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func (session Session) Path() dbus.ObjectPath {
|
||||
return session.dbus.Path()
|
||||
}
|
||||
25
session_funcs.go
Normal file
25
session_funcs.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package libsecret
|
||||
|
||||
import (
|
||||
`github.com/godbus/dbus`
|
||||
)
|
||||
|
||||
// NewSession returns a pointer to a new Session based on a Dbus connection and a Dbus path.
|
||||
func NewSession(conn *dbus.Conn, path dbus.ObjectPath) (session *Session) {
|
||||
|
||||
session = &Session{
|
||||
Conn: conn,
|
||||
Dbus: conn.Object(DBusServiceName, path),
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Path returns the path of the underlying Dbus connection.
|
||||
func (s Session) Path() (path dbus.ObjectPath) {
|
||||
|
||||
// Remove this method in V1. It's bloat since we now have an exported Dbus.
|
||||
path = s.Dbus.Path()
|
||||
|
||||
return
|
||||
}
|
||||
88
types.go
Normal file
88
types.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package libsecret
|
||||
|
||||
import (
|
||||
`github.com/godbus/dbus`
|
||||
)
|
||||
|
||||
// DBusObject is any type that has a Path method that returns a dbus.ObjectPath.
|
||||
type DBusObject interface {
|
||||
Path() dbus.ObjectPath
|
||||
}
|
||||
|
||||
/*
|
||||
Collection is an accessor for libsecret collections, which contain multiple Secret Item items.
|
||||
Reference:
|
||||
https://developer-old.gnome.org/libsecret/0.18/SecretCollection.html
|
||||
https://specifications.freedesktop.org/secret-service/latest/ch03.html
|
||||
*/
|
||||
type Collection struct {
|
||||
// Conn is an active connection to the Dbus.
|
||||
Conn *dbus.Conn
|
||||
// Dbus is the Dbus bus object.
|
||||
Dbus dbus.BusObject
|
||||
}
|
||||
|
||||
/*
|
||||
Item is an entry in a Collection that contains a Secret.
|
||||
https://developer-old.gnome.org/libsecret/0.18/SecretItem.html
|
||||
https://specifications.freedesktop.org/secret-service/latest/re03.html
|
||||
*/
|
||||
type Item struct {
|
||||
// Conn is an active connection to the Dbus.
|
||||
Conn *dbus.Conn
|
||||
// Dbus is the Dbus bus object.
|
||||
Dbus dbus.BusObject
|
||||
}
|
||||
|
||||
/*
|
||||
Prompt is an interface to handling unlocking prompts.
|
||||
https://developer-old.gnome.org/libsecret/0.18/SecretPrompt.html
|
||||
https://specifications.freedesktop.org/secret-service/latest/ch09.html
|
||||
*/
|
||||
type Prompt struct {
|
||||
// Conn is an active connection to the Dbus.
|
||||
Conn *dbus.Conn
|
||||
// Dbus is the Dbus bus object.
|
||||
Dbus dbus.BusObject
|
||||
}
|
||||
|
||||
/*
|
||||
Secret is the "Good Stuff" - the actual secret content.
|
||||
https://developer-old.gnome.org/libsecret/0.18/SecretValue.html
|
||||
https://specifications.freedesktop.org/secret-service/latest/re03.html
|
||||
https://specifications.freedesktop.org/secret-service/latest/ch14.html#type-Secret
|
||||
*/
|
||||
type Secret struct {
|
||||
// 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 []byte
|
||||
// ContentType is the MIME type of Value.
|
||||
ContentType string
|
||||
}
|
||||
|
||||
/*
|
||||
Service is a general SecretService interface, sort of handler for Dbus - it's used for fetching a Session, Collections, etc.
|
||||
https://developer-old.gnome.org/libsecret/0.18/SecretService.html
|
||||
https://specifications.freedesktop.org/secret-service/latest/re01.html
|
||||
*/
|
||||
type Service struct {
|
||||
// Conn is an active connection to the Dbus.
|
||||
Conn *dbus.Conn
|
||||
// Dbus is the Dbus bus object.
|
||||
Dbus dbus.BusObject
|
||||
}
|
||||
|
||||
/*
|
||||
Session is a session/instance/connection to SecretService.
|
||||
https://developer-old.gnome.org/libsecret/0.18/SecretService.html
|
||||
https://specifications.freedesktop.org/secret-service/latest/ch06.html
|
||||
*/
|
||||
type Session struct {
|
||||
// Conn is an active connection to the Dbus.
|
||||
Conn *dbus.Conn
|
||||
// Dbus is the Dbus bus object.
|
||||
Dbus dbus.BusObject
|
||||
}
|
||||
Reference in New Issue
Block a user