Merge pull request #11 from quexten/feature/libportal-autotype

Implement libportal autotype
This commit is contained in:
Bernd Schoolmann 2023-12-06 02:50:35 +01:00 committed by GitHub
commit cb3c40d98c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 86 additions and 23 deletions

View File

@ -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.

View File

@ -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,17 +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), 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)
c <- true

View File

@ -0,0 +1,70 @@
//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
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))
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
}
}
}
}

View File

@ -1,4 +1,4 @@
//go:build linux
//go:build linux && uinput
package autotype
@ -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)
}

View File

@ -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)
},
}