From 2d31ef0eb894fede5f75a78fdc3f1f65ea85546b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Kintzi?= Date: Mon, 13 Nov 2023 20:41:04 +0100 Subject: [PATCH] Add KeyringUnlocker widget --- go.mod | 1 + go.sum | 3 + widgets/keyring.go | 92 ++++++++++++++++++++++++++++++ widgets/keyring_dbus.go | 121 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 217 insertions(+) create mode 100644 widgets/keyring.go create mode 100644 widgets/keyring_dbus.go diff --git a/go.mod b/go.mod index 50f5542..0801c6f 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.21.3 require ( github.com/adrg/xdg v0.4.0 + github.com/coreos/go-systemd/v22 v22.5.0 github.com/godbus/dbus/v5 v5.1.0 github.com/google/go-cmp v0.6.0 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 diff --git a/go.sum b/go.sum index 071b322..571bffd 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,13 @@ github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= diff --git a/widgets/keyring.go b/widgets/keyring.go new file mode 100644 index 0000000..4f8a1ec --- /dev/null +++ b/widgets/keyring.go @@ -0,0 +1,92 @@ +package widgets + +import ( + "context" + "os/exec" + "sync" + + "github.com/godbus/dbus/v5" + "gobytes.dev/statusbar" + "gobytes.dev/statusbar/internal" + + sd "github.com/coreos/go-systemd/v22/dbus" +) + +const targetName = "keyring-unlocked.target" + +type KeyringUnlocker struct { + mutext sync.Mutex + locked bool + + baseWidget `yaml:",inline"` +} + +func NewKeyringWidget() *KeyringUnlocker { return new(KeyringUnlocker) } + +func (w *KeyringUnlocker) Run(ctx context.Context, update statusbar.UpdateFunc) { + mon := make(chan *keyringEvent) + sdconn, err := sd.NewUserConnectionContext(ctx) + if err != nil { + w.errorf("Error conecting to SystemD: %s", err) + } + go func(mon <-chan *keyringEvent, update statusbar.UpdateFunc, w *KeyringUnlocker) { + var ( + seq uint64 + block = w.Block() + blocks = []statusbar.Block{block} + ) + block.FullText = "\uF1A7" + block.Name = "keyring" + block.Markup = "pango" + + for ev := range mon { + if ev.Seq < seq { + continue + } + w.mutext.Lock() + w.locked = ev.Locked + w.mutext.Unlock() + blocks = blocks[:0] + if ev.Locked { + blocks = append(blocks, block) + _, err := sdconn.StopUnitContext(ctx, targetName, "replace", nil) + if err != nil { + w.errorf("Error stopping target %s: %s", targetName, err) + } + } else { + _, err := sdconn.StartUnitContext(ctx, targetName, "replace", nil) + if err != nil { + w.errorf("Error starting target %s: %s", targetName, err) + } + } + update(blocks) + } + }(mon, update, w) + conn, err := dbus.ConnectSessionBus() + if err != nil { + w.errorf("Error conecting to dbus: %s", err) + } + err = newSecretService(conn).MonitorDefaultKeyring(ctx, mon) + if err != nil { + w.errorf("Error monitoring keyring: %s", err) + } +} + +func (w *KeyringUnlocker) Event(ev statusbar.Event) { + w.mutext.Lock() + locked := w.locked + w.mutext.Unlock() + go func(locked bool) { + if locked { + cmd := exec.Command("unlock_keyring") + err := cmd.Run() + if err != nil { + w.errorf("Failed to unlock keyring: %s") + } + } + }(locked) +} + +func init() { + statusbar.RegisterInternalWidget[KeyringUnlocker](internal.M, "KeyringUnlocker") +} diff --git a/widgets/keyring_dbus.go b/widgets/keyring_dbus.go new file mode 100644 index 0000000..6a6eca5 --- /dev/null +++ b/widgets/keyring_dbus.go @@ -0,0 +1,121 @@ +package widgets + +import ( + "context" + + "github.com/godbus/dbus/v5" +) + +const ( + dBusSecretsPath = "/org/freedesktop/secrets" + dBusSecretsServiceName = "org.freedesktop.secrets" + dBusSecretsServiceIface = "org.freedesktop.Secret.Service" + dBusCollectionChangeSig = "CollectionChanged" + dBusDefaultCollectionPath = "/org/freedesktop/secrets/aliases/default" +) + +type keyringEvent struct { + Locked bool + Seq uint64 +} + +type keyringDBus struct { + conn *dbus.Conn + dbus dbus.BusObject +} + +func newKeyring(conn *dbus.Conn, path dbus.ObjectPath) *keyringDBus { + return &keyringDBus{ + conn: conn, + dbus: conn.Object(dBusSecretsServiceName, path), + } +} + +func (k *keyringDBus) Locked() (bool, error) { + val, err := k.dbus.GetProperty("org.freedesktop.Secret.Collection.Locked") + if err != nil { + return true, err + } + return val.Value().(bool), nil +} + +func (k *keyringDBus) Path() dbus.ObjectPath { + return k.dbus.Path() +} + +type secretsService struct { + conn *dbus.Conn + dbus dbus.BusObject +} + +func newSecretService(conn *dbus.Conn) *secretsService { + return &secretsService{ + conn: conn, + dbus: conn.Object(dBusSecretsServiceName, dBusSecretsPath), + } +} + +// ReadAlias (IN String name, OUT ObjectPath Collection) +func (s *secretsService) ReadAlias(alias string) (*keyringDBus, error) { + var path dbus.ObjectPath + err := s.dbus.Call("org.freedesktop.Secret.Service.ReadAlias", 0, alias).Store(&path) + if err != nil { + return nil, err + } + return newKeyring(s.conn, path), nil +} + +func (s *secretsService) Path() dbus.ObjectPath { + return s.dbus.Path() +} + +func (s *secretsService) MonitorDefaultKeyring(ctx context.Context, mon chan<- *keyringEvent) error { + keyring, err := s.ReadAlias("default") + if err != nil { + return err + } + locked, err := keyring.Locked() + if err != nil { + return err + } + ch := make(chan *dbus.Signal) + errs := make(chan error) + go func(conn *dbus.Conn, k *keyringDBus, ch <-chan *dbus.Signal, errs chan<- error, mon chan<- *keyringEvent) { + defer close(errs) + for s := range ch { + path := s.Body[0].(dbus.ObjectPath) + if k.Path() != path { + continue + } + locked, err := k.Locked() + if err != nil { + errs <- err + return + } + mon <- &keyringEvent{ + Locked: locked, + Seq: uint64(s.Sequence + 2), + } + } + }(s.conn, keyring, ch, errs, mon) + + s.conn.Signal(ch) + err = s.conn.AddMatchSignal( + dbus.WithMatchInterface(dBusSecretsServiceIface), + dbus.WithMatchMember(dBusCollectionChangeSig)) + if err != nil { + return err + } + mon <- &keyringEvent{ + Locked: locked, + Seq: 1, + } + select { + case err := <-errs: + return err + case <-ctx.Done(): + s.conn.RemoveSignal(ch) + close(ch) + } + return <-errs +} -- 2.45.2