aeth 4 ماه پیش
والد
کامیت
a7b907b5a5
6فایلهای تغییر یافته به همراه277 افزوده شده و 25 حذف شده
  1. 8 8
      main.go
  2. 119 12
      pkg/collection.go
  3. 81 4
      pkg/service.go
  4. 40 1
      pkg/xml.go
  5. 29 0
      scripts/test_add.py
  6. BIN
      test

+ 8 - 8
main.go

@@ -16,16 +16,16 @@ func main() {
 	}
 	defer conn.Close()
 
-	path := "/dev/aetherial/KeychainLinker"
-	session := &keychainlinker.Service{SessionBase: "/dev/aetherial/KeychainLinker/session/",
-		CollectionBase: "/dev/aetherial/KeychainLinker/collection/",
-		Collections:    []dbus.ObjectPath{}}
+	path := "/org/freedesktop/secrets"
+	service := keychainlinker.NewService(dbus.ObjectPath(path))
+
+	conn.Export(service, dbus.ObjectPath(path), "org.freedesktop.Secret.Service")
+	conn.Export(service.Cache["/org/freedesktop/secrets/collection/default"], "/org/freedesktop/secrets/collection/default", "org.freedesktop.DBus.Properties")
 
-	conn.Export(session, dbus.ObjectPath(path), "dev.aetherial.git.KeychainLinker.Service")
 	conn.Export(introspect.Introspectable(keychainlinker.DbusAdv), dbus.ObjectPath(path),
 		"org.freedesktop.DBus.Introspectable")
-
-	reply, err := conn.RequestName("dev.aetherial.git.KeychainLinker.Service",
+	conn.Export(introspect.Introspectable(keychainlinker.DbusAdv), "/org/freedesktop/secrets/collection/default", "org.freedesktop.DBus.Introspectable")
+	reply, err := conn.RequestName("org.freedesktop.secrets",
 		dbus.NameFlagDoNotQueue)
 	if err != nil {
 		panic(err)
@@ -34,6 +34,6 @@ func main() {
 		fmt.Fprintln(os.Stderr, "name already taken")
 		os.Exit(1)
 	}
-	fmt.Println("Listening on dev.aetherial.git.KeychainLinker.Service / /dev/aetherial/git/KeychainLinker/Service ...")
+	fmt.Println("Listening on org.freedesktop.secrets / /org/freedesktop/secrets ...")
 	select {}
 }

+ 119 - 12
pkg/collection.go

@@ -1,18 +1,84 @@
 package keychainlinker
 
-import "github.com/godbus/dbus/v5"
+import (
+	"fmt"
+	"strconv"
+
+	"github.com/godbus/dbus/v5"
+)
+
+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
-	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.
+	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) Get(iface, property string) (dbus.Variant, *dbus.Error) {
+	if iface != "org.freedesktop.Secret.Collection" {
+		return dbus.Variant{}, dbus.MakeFailedError(fmt.Errorf("no such property"))
+	}
+	switch property {
+	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
+	case "Items":
+		return dbus.MakeVariant(c.Items), nil
+	}
+	return dbus.Variant{}, dbus.MakeFailedError(fmt.Errorf("no such property"))
+
+}
+
+func (c *Collection) Set(iface, property string, value dbus.Variant) *dbus.Error {
+	if iface == "org.freedesktop.Secret.Collection" && property == "Label" {
+		if label, ok := value.Value().(string); ok {
+			c.Label = label
+			return nil
+		}
+		return dbus.MakeFailedError(fmt.Errorf("invalid type"))
+	}
+	return dbus.MakeFailedError(fmt.Errorf("no such property"))
+}
+
+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.
@@ -26,18 +92,59 @@ 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) {
-	// implement a recursive searching thing
-	return []dbus.ObjectPath{}, nil
+	matched := []dbus.ObjectPath{}
+	for path, sec := range c.Cache {
+		secretAttr := sec.LookupProps
+		var passedLookup bool
+		passedLookup = true
+		for k, v := range attr {
+			got, ok := secretAttr[k]
+			if !ok {
+				passedLookup = false
+				continue
+			}
+			if got != v {
+				passedLookup = false
+				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 fields: a map of properties to assign to the item. Will be used to match during lookups
+	: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(fields map[string]dbus.Variant, secret SecretStruct, replace bool) (dbus.ObjectPath, dbus.ObjectPath, *dbus.Error) {
-	return dbus.ObjectPath("/"), dbus.ObjectPath("/"), nil
+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
 }

+ 81 - 4
pkg/service.go

@@ -3,20 +3,81 @@ package keychainlinker
 import (
 	"fmt"
 	"path"
+	"strconv"
+	"time"
 
 	"github.com/godbus/dbus/v5"
 )
 
+const DEFAULT_COLLECTION = "/org/freedesktop/secrets/aliases/default"
+
+type Cache struct {
+	Collections map[dbus.ObjectPath]Collection
+}
+
 type Service struct {
 	/*
 		Working on implementing the org.freedesktop.Secret.Service interface, from their v0.2 spec:
 		https://specifications.freedesktop.org/secret-service-spec/latest-single/#org.freedesktop.Secret.Service
 	*/
+	Cache          map[dbus.ObjectPath]*Collection
 	Collections    []dbus.ObjectPath
+	PathCount      int
+	Alias          map[string]dbus.ObjectPath
 	SessionBase    string // e.g. "/org/freedesktop/secrets/session/"
 	CollectionBase string // e.g. "/org/freedesktop/secrets/collection/"
 }
 
+/*
+implementing the properties interface
+func (s *Service) Get(iface, property string) (dbus.Variant, *dbus.Error) {
+
+		if iface == "org.freedesktop.Secret.Service" && property == "Collections" {
+			return dbus.MakeVariant(s.Collections), nil
+		}
+		return dbus.Variant{}, dbus.MakeFailedError(fmt.Errorf("no such property"))
+	}
+
+	func (s *Service) GetAll(iface string) (map[string]dbus.Variant, *dbus.Error) {
+		if iface == "org.freedesktop.Secret.Service" {
+			return map[string]dbus.Variant{
+				"Collections": dbus.MakeVariant(s.Collections),
+			}, nil
+		}
+		return nil, dbus.MakeFailedError(fmt.Errorf("no such interface"))
+	}
+
+/*
+Create a new service interface
+*/
+func NewService(base dbus.ObjectPath) *Service {
+	return &Service{
+		Cache: map[dbus.ObjectPath]*Collection{
+			DEFAULT_COLLECTION: {
+				Items: []dbus.ObjectPath{
+					"/",
+				},
+				Label:     "default",
+				Cache:     map[dbus.ObjectPath]CacheItem{},
+				PathCount: 0,
+				PathBase:  fmt.Sprintf("%s/collection/default", base),
+				Private:   "true",
+				Created:   uint64(time.Now().Unix()),
+				Modified:  uint64(time.Now().Unix()),
+			},
+		},
+		Collections: []dbus.ObjectPath{
+			DEFAULT_COLLECTION,
+		},
+		PathCount: 0,
+		Alias: map[string]dbus.ObjectPath{
+			"default": DEFAULT_COLLECTION,
+		},
+		SessionBase:    fmt.Sprintf("%s/session", base),
+		CollectionBase: fmt.Sprintf("%s/collection", base),
+	}
+}
+
 /*
 Opens a session for the Secret Service Interface
 
@@ -27,8 +88,8 @@ func (s *Service) OpenSession(algorithm string, input dbus.Variant) (dbus.Varian
 	if algorithm != "PLAIN" {
 		return dbus.Variant{}, "/", dbus.MakeFailedError(fmt.Errorf("only PLAIN is supported"))
 	}
-
 	sessionPath := dbus.ObjectPath(path.Join(s.SessionBase, "1"))
+	fmt.Println(sessionPath)
 	return input, sessionPath, nil
 }
 
@@ -39,8 +100,10 @@ Creates a collection with the Service object
 	:param alias: the shortname of the collection
 */
 func (s *Service) CreateCollection(properties map[string]dbus.Variant, alias string) (dbus.ObjectPath, dbus.ObjectPath, *dbus.Error) {
-	collPath := dbus.ObjectPath(path.Join(s.CollectionBase, "login"))
+	collPath := dbus.ObjectPath(path.Join(s.CollectionBase, strconv.Itoa(s.PathCount+1)))
 	s.Collections = append(s.Collections, collPath)
+	s.PathCount = s.PathCount + 1
+	s.Alias[alias] = collPath
 	return collPath, "/", nil
 }
 
@@ -51,7 +114,16 @@ search for items in the keychain that satisfy 'attrs'
 */
 func (s *Service) SearchItems(attrs map[string]string) ([]dbus.ObjectPath, []dbus.ObjectPath, *dbus.Error) {
 	// Just return empty results for now
-	return []dbus.ObjectPath{}, []dbus.ObjectPath{}, nil
+	found := []dbus.ObjectPath{}
+	for _, v := range s.Cache {
+		f, err := v.SearchItems(attrs)
+		if err != nil {
+			return []dbus.ObjectPath{}, []dbus.ObjectPath{}, err
+		}
+		found = append(found, f...)
+
+	}
+	return found, []dbus.ObjectPath{}, nil
 }
 
 /*
@@ -88,7 +160,11 @@ Return a collection based on the alias name
 	:param name: the alias to search for
 */
 func (s *Service) ReadAlias(name string) (dbus.ObjectPath, *dbus.Error) {
-	return dbus.ObjectPath(""), nil
+	objectPath, ok := s.Alias[name]
+	if !ok {
+		return dbus.ObjectPath("/"), &dbus.ErrMsgNoObject
+	}
+	return objectPath, nil
 
 }
 
@@ -99,6 +175,7 @@ set the alias of the passed in collection
 	:param collection: the collection to modify
 */
 func (s *Service) SetAlias(name string, collection dbus.ObjectPath) *dbus.Error {
+	s.Alias[name] = collection
 	return nil
 
 }

+ 40 - 1
pkg/xml.go

@@ -6,7 +6,46 @@ import (
 
 const DbusAdv = `
 <node>
-	<interface name="dev.aetherial.git.KeychainLinker.Service">
+	  <interface name="org.freedesktop.Secret.Collection">
+
+		<property name="Label" type="s" access="readwrite"/>
+		<property name="Locked" type="b" access="read"/>
+		<property name="Created" type="u" access="read"/>
+		<property name="Modified" type="u" access="read"/>
+		<property name="Items" type="ao" access="read"/>
+
+		<method name="Delete">
+		  <annotation name="org.freedesktop.DBus.GLib.Async" value="true"/>
+		</method>
+
+		<method name="SearchItems">
+		  <arg name="attributes" type="a{ss}" direction="in"/>
+		  <arg name="unlocked" type="ao" direction="out"/>
+		  <arg name="locked" type="ao" direction="out"/>
+		</method>
+
+		<method name="CreateItem">
+		  <arg name="properties" type="a{sv}" direction="in"/>
+		  <arg name="secret" type="(oayays)" direction="in"/>
+		  <arg name="replace" type="b" direction="in"/>
+		  <arg name="item" type="o" direction="out"/>
+		  <annotation name="org.freedesktop.DBus.GLib.Async" value="true"/>
+		</method>
+
+		<signal name="ItemCreated">
+		  <arg name="item" type="o"/>
+		</signal>
+
+		<signal name="ItemDeleted">
+		  <arg name="item" type="o"/>
+		</signal>
+
+		<signal name="ItemChanged">
+		  <arg name="item" type="o"/>
+		</signal>
+	</interface>
+
+	<interface name="org.freedesktop.Secret.Service">
 		<method name="OpenSession">
 		  <arg name="algorithm" type="s" direction="in"/>
 		  <arg name="input" type="v" direction="in"/>

+ 29 - 0
scripts/test_add.py

@@ -0,0 +1,29 @@
+import secretstorage
+import dbus
+
+# Connect to the session bus
+bus = dbus.SessionBus()
+
+# Connect to the Secret Service
+connection = secretstorage.dbus_init()
+collection = secretstorage.get_default_collection(connection)
+
+
+# Define attributes and label
+attributes = {
+    'username': 'alice',
+    'service': 'example-app'
+}
+label = 'My Example Secret'
+secret = 'super_secret_password_123'
+
+# Create the item
+item = collection.create_item(
+    label=label,
+    attributes=attributes,
+    secret=secret,
+    replace=True  # Replace if an item with same attributes exists
+)
+
+print(f"Secret stored with label: {label}")
+