|
| 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 | +} |
0 commit comments