Skip to content

Commit

Permalink
Merge pull request #80 from mikewilson-dd/memory-data-store
Browse files Browse the repository at this point in the history
Add in memory keyring.
  • Loading branch information
mtibben authored Oct 11, 2021
2 parents ae125c6 + 78c2e70 commit b50c01a
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 15 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.vagrant
.idea
23 changes: 23 additions & 0 deletions examples/example.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package examples

import (
"github.com/99designs/keyring"
"log"
)

// ExampleOpen is an example of how you would create a new keyring and how you would
// retrieve data from it.
func ExampleOpen() {
// Use the best keyring implementation for your operating system
kr, err := keyring.Open(keyring.Config{
ServiceName: "my-service",
})

v, err := kr.Get("llamas")
if err != nil {
log.Fatal(err)
}

log.Printf("llamas was %v", v)
}

14 changes: 14 additions & 0 deletions keyring.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package keyring

import (
"bytes"
"errors"
"log"
"time"
Expand All @@ -22,6 +23,7 @@ const (
WinCredBackend BackendType = "wincred"
FileBackend BackendType = "file"
PassBackend BackendType = "pass"
MemoryBackend BackendType = "memory"
)

// This order makes sure the OS-specific backends
Expand All @@ -38,6 +40,7 @@ var backendOrder = []BackendType{
// General
PassBackend,
FileBackend,
MemoryBackend,
}

var supportedBackends = map[BackendType]opener{}
Expand Down Expand Up @@ -87,6 +90,17 @@ type Item struct {
KeychainNotSynchronizable bool
}

// Equals will return true if the current item is equal to the supplied item.
func (i Item) Equals(other Item) bool {

return i.Key == other.Key &&
bytes.Equal(i.Data, other.Data) &&
i.Label == other.Label &&
i.Description == other.Description &&
i.KeychainNotTrustApplication == other.KeychainNotTrustApplication &&
i.KeychainNotSynchronizable == other.KeychainNotSynchronizable
}

// Metadata is information about a thing stored on the keyring; retrieving
// metadata must not require authentication. The embedded Item should be
// filled in with an empty Data field.
Expand Down
71 changes: 57 additions & 14 deletions keyring_test.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,64 @@
package keyring_test
package keyring

import (
"log"
import "testing"

"github.com/99designs/keyring"
)
func TestItemsEqual(t *testing.T) {
i := Item{
Key: "key",
Data: []byte("data"),
Label: "label",
Description: "description",
KeychainNotTrustApplication: false,
KeychainNotSynchronizable: false,
}

// We're basically going to create copies of i and make sure each field difference generates a false return value
// from Equals.

// First, make sure identity is true.
o := i
if !i.Equals(o) {
t.Fatalf("identity case should result in true")
}

// Test key differences
o.Key = "something else"
if i.Equals(o) {
t.Fatalf("key difference should result in false")
}

func ExampleOpen() {
// Use the best keyring implementation for your operating system
kr, err := keyring.Open(keyring.Config{
ServiceName: "my-service",
})
// Test key differences
o = i
o.Data = []byte("something else")
if i.Equals(o) {
t.Fatalf("data difference should result in false")
}

// Test label differences
o = i
o.Label = "something else"
if i.Equals(o) {
t.Fatalf("label difference should result in false")
}

v, err := kr.Get("llamas")
if err != nil {
log.Fatal(err)
// Test description differences
o = i
o.Description = "something else"
if i.Equals(o) {
t.Fatalf("description difference should result in false")
}

log.Printf("llamas was %v", v)
// Test KeychainNotTrustApplication differences
o = i
o.KeychainNotTrustApplication = true
if i.Equals(o) {
t.Fatalf("KeyChainNotTrustApplication difference should result in false")
}

// Test KeychainNotSynchronizable differences
o = i
o.KeychainNotSynchronizable = true
if i.Equals(o) {
t.Fatalf("KeyChainNotSynchronizable difference should result in false")
}
}
2 changes: 1 addition & 1 deletion kwallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func (k *kwalletKeyring) Get(key string) (Item, error) {
// found in docs for methods to use to retrieve metadata without needing unlock
// credentials.
func (k *kwalletKeyring) GetMetadata(_ string) (Metadata, error) {
return Metadata{}, ErrMetadataNeedsCredentials
return Metadata{}, ErrNoAvailImpl
}

func (k *kwalletKeyring) Set(item Item) error {
Expand Down
71 changes: 71 additions & 0 deletions memory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package keyring

import (
"sync"
)

func init() {
supportedBackends[MemoryBackend] = opener(func(cfg Config) (Keyring, error) {
memory := newMemoryKeyring()

return memory, nil
})
}

type memoryKeyring struct {
mutex sync.RWMutex
mapStore map[string]Item
}

func newMemoryKeyring() *memoryKeyring {
return &memoryKeyring{
mutex: sync.RWMutex{},
mapStore: map[string]Item{},
}
}

func (k *memoryKeyring) Get(key string) (Item, error) {
k.mutex.RLock()
defer k.mutex.RUnlock()

if item, ok := k.mapStore[key]; !ok {
return Item{}, ErrKeyNotFound
} else {
return item, nil
}
}

// GetMetadata for memory returns an error indicating that it's unsupported
// for this backend.
//
// It doesn't really apply to the memory backend, as it's a simple wrapper around a map.
func (k *memoryKeyring) GetMetadata(_ string) (Metadata, error) {
return Metadata{}, ErrNoAvailImpl
}

func (k *memoryKeyring) Set(item Item) error {
k.mutex.Lock()
defer k.mutex.Unlock()

k.mapStore[item.Key] = item
return nil
}

func (k *memoryKeyring) Remove(key string) error {
k.mutex.Lock()
defer k.mutex.Unlock()

delete(k.mapStore, key)
return nil
}

func (k *memoryKeyring) Keys() ([]string, error) {
k.mutex.RLock()
defer k.mutex.RUnlock()

keys := []string{}
for k := range k.mapStore {
keys = append(keys, k)
}
return keys, nil
}
89 changes: 89 additions & 0 deletions memory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package keyring

import "testing"

func TestSetAndGets(t *testing.T) {
k := newMemoryKeyring()

// Set a few items and try to retrieve them
items := []Item{
makeItem("key1", "data1", "label1", "description1"),
makeItem("key2", "data2", "label2", "description2"),
makeItem("key3", "data3", "label3", "description3"),
makeItem("key4", "data4", "label4", "description4"),
}

for _, item := range items {
k.Set(item)
}

for _, item := range items {
getItem, _ := k.Get(item.Key)

if !item.Equals(getItem) {
t.Fatalf("%s is not equal to its version from the store", item.Key)
}
}

if _, err := k.Get("non-existent key"); err != ErrKeyNotFound {
t.Fatalf("error for non-existent key should have been key not found, was: %v", err)
}
}

func TestRemove(t *testing.T) {
k := newMemoryKeyring()

item := makeItem("key1", "data1", "label1", "description1")

k.Set(item)

if _, err := k.Get(item.Key); err != nil {
t.Fatalf("unable to find stored item")
}

k.Remove(item.Key)

if _, err := k.Get(item.Key); err == nil {
t.Fatalf("after removal, should not have been able to find item")
}
}

func TestKeys(t *testing.T) {
k := newMemoryKeyring()

items := []Item{
makeItem("key1", "data1", "label1", "description1"),
makeItem("key2", "data2", "label2", "description2"),
makeItem("key3", "data3", "label3", "description3"),
makeItem("key4", "data4", "label4", "description4"),
}

for _, item := range items {
k.Set(item)
}

keys, _ := k.Keys()
for _, item := range items {
foundKey := false

for _, key := range keys {
if item.Key == key {
foundKey = true
break
}
}

if !foundKey {
t.Errorf("unable to find key %s", item.Key)
}
}
}

func makeItem(key, data, label, description string) Item {
return Item{
Key: key,
Data: []byte(data),
Label: label,
Description: description,
}
}

0 comments on commit b50c01a

Please sign in to comment.