From f68a4710b417f5501a0e825116663585fa0bd60d Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 6 Dec 2023 02:09:43 +0100 Subject: [PATCH 1/5] Implement libportal autotype --- autofill/autotype/libportalautotype.go | 77 ++++++++++++++++++++++++++ autofill/autotype/uinputautotype.go | 2 +- 2 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 autofill/autotype/libportalautotype.go diff --git a/autofill/autotype/libportalautotype.go b/autofill/autotype/libportalautotype.go new file mode 100644 index 0000000..23e85f8 --- /dev/null +++ b/autofill/autotype/libportalautotype.go @@ -0,0 +1,77 @@ +//go:build linux && !uinput + +package autotype + +import ( + "fmt" + "time" + + "github.com/godbus/dbus/v5" +) + +var globalID = 0 + +const autoTypeDelay = 1 * time.Millisecond + +func TypeString(textToType string, layout string) { + bus, err := dbus.SessionBus() + if err != nil { + panic(err) + } + + obj := bus.Object("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop") + obj.AddMatchSignal("org.freedesktop.portal.Request", "Response") + + globalID++ + obj.Call("org.freedesktop.portal.RemoteDesktop.CreateSession", 0, map[string]dbus.Variant{ + "session_handle_token": dbus.MakeVariant("u" + fmt.Sprint(globalID)), + }) + + signals := make(chan *dbus.Signal, 10) + bus.Signal(signals) + + var state = 0 + var sessionHandle dbus.ObjectPath + + for { + select { + case message := <-signals: + fmt.Println("Message:", message) + if state == 0 { + result := message.Body[1].(map[string]dbus.Variant) + resultSessionHandle := result["session_handle"] + sessionHandle = dbus.ObjectPath(resultSessionHandle.String()[1 : len(resultSessionHandle.String())-1]) + obj.Call("org.freedesktop.portal.RemoteDesktop.SelectDevices", 0, sessionHandle, map[string]dbus.Variant{}) + state = 1 + } else if state == 1 { + obj.Call("org.freedesktop.portal.RemoteDesktop.Start", 0, sessionHandle, "", map[string]dbus.Variant{}) + state = 2 + } else if state == 2 { + state = 3 + for _, char := range textToType { + if char == '\t' { + obj.Call("org.freedesktop.portal.RemoteDesktop.NotifyKeyboardKeycode", 0, sessionHandle, map[string]dbus.Variant{}, 15, uint32(1)) + time.Sleep(autoTypeDelay) + obj.Call("org.freedesktop.portal.RemoteDesktop.NotifyKeyboardKeycode", 0, sessionHandle, map[string]dbus.Variant{}, 15, uint32(0)) + time.Sleep(autoTypeDelay) + } else { + obj.Call("org.freedesktop.portal.RemoteDesktop.NotifyKeyboardKeysym", 0, sessionHandle, map[string]dbus.Variant{}, int32(char), uint32(1)) + time.Sleep(autoTypeDelay) + obj.Call("org.freedesktop.portal.RemoteDesktop.NotifyKeyboardKeysym", 0, sessionHandle, map[string]dbus.Variant{}, int32(char), uint32(0)) + time.Sleep(autoTypeDelay) + } + } + bus.Close() + return + } else { + fmt.Println("state", state) + fmt.Println("Message:", message) + } + } + } +} + +func Paste(layout string) error { + fmt.Println("Not implemented") + return nil +} diff --git a/autofill/autotype/uinputautotype.go b/autofill/autotype/uinputautotype.go index 5a32c46..fe125c9 100644 --- a/autofill/autotype/uinputautotype.go +++ b/autofill/autotype/uinputautotype.go @@ -1,4 +1,4 @@ -//go:build linux +//go:build linux && uinput package autotype From 905a09cce76a9fd3a5abdf5b0578e8e350dc5187 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 6 Dec 2023 02:39:36 +0100 Subject: [PATCH 2/5] Add autotype delay to account for focus shift --- autofill/autotype/libportalautotype.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/autofill/autotype/libportalautotype.go b/autofill/autotype/libportalautotype.go index 23e85f8..9edc1e8 100644 --- a/autofill/autotype/libportalautotype.go +++ b/autofill/autotype/libportalautotype.go @@ -48,6 +48,7 @@ func TypeString(textToType string, layout string) { state = 2 } else if state == 2 { state = 3 + time.Sleep(200 * time.Millisecond) for _, char := range textToType { if char == '\t' { obj.Call("org.freedesktop.portal.RemoteDesktop.NotifyKeyboardKeycode", 0, sessionHandle, map[string]dbus.Variant{}, 15, uint32(1)) @@ -63,9 +64,6 @@ func TypeString(textToType string, layout string) { } bus.Close() return - } else { - fmt.Println("state", state) - fmt.Println("Message:", message) } } } From 04d2357f3c3611e88d1a0da2e6fb6db0d9e16076 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 6 Dec 2023 02:40:03 +0100 Subject: [PATCH 3/5] Prevent multiple autotype permission dialogs --- autofill/autofill.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/autofill/autofill.go b/autofill/autofill.go index 6c9fe5f..7c9a4cf 100644 --- a/autofill/autofill.go +++ b/autofill/autofill.go @@ -77,9 +77,7 @@ func Run(layout string, useCopyPaste bool, client client.Client) { clipboard.WriteAll(login.Password) autotype.Paste(layout) } else { - autotype.TypeString(string(login.Username), layout) - autotype.TypeString("\t", layout) - autotype.TypeString(string(login.Password), layout) + autotype.TypeString(string(login.Username)+"\t"+string(login.Password), layout) } clipboard.WriteAll(login.TwoFactorCode) From 5a40f237a79b3de95232014c7a33bc6f7e9c74cf Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 6 Dec 2023 02:41:50 +0100 Subject: [PATCH 4/5] Remove use-copy-paste option from autotype --- autofill/autofill.go | 12 ++---------- autofill/autotype/libportalautotype.go | 5 ----- autofill/autotype/uinputautotype.go | 4 ---- cmd/autofill.go | 3 +-- 4 files changed, 3 insertions(+), 21 deletions(-) diff --git a/autofill/autofill.go b/autofill/autofill.go index 7c9a4cf..527ed14 100644 --- a/autofill/autofill.go +++ b/autofill/autofill.go @@ -49,7 +49,7 @@ func ListLogins(client client.Client) ([]messages.DecryptedLoginCipher, error) { } } -func Run(layout string, useCopyPaste bool, client client.Client) { +func Run(layout string, client client.Client) { logins, err := ListLogins(client) if err != nil { panic(err) @@ -70,15 +70,7 @@ func Run(layout string, useCopyPaste bool, client client.Client) { panic(err) } - if useCopyPaste { - clipboard.WriteAll(string(login.Username)) - autotype.Paste(layout) - autotype.TypeString("\t", layout) - clipboard.WriteAll(login.Password) - autotype.Paste(layout) - } else { - autotype.TypeString(string(login.Username)+"\t"+string(login.Password), layout) - } + autotype.TypeString(string(login.Username)+"\t"+string(login.Password), layout) clipboard.WriteAll(login.TwoFactorCode) c <- true diff --git a/autofill/autotype/libportalautotype.go b/autofill/autotype/libportalautotype.go index 9edc1e8..64ad3fb 100644 --- a/autofill/autotype/libportalautotype.go +++ b/autofill/autotype/libportalautotype.go @@ -68,8 +68,3 @@ func TypeString(textToType string, layout string) { } } } - -func Paste(layout string) error { - fmt.Println("Not implemented") - return nil -} diff --git a/autofill/autotype/uinputautotype.go b/autofill/autotype/uinputautotype.go index fe125c9..50bb452 100644 --- a/autofill/autotype/uinputautotype.go +++ b/autofill/autotype/uinputautotype.go @@ -7,7 +7,3 @@ import "github.com/quexten/goldwarden/autofill/autotype/uinput" func TypeString(text string, layout string) error { return uinput.TypeString(text, layout) } - -func Paste(layout string) error { - return uinput.Paste(layout) -} diff --git a/cmd/autofill.go b/cmd/autofill.go index 6ce4baf..1710720 100644 --- a/cmd/autofill.go +++ b/cmd/autofill.go @@ -13,8 +13,7 @@ var autofillCmd = &cobra.Command{ Long: `Autofill credentials`, Run: func(cmd *cobra.Command, args []string) { layout := cmd.Flag("layout").Value.String() - useCopyPaste, _ := cmd.Flags().GetBool("use-copy-paste") - autofill.Run(layout, useCopyPaste, commandClient) + autofill.Run(layout, commandClient) }, } From 91751978b96d83230bf75916d2ac12dbbe2cb21c Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 6 Dec 2023 02:46:28 +0100 Subject: [PATCH 5/5] Update autotype section in readme --- Readme.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Readme.md b/Readme.md index bd0c71b..9b821e4 100644 --- a/Readme.md +++ b/Readme.md @@ -172,16 +172,24 @@ And then just run the command as usual: restic backup ``` -### Autofill +### Autotype based Autofill [goldwarden_autofill.webm](https://github.com/quexten/goldwarden/assets/11866552/6ac7cdc2-0cd7-42fd-9fd0-cfff26e2ceee) -The autofill feature is a bit experimental. It autotypes the password via uinput. This needs a keyboardlayout to map the letters to +You can bind this to a hotkey in your desktop environment (i.e i3/sway config file, Gnome custom shortcuts, etc). + +#### XDG-RemoteDesktop-Portal + +By default, the remote desktop portal is used. As long as your desktop environment handle this (KDE and Gnome do, wlroots does not yet) +this enables autotyping without having to modify permissions. +`goldwarden autofill` + +#### (Legacy) Uinput +If your desktop environment does not implement the remotedesktop portal, your only other option is uinput based autotype. This requires your user +to have access to the input group to use uinput to autotype. This needs a keyboardlayout to map the letters to keycodes. Currently supported are qwerty and dvorak. `goldwarden autofill --layout qwerty` `goldwarden autofill --layout dvorak` -You can bind this to a hotkey in your desktop environment (i.e i3/sway config file, Gnome custom shortcuts, etc). - ### Login with device Approving other devices works out of the box and is enabled by default. If the agent is unlocked, you will be prompted to approve the device. If you want to log into goldwarden using another device, add the "--passwordless" parameter to the login command.