tests pass; tagging release

This commit is contained in:
brent s. 2021-12-28 05:48:08 -05:00
parent 330548689a
commit 89456270c5
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
9 changed files with 230 additions and 22 deletions

133
README.adoc Normal file
View File

@ -0,0 +1,133 @@
= gokwallet
Brent Saner <bts@square-r00t.net>
:doctype: book
:docinfo: shared
:data-uri:
:imagesdir: images
:sectlinks:
:sectnums:
:sectnumlevels: 7
:toc: preamble
:toc2: left
:idprefix:
:toclevels: 7
:source-highlighter: rouge

image::https://pkg.go.dev/badge/r00t2.io/gokwallet.svg[link="https://pkg.go.dev/r00t2.io/gokwallet"]

Package `gokwallet` serves as a Golang interface to KDE's https://utils.kde.org/projects/kwalletmanager/[KWallet^].

Note that to use this library, the running machine must have both Dbus and kwalletd running.

Relatedly, note also that this library interfaces with kwalletd. KWallet is in the process of moving to libsecret/SecretService
(see https://bugs.kde.org/show_bug.cgi?id=313216[here^] and https://invent.kde.org/frameworks/kwallet/-/merge_requests/11[here^]),
thus replacing kwalletd. While there is a pull request in place, it has not yet been merged in (and it may be a while before downstream distributions incorporate that version). However, when that time comes I highly recommend using my `gosecret` library to interface with that (module https://pkg.go.dev/r00t2.io/gosecret[`r00t2.io/gosecret`^]).

== KWallet Concepts

For reference, KWallet has the following structure (modified slightly to reflect this library):

* A main Dbus service interface ("org.kde.kwalletd5"), `WalletManager`, allows one to retrieve and operate on/with `Wallet` items.

* One or more `Wallet` items allow one to retrieve and operate on/with `Folder` items.

* One or more `Folder` items allow one to retrieve and operate on/with `Passwords`, `Maps`, `BinaryData`, and `UnknownItem` `WalletItem` items.

Thus, the hierarchy (as exposed by this library) looks like this:

----
WalletManager
├─ Wallet "A"
│ ├─ Folder "A_1"
│ │ ├─ Passwords
│ │ │ ├─ Password "A_1_a"
│ │ │ └─ Password "A_1_b"
│ │ ├─ Maps
│ │ │ ├─ Map "A_1_a"
│ │ │ └─ Map "A_1_b"
│ │ ├─ BinaryData
│ │ │ ├─ Blob "A_1_a"
│ │ │ └─ Blob "A_1_b"
│ │ └─ Unknown
│ │ ├─ UnknownItem "A_1_a"
│ │ └─ UnknownItem "A_1_b"
│ └─ Folder "A_2"
│ ├─ Passwords
│ │ ├─ Password "A_2_a"
│ │ └─ Password "A_2_b"
│ ├─ Maps
│ │ ├─ Map "A_2_a"
│ │ └─ Map "A_2_b"
│ ├─ BinaryData
│ │ ├─ Blob "A_2_a"
│ │ └─ Blob "A_2_b"
│ └─ Unknown
│ ├─ UnknownItem "A_2_a"
│ └─ UnknownItem "A_2_b"
└─ Wallet "B"
└─ Folder "B_1"
├─ Passwords
│ ├─ Password "B_1_a"
│ └─ Password "B_1_b"
├─ Maps
│ ├─ Map "B_1_a"
│ └─ Map "B_1_b"
├─ BinaryData
│ ├─ Blob "B_1_a"
│ └─ Blob "B_1_b"
└─ Unknown
├─ UnknownItem "B_1_a"
└─ UnknownItem "B_1_b"
----

This is an approximation, but should show a relatively accurate representation of the model.
Note that most systems are likely to only have a single wallet, "kdewallet".

== Usage

Full documentation can be found via inline documentation.
Additionally, use either https://pkg.go.dev/r00t2.io/gokwallet or https://pkg.go.dev/golang.org/x/tools/cmd/godoc (or `go doc`) in the source root.

You most likely do *not* want to call any New<object> function directly;
NewWalletManager with its RecurseOpts parameter (`recursion`) should get you everything you want/need.

Here's a quick demonstration:

[source,go]
----
package main

import (
`fmt`
`log`

`r00t2.io/gokwallet`
)

func main() {

var err error
var r *gokwallet.RecurseOpts
var wm *gokwallet.WalletManager
var w *gokwallet.Wallet
var f *gokwallet.Folder
var p *gokwallet.Password

r = gokwallet.DefaultRecurseOpts
r.AllWalletItems = true

if wm, err = gokwallet.NewWalletManager(r, "ExampleKWalletApplication"); err != nil {
log.Panicln(err)
}

w = wm.Wallets["kdewallet"]

f = w.Folders["Passwords"]

if p, err = f.WritePassword("test_password", "this is a test password"); err != nil {
log.Panicln(err)
}

fmt.Println(p.Value)
}
----

View File

@ -13,6 +13,7 @@ const (
// Identifiers/names/keys.
var (
walletTest uuid.UUID = uuid.New()
walletTestAlt uuid.UUID = uuid.New()
folderTest uuid.UUID = uuid.New()
blobTest uuid.UUID = uuid.New()
mapTest uuid.UUID = uuid.New()

40
doc.go
View File

@ -12,6 +12,8 @@ While there is a pull request in place, it has not yet been merged in (and it ma
distributions incorporate that version). However, when that time comes I highly recommend using my `gosecret`
library to interface with that (module r00t2.io/gosecret; see https://pkg.go.dev/r00t2.io/gosecret).

KWallet Concepts

KWallet has the following structure (modified slightly to reflect this library):

- A main Dbus service interface ("org.kde.kwalletd5"), WalletManager, allows one to retrieve and operate on/with Wallet items.
@ -75,5 +77,43 @@ Additionally, use either https://pkg.go.dev/r00t2.io/gokwallet or https://pkg.go

You most likely do *not* want to call any New<object> function directly;
NewWalletManager with its RecurseOpts parameter (`recursion`) should get you everything you want/need.

Here's a quick demonstration:

package main

import (
`fmt`
`log`

`r00t2.io/gokwallet`
)

func main() {

var err error
var r *gokwallet.RecurseOpts
var wm *gokwallet.WalletManager
var w *gokwallet.Wallet
var f *gokwallet.Folder
var p *gokwallet.Password

r = gokwallet.DefaultRecurseOpts
r.AllWalletItems = true

if wm, err = gokwallet.NewWalletManager(r, "ExampleKWalletApplication"); err != nil {
log.Panicln(err)
}

w = wm.Wallets["kdewallet"]

f = w.Folders["Passwords"]

if p, err = f.WritePassword("test_password", "this is a test password"); err != nil {
log.Panicln(err)
}

fmt.Println(p.Value)
}
*/
package gokwallet

View File

@ -87,6 +87,7 @@ func (f *Folder) HasEntry(entryName string) (hasEntry bool, err error) {
KeyNotExist returns true if a key/entry name entryName does *not* exist.
Essentially the same as Folder.HasEntry, but whereas Folder.HasEntry requires the parent wallet
to be open/unlocked, Folder.KeyNotExist does not require this.
However, it's prone to somewhat unreliable results; it's best to use Folder.HasEntry wherever/whenever possible.
*/
func (f *Folder) KeyNotExist(entryName string) (doesNotExist bool, err error) {


View File

@ -71,23 +71,33 @@ func TestFolder(t *testing.T) {
t.Errorf("failed to find entry '%v' via HasEntry in Folder '%v:%v': %v", p.Name, w.Name, f.Name, err)
}

if b, err = f.KeyNotExist(p.Name); err != nil {
t.Errorf("failed to run KeyNotExist in Folder '%v:%v' for key '%v': %v", w.Name, f.Name, p.Name, err)
} else if b {
t.Errorf("failed to get false for '%v' via KeyNotExist in Folder '%v:%v': %v", p.Name, w.Name, f.Name, err)
}
// This gives an incorrect return of true and I'm not entirely sure why. Maybe it needs a .sync or .reconfigure Dbus call?
// Or maybe it needs to be encapsulated in quotes?
/*
if b, err = f.KeyNotExist(p.Name); err != nil {
t.Errorf("failed to run KeyNotExist in Folder '%v:%v' for key '%v': %v", w.Name, f.Name, p.Name, err)
} else if b {
t.Errorf("failed to get false for '%v' via KeyNotExist in Folder '%v:%v'", p.Name, w.Name, f.Name)
// t.Fatalf("failed to get false for '%v' via KeyNotExist in Folder '%v:%v'", p.Name, w.Name, f.Name)
}
*/

if entries, err = f.ListEntries(); err != nil {
t.Errorf("failed to run ListEntries in Folder '%v:%v': %v", w.Name, f.Name, err)
} else if len(entries) != 1 {
t.Errorf("ListEntries for Folder '%v:%v' contains %v entries; should be 1", w.Name, f.Name, len(entries))
} else if entries == nil || len(entries) == 0 {
t.Errorf("ListEntries for Folder '%v:%v' is 0", w.Name, f.Name)
}

if p.Value != entries[0] {
t.Errorf(
"received incorrect value for test password in '%v:%v'; should be '%#v' but received '%#v'",
w.Name, f.Name, p.Value, entries[0],
)
b = false
for idx, e := range entries {
if e == p.Name {
t.Logf("found matching value for test password in '%v:%v' at index %v: %v", w.Name, f.Name, idx, p.Name)
b = true
break
}
}
if !b {
t.Errorf("failed to find test password '%v:%v:%v'", w.Name, f.Name, p.Name)
}

// This tests the parent folder's Rename method.

View File

@ -96,6 +96,8 @@ func (m *Map) Update() (err error) {
return
}

m.Value = make(map[string]string, 0)

if call = m.Dbus.Call(
DbusWMReadMap, 0, m.folder.wallet.handle, m.folder.Name, m.Name, m.folder.wallet.wm.AppID,
); call.Err != nil {
@ -106,8 +108,10 @@ func (m *Map) Update() (err error) {
return
}

if m.Value, _, err = bytesToMap(b); err != nil {
return
if len(b) != 0 {
if m.Value, _, err = bytesToMap(b); err != nil {
return
}
}

return

View File

@ -155,14 +155,14 @@ func mapToBytes(m map[string]string) (raw []byte, err error) {
if err = binary.Write(buf, binary.BigEndian, &kLen); err != nil {
return
}
if err = binary.Write(buf, binary.BigEndian, &k); err != nil {
if err = binary.Write(buf, binary.BigEndian, &kB); err != nil {
return
}

if err = binary.Write(buf, binary.BigEndian, &vLen); err != nil {
return
}
if err = binary.Write(buf, binary.BigEndian, &v); err != nil {
if err = binary.Write(buf, binary.BigEndian, &vB); err != nil {
return
}
}

View File

@ -85,7 +85,7 @@ func (w *Wallet) DisconnectApplication(appName string) (err error) {
}

if call = w.Dbus.Call(
DbusWMDisconnectApp, 0, appName, w.wm.AppID,
DbusWMDisconnectApp, 0, w.Name, appName,
); call.Err != nil {
err = call.Err
return

View File

@ -11,8 +11,10 @@ func TestWallet(t *testing.T) {
var b bool
var conns []string
var folders []string
var wallets []string
var r *RecurseOpts = DefaultRecurseOpts
var wm *WalletManager
var wm2 *WalletManager
var w *Wallet
var w2 *Wallet

@ -23,18 +25,35 @@ func TestWallet(t *testing.T) {
}
defer wm.Close()

if wm2, err = NewWalletManager(r, appIdTestAlt); err != nil {
t.Fatalf("failed to get WalletManager '%v' for TestWallet: %v", appIdTest, err)
}
defer wm2.Close()

if w, err = NewWallet(wm, walletTest.String(), r); err != nil {
t.Fatalf("failed to get Wallet '%v:%v' for TestWallet: %v", appIdTest, walletTest.String(), err)
}
defer w.Delete()
defer w.Disconnect()

// We test Disconnect above but we also need to test explicit disconnect by application name.
if w2, err = NewWallet(wm, walletTest.String(), r); err != nil {
t.Fatalf("failed to get Wallet '%v:%v' for TestWallet: %v", appIdTestAlt, walletTest.String(), err)
if w2, err = NewWallet(wm2, walletTestAlt.String(), r); err != nil {
t.Fatalf("failed to get Wallet '%v:%v' for TestWallet: %v", appIdTestAlt, walletTestAlt.String(), err)
}
if err = w2.DisconnectApplication(appIdTest); err != nil {
defer w2.Delete()

if wallets, err = wm.WalletNames(); err != nil {
t.Errorf("failure when getting wallet names for '%v': %v", appIdTest, err)
} else {
t.Logf("wallet names found via %v: %#v", appIdTest, wallets)
}

if w2, err = NewWallet(wm, walletTestAlt.String(), r); err != nil {
t.Errorf("could not open '%v' in '%v': %v", walletTestAlt.String(), appIdTest, err)
}
if err = w2.DisconnectApplication(appIdTestAlt); err != nil {
t.Errorf(
"failed to execute DisconnectApplication for '%v:%v' successfully: %v", appIdTestAlt, walletTest.String(), err,
"failed to execute DisconnectApplication for '%v:%v' successfully: %v", appIdTestAlt, walletTestAlt.String(), err,
)
}

@ -80,7 +99,7 @@ func TestWallet(t *testing.T) {
if folders, err = w.ListFolders(); err != nil {
t.Errorf("error when running ListFolders for wallet '%v:%v': %v", appIdTest, walletTest.String(), err)
} else {
t.Logf("ListFolders returned for wallet '%v:%v': %v", appIdTest, walletTest.String(), folders)
t.Logf("ListFolders returned for wallet '%v:%v': %#v", appIdTest, walletTest.String(), folders)
}

if err = w.RemoveFolder(folderTest.String()); err != nil {