Skip to content

Commit 7300e07

Browse files
authored
Add Delete method to accessors (#21)
1 parent 65160f1 commit 7300e07

File tree

9 files changed

+118
-46
lines changed

9 files changed

+118
-46
lines changed

cache/accessor/accessor.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import "context"
77

88
// Accessor accesses data storage.
99
type Accessor interface {
10+
Delete(context.Context) error
1011
Read(context.Context) ([]byte, error)
1112
Write(context.Context, []byte) error
1213
}

cache/accessor/darwin.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@ func New(servName string, opts ...option) (*Storage, error) {
4343
return &s, nil
4444
}
4545

46+
// Delete deletes the stored data, if any exists.
47+
func (s *Storage) Delete(context.Context) error {
48+
err := keychain.DeleteGenericPasswordItem(s.service, s.account)
49+
if errors.Is(err, keychain.ErrorItemNotFound) || errors.Is(err, keychain.ErrorNoSuchKeychain) {
50+
return nil
51+
}
52+
return err
53+
}
54+
4655
// Read returns data stored on the keychain or, if the keychain item doesn't exist, a nil slice and nil error.
4756
func (s *Storage) Read(context.Context) ([]byte, error) {
4857
data, err := keychain.GetGenericPassword(s.service, s.account, "", "")
@@ -70,3 +79,5 @@ func (s *Storage) Write(_ context.Context, data []byte) error {
7079
}
7180
return err
7281
}
82+
83+
var _ Accessor = (*Storage)(nil)

cache/accessor/darwin_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import (
1313
)
1414

1515
func TestWithAccount(t *testing.T) {
16+
if !manualTests {
17+
t.Skipf("set %s to run this test", msalextManualTest)
18+
}
1619
account := "account"
1720
a, err := New(t.Name(), WithAccount(account))
1821
require.NoError(t, err)
@@ -24,4 +27,6 @@ func TestWithAccount(t *testing.T) {
2427
actual, err := a.Read(ctx)
2528
require.NoError(t, err)
2629
require.Equal(t, expected, actual)
30+
31+
require.NoError(t, a.Delete(ctx))
2732
}

cache/accessor/file/file.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@ func New(p string) (*Storage, error) {
2424
return &Storage{m: &sync.RWMutex{}, p: p}, nil
2525
}
2626

27+
// Delete deletes the file, if it exists.
28+
func (s *Storage) Delete(context.Context) error {
29+
s.m.Lock()
30+
defer s.m.Unlock()
31+
err := os.Remove(s.p)
32+
if errors.Is(err, os.ErrNotExist) {
33+
return nil
34+
}
35+
return err
36+
}
37+
2738
// Read returns the file's content or, if the file doesn't exist, a nil slice and error.
2839
func (s *Storage) Read(context.Context) ([]byte, error) {
2940
s.m.RLock()

cache/accessor/file/file_test.go

Lines changed: 29 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -14,57 +14,43 @@ import (
1414

1515
var ctx = context.Background()
1616

17-
func TestRead(t *testing.T) {
18-
p := filepath.Join(t.TempDir(), t.Name())
19-
a, err := New(p)
20-
require.NoError(t, err)
21-
22-
expected := []byte("expected")
23-
require.NoError(t, os.WriteFile(p, expected, 0600))
24-
25-
actual, err := a.Read(ctx)
26-
require.NoError(t, err)
27-
require.Equal(t, expected, actual)
28-
}
29-
30-
func TestRoundTrip(t *testing.T) {
31-
p := filepath.Join(t.TempDir(), "nonexistent", t.Name())
32-
a, err := New(p)
33-
require.NoError(t, err)
34-
35-
var expected []byte
36-
for i := 0; i < 4; i++ {
37-
actual, err := a.Read(ctx)
38-
require.NoError(t, err)
39-
require.Equal(t, expected, actual)
40-
41-
expected = append(expected, byte(i))
42-
require.NoError(t, a.Write(ctx, expected))
43-
}
44-
}
45-
46-
func TestWrite(t *testing.T) {
47-
p := filepath.Join(t.TempDir(), t.Name())
48-
for _, create := range []bool{true, false} {
49-
name := "file exists"
50-
if create {
51-
name = "new file"
52-
}
53-
t.Run(name, func(t *testing.T) {
54-
if create {
55-
f, err := os.OpenFile(p, os.O_CREATE|os.O_EXCL, 0600)
17+
func TestReadWriteDelete(t *testing.T) {
18+
for _, test := range []struct {
19+
desc string
20+
initialData, want []byte
21+
}{
22+
{desc: "Test when the file exists", initialData: []byte("data"), want: []byte("want")},
23+
{desc: "Test when the file doesn't exist", want: []byte("want")},
24+
} {
25+
t.Run(test.desc, func(t *testing.T) {
26+
p := filepath.Join(t.TempDir(), t.Name())
27+
if test.initialData != nil {
28+
require.NoError(t, os.MkdirAll(filepath.Dir(p), 0700))
29+
f, err := os.OpenFile(p, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0600)
30+
require.NoError(t, err)
31+
_, err = f.Write(test.initialData)
5632
require.NoError(t, err)
5733
require.NoError(t, f.Close())
5834
}
5935
a, err := New(p)
6036
require.NoError(t, err)
6137

62-
expected := []byte("expected")
63-
require.NoError(t, a.Write(ctx, expected))
38+
if test.initialData != nil {
39+
actual, err := a.Read(ctx)
40+
require.NoError(t, err)
41+
require.Equal(t, test.initialData, actual)
42+
}
6443

65-
actual, err := os.ReadFile(p)
44+
cp := make([]byte, len(test.want))
45+
copy(cp, test.want)
46+
err = a.Write(ctx, cp)
6647
require.NoError(t, err)
67-
require.Equal(t, expected, actual)
48+
49+
actual, err := a.Read(ctx)
50+
require.NoError(t, err)
51+
require.Equal(t, test.want, actual)
52+
53+
require.NoError(t, a.Delete(context.Background()))
6854
})
6955
}
7056
}

cache/accessor/linux.go

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,16 @@ void free_g_error(void *f, gError *err)
6767
fn(err);
6868
}
6969
70+
// clear (delete) a secret. f must be a pointer to secret_password_clear_sync
71+
// https://gnome.pages.gitlab.gnome.org/libsecret/func.password_clear_sync.html
72+
int clear(void *f, schema *sch, void* cancellable, gError **err, char *key1, char *value1, char *key2, char *value2)
73+
{
74+
int (*fn)(schema *sch, void* cancellable, gError **err, char *key1, char *value1, char *key2, char *value2, ...);
75+
fn = (int (*)(schema *sch, void* cancellable, gError **err, char *key1, char *value1, char *key2, char *value2, ...))f;
76+
int r = fn(sch, cancellable, err, key1, value1, key2, value2, NULL);
77+
return r;
78+
}
79+
7080
// lookup a password. f must be a pointer to secret_password_lookup_sync
7181
// https://gnome.pages.gitlab.gnome.org/libsecret/func.password_lookup_sync.html
7282
char *lookup(void *f, schema *sch, void* cancellable, gError **err, char *key1, char *value1, char *key2, char *value2)
@@ -137,8 +147,8 @@ type Storage struct {
137147
handle unsafe.Pointer
138148
// label of the secret schema
139149
label string
140-
// freeError, lookup and store are the addresses of libsecret functions
141-
freeError, lookup, store unsafe.Pointer
150+
// clear, freeError, lookup and store are the addresses of libsecret functions
151+
clear, freeError, lookup, store unsafe.Pointer
142152
// schema identifies the cached data in the secret service
143153
schema *C.schema
144154
}
@@ -179,6 +189,10 @@ func New(name string, opts ...option) (*Storage, error) {
179189
}
180190
})
181191

192+
clear, err := s.symbol("secret_password_clear_sync")
193+
if err != nil {
194+
return nil, err
195+
}
182196
freeError, err := s.symbol("g_error_free")
183197
if err != nil {
184198
return nil, err
@@ -191,6 +205,7 @@ func New(name string, opts ...option) (*Storage, error) {
191205
if err != nil {
192206
return nil, err
193207
}
208+
s.clear = clear
194209
s.freeError = freeError
195210
s.lookup = lookup
196211
s.store = store
@@ -205,6 +220,27 @@ func New(name string, opts ...option) (*Storage, error) {
205220
return &s, nil
206221
}
207222

223+
// Delete deletes the stored data, if any exists.
224+
func (s *Storage) Delete(context.Context) error {
225+
// the first nil terminates the list and libsecret ignores any extras
226+
attrs := []*C.char{nil, nil, nil, nil}
227+
for i, attr := range s.attributes {
228+
name := C.CString(attr.name)
229+
defer C.free(unsafe.Pointer(name))
230+
value := C.CString(attr.value)
231+
defer C.free(unsafe.Pointer(value))
232+
attrs[i*2] = name
233+
attrs[(i*2)+1] = value
234+
}
235+
var e *C.gError
236+
_ = C.clear(s.clear, s.schema, nil, &e, attrs[0], attrs[1], attrs[2], attrs[3])
237+
if e != nil {
238+
defer C.free_g_error(s.freeError, e)
239+
return fmt.Errorf("couldn't delete cache data: %q", C.GoString(e.message))
240+
}
241+
return nil
242+
}
243+
208244
// Read returns data stored according to the secret schema or, if no such data exists, a nil slice and nil error.
209245
func (s *Storage) Read(context.Context) ([]byte, error) {
210246
// the first nil terminates the list and libsecret ignores any extras
@@ -274,3 +310,5 @@ func (s *Storage) symbol(name string) (unsafe.Pointer, error) {
274310
}
275311
return fp, nil
276312
}
313+
314+
var _ Accessor = (*Storage)(nil)

cache/accessor/storage_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ var (
2525
manualTests = runtime.GOOS == "windows" || os.Getenv(msalextManualTest) != ""
2626
)
2727

28-
func TestReadWrite(t *testing.T) {
28+
func TestReadWriteDelete(t *testing.T) {
2929
if !manualTests {
3030
t.Skipf("set %s to run this test", msalextManualTest)
3131
}
@@ -51,6 +51,8 @@ func TestReadWrite(t *testing.T) {
5151
actual, err := a.Read(ctx)
5252
require.NoError(t, err)
5353
require.Equal(t, test.want, actual)
54+
55+
require.NoError(t, a.Delete(context.Background()))
5456
})
5557
}
5658
}

cache/accessor/windows.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,17 @@ func New(p string) (*Storage, error) {
2828
return &Storage{m: &sync.RWMutex{}, p: p}, nil
2929
}
3030

31+
// Delete deletes the file, if it exists.
32+
func (s *Storage) Delete(context.Context) error {
33+
s.m.Lock()
34+
defer s.m.Unlock()
35+
err := os.Remove(s.p)
36+
if errors.Is(err, os.ErrNotExist) {
37+
return nil
38+
}
39+
return err
40+
}
41+
3142
// Read returns data from the file. If the file doesn't exist, Read returns a nil slice and error.
3243
func (s *Storage) Read(context.Context) ([]byte, error) {
3344
s.m.RLock()
@@ -100,3 +111,5 @@ func dpapi(op operation, data []byte) (result []byte, err error) {
100111
}
101112
return result, err
102113
}
114+
115+
var _ Accessor = (*Storage)(nil)

cache/cache_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ type fakeExternalCache struct {
2626
readCallback, writeCallback func() error
2727
}
2828

29+
func (c *fakeExternalCache) Delete(context.Context) error {
30+
c.data = nil
31+
return nil
32+
}
33+
2934
func (a *fakeExternalCache) Read(context.Context) ([]byte, error) {
3035
var err error
3136
if a.readCallback != nil {

0 commit comments

Comments
 (0)