Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add in memory keyring. #80

Merged
merged 1 commit into from
Oct 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -21,6 +22,7 @@ const (
WinCredBackend BackendType = "wincred"
FileBackend BackendType = "file"
PassBackend BackendType = "pass"
MemoryBackend BackendType = "memory"
)

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

var supportedBackends = map[BackendType]opener{}
Expand Down Expand Up @@ -85,6 +88,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 @@ -107,7 +107,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,
}
}