Skip to content

Commit f302488

Browse files
authored
macOS storage accessor (#18)
1 parent a4ebaff commit f302488

File tree

5 files changed

+104
-2
lines changed

5 files changed

+104
-2
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
//go:build darwin && cgo
5+
// +build darwin,cgo
6+
7+
package accessor
8+
9+
import (
10+
"context"
11+
"errors"
12+
13+
"github.com/keybase/go-keychain"
14+
)
15+
16+
type option func(*Storage) error
17+
18+
// WithAccount sets an optional account name for the keychain item holding cached data.
19+
func WithAccount(name string) option {
20+
return func(s *Storage) error {
21+
s.account = name
22+
return nil
23+
}
24+
}
25+
26+
// Storage stores data as a password on the macOS keychain. The keychain must be unlocked before Storage can read
27+
// or write data. macOS may not allow keychain access from a headless environment such as an SSH session.
28+
type Storage struct {
29+
account, service string
30+
}
31+
32+
// New is the constructor for Storage. "servName" is the service name for the keychain item holding cached data.
33+
func New(servName string, opts ...option) (*Storage, error) {
34+
if servName == "" {
35+
return nil, errors.New("servName can't be empty")
36+
}
37+
s := Storage{service: servName}
38+
for _, o := range opts {
39+
if err := o(&s); err != nil {
40+
return nil, err
41+
}
42+
}
43+
return &s, nil
44+
}
45+
46+
// Read returns data stored on the keychain or, if the keychain item doesn't exist, a nil slice and nil error.
47+
func (s *Storage) Read(context.Context) ([]byte, error) {
48+
data, err := keychain.GetGenericPassword(s.service, s.account, "", "")
49+
if err != nil {
50+
return nil, err
51+
}
52+
return data, nil
53+
}
54+
55+
// Write stores data on the keychain.
56+
func (s *Storage) Write(_ context.Context, data []byte) error {
57+
pw, err := keychain.GetGenericPassword(s.service, s.account, "", "")
58+
if err != nil {
59+
return err
60+
}
61+
item := keychain.NewGenericPassword(s.service, s.account, "", nil, "")
62+
if pw == nil {
63+
// password not found: add it to the keychain
64+
item.SetData(data)
65+
err = keychain.AddItem(item)
66+
} else {
67+
// password found: update its value
68+
update := keychain.NewGenericPassword(s.service, s.account, "", data, "")
69+
err = keychain.UpdateItem(item, update)
70+
}
71+
return err
72+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
//go:build darwin && cgo
5+
// +build darwin,cgo
6+
7+
package accessor
8+
9+
import (
10+
"testing"
11+
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func TestWithAccount(t *testing.T) {
16+
account := "account"
17+
a, err := New(t.Name(), WithAccount(account))
18+
require.NoError(t, err)
19+
20+
expected := []byte("expected")
21+
err = a.Write(ctx, expected)
22+
require.NoError(t, err)
23+
24+
actual, err := a.Read(ctx)
25+
require.NoError(t, err)
26+
require.Equal(t, expected, actual)
27+
}

extensions/cache/accessor/storage_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License. See LICENSE in the project root for license information.
33

4-
//go:build (linux && cgo) || windows
5-
// +build linux,cgo windows
4+
//go:build (darwin && cgo) || (linux && cgo) || windows
5+
// +build darwin,cgo linux,cgo windows
66

77
package accessor
88

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.18
44

55
require (
66
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0
7+
github.com/keybase/go-keychain v0.0.0-20230523030712-b5615109f100
78
github.com/stretchr/testify v1.8.2
89
golang.org/x/sys v0.8.0
910
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5
77
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
88
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
99
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
10+
github.com/keybase/go-keychain v0.0.0-20230523030712-b5615109f100 h1:rG3VnJUnAWyiv7qYmmdOdSapzz6HM+zb9/uRFr0T5EM=
11+
github.com/keybase/go-keychain v0.0.0-20230523030712-b5615109f100/go.mod h1:qDHUvIjGZJUtdPtuP4WMu5/U4aVWbFw1MhlkJqCGmCQ=
1012
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
1113
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
1214
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=

0 commit comments

Comments
 (0)