package keychainlinker import ( "fmt" "strconv" "github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5/introspect" ) type CacheItem struct { Secret SecretStruct Label string LookupProps map[string]string } type Collection struct { /* Implying the org.freedesktop.Secret.Collection interface as per the v0.2 spec: https://specifications.freedesktop.org/secret-service-spec/latest-single/#org.freedesktop.Secret.Collection */ Items []dbus.ObjectPath // items in the collection Cache map[dbus.ObjectPath]CacheItem // memory cache for the collection. Will be removed later PathCount int PathBase string Private string // specifies whether the collection is private or not Label string // The displayable label of this collection. Locked string // Whether the collection is locked and must be authenticated by the client application. Created uint64 // The unix time when the collection was created. Modified uint64 // The unix time when the collection was last modified. } func (c *Collection) GetAll(iface string) (map[string]dbus.Variant, *dbus.Error) { if iface == "org.freedesktop.Secret.Collection" { return map[string]dbus.Variant{ "Label": dbus.MakeVariant(c.Label), "Locked": dbus.MakeVariant(c.Locked), "Created": dbus.MakeVariant(c.Created), "Modified": dbus.MakeVariant(c.Modified), "Items": dbus.MakeVariant(c.Items), }, nil } return nil, dbus.MakeFailedError(fmt.Errorf("no such interface")) } /* Create a path to assign the secret */ func (c *Collection) pathBuilder() dbus.ObjectPath { return dbus.ObjectPath(c.PathBase + "/" + strconv.Itoa(c.PathCount+1)) } // deletes the collection, returning an object path tied to a prompt incase it is necessary. func (c *Collection) Delete() (dbus.ObjectPath, *dbus.Error) { return dbus.ObjectPath("prompt"), nil } /* Searches the collection for matching items :param attr: the attributes to attempt to match to a key in the collection */ func (c *Collection) SearchItems(attr map[string]string) ([]dbus.ObjectPath, *dbus.Error) { fmt.Println("recv call for SearchItems") matched := []dbus.ObjectPath{} for path, sec := range c.Cache { secretAttr := sec.LookupProps var passedLookup bool passedLookup = false for k, v := range attr { got, ok := secretAttr[k] if !ok { continue } if got == v { passedLookup = true continue } } if passedLookup { matched = append(matched, path) } } return matched, nil } /* Creates a new item in the collection with the properties defined in 'props'. Returns the items dbus object path, as well as a path to a dbus prompt incase it is required to edit :param props: a map of properties to assign to the item. Will be used to match during lookups :param secret: the secret to encode into the collection :param replace: replace secret if a matching one is found in the store */ func (c *Collection) CreateItem(props map[string]dbus.Variant, secret SecretStruct, replace bool) (dbus.ObjectPath, dbus.ObjectPath, *dbus.Error) { v := props["org.freedesktop.Secret.Item.Attributes"] attrs, ok := v.Value().(map[string]string) if !ok { return dbus.ObjectPath("/"), dbus.ObjectPath("/"), &dbus.ErrMsgNoObject } label, ok := props["org.freedesktop.Secret.Item.Label"].Value().(string) if !ok { // no label found label = "" } if !replace { // implement the the replace option } path := c.pathBuilder() c.Cache[path] = CacheItem{ LookupProps: attrs, Label: label, Secret: secret, } return path, dbus.ObjectPath("/"), nil } // implementing method to read the object property func (c *Collection) Get(iface, property string) (dbus.Variant, *dbus.Error) { if iface != "org/freedesktop/secret/service" { return dbus.Variant{}, dbus.MakeFailedError(dbus.ErrMsgUnknownInterface) } switch property { case "Items": return dbus.MakeVariant(c.Items), nil case "Label": return dbus.MakeVariant(c.Label), nil case "Locked": return dbus.MakeVariant(c.Locked), nil case "Created": return dbus.MakeVariant(c.Created), nil case "Modified": return dbus.MakeVariant(c.Modified), nil default: return dbus.Variant{}, dbus.MakeFailedError(dbus.ErrMsgUnknownMethod) } } // implementing method to read the object property func (c *Collection) Set(iface, property string, value dbus.Variant) *dbus.Error { if iface != "org/freedesktop/secret/service" { return dbus.MakeFailedError(dbus.ErrMsgUnknownInterface) } switch property { case "Label": label, ok := value.Value().(string) if !ok { return dbus.MakeFailedError(dbus.ErrMsgInvalidArg) } c.Label = label return nil default: return dbus.MakeFailedError(dbus.ErrMsgUnknownMethod) } } var CollectionNode = introspect.Node{ Name: "/org/freedesktop/secrets/collection/default", // or your collection path Interfaces: []introspect.Interface{ { Name: "org.freedesktop.Secret.Collection", Methods: []introspect.Method{ { Name: "CreateItem", Args: []introspect.Arg{ {Name: "properties", Type: "a{sv}", Direction: "in"}, {Name: "secret", Type: "v", Direction: "in"}, {Name: "replace", Type: "b", Direction: "in"}, {Name: "item", Type: "o", Direction: "out"}, {Name: "prompt", Type: "o", Direction: "out"}, }, }, { Name: "SearchItems", Args: []introspect.Arg{ {Name: "attributes", Type: "a{ss}", Direction: "in"}, {Name: "unlocked", Type: "ao", Direction: "out"}, {Name: "locked", Type: "ao", Direction: "out"}, }, }, { Name: "Delete", Args: []introspect.Arg{ {Name: "prompt", Type: "o", Direction: "out"}, }, }, }, Properties: []introspect.Property{ {Name: "Items", Type: "ao", Access: "read"}, {Name: "Label", Type: "s", Access: "readwrite"}, {Name: "Locked", Type: "b", Access: "read"}, {Name: "Created", Type: "t", Access: "read"}, {Name: "Modified", Type: "t", Access: "read"}, }, }, { Name: "org.freedesktop.DBus.Introspectable", Methods: []introspect.Method{ { Name: "Introspect", Args: []introspect.Arg{ {Name: "data", Type: "s", Direction: "out"}, }, }, }, }, }, }