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