/
authcode.go
300 lines (244 loc) · 9.56 KB
/
authcode.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
// TODO: We should upgrade credential keys to use a cryptographically secure
// hash algorithm.
/* #nosec G401 G505 */
package persistence
import (
"context"
"crypto/sha1"
"fmt"
"time"
"github.com/hashicorp/vault/sdk/helper/locksutil"
"github.com/hashicorp/vault/sdk/logical"
"github.com/puppetlabs/leg/timeutil/pkg/clockctx"
"github.com/puppetlabs/vault-plugin-secrets-oauthapp/v3/pkg/provider"
"github.com/puppetlabs/vault-plugin-secrets-oauthapp/v3/pkg/vaultext"
)
const (
authCodeKeyPrefix = "creds/"
deviceAuthKeyPrefix = "devices/"
)
type AuthCodeKeyer interface {
// AuthCodeKey returns the storage key for storing AuthCodeEntry objects.
AuthCodeKey() string
// DeviceAuthKey returns the storage key for storing DeviceAuthEntry
// objects.
DeviceAuthKey() string
}
type AuthCodeEntry struct {
// We embed a *provider.Token as the base type. This ensures compatibility
// and keeps storage size reasonable because this will be the default
// configuration.
*provider.Token `json:",inline"`
// AuthServerName is the authorization server we should use to handle this
// entry.
AuthServerName string `json:"auth_server_name"`
// MaximumExpirySeconds caps issued auth tokens to a desired lifetime.
MaximumExpirySeconds int `json:"maximum_expiry_seconds,omitempty"`
// LastIssueTime is the most recent time a token was successfully issued.
LastIssueTime time.Time `json:"last_issue_time,omitempty"`
// AuthServerError indicates that the actual backing server and provider
// could not be acquired to make this token request.
AuthServerError string `json:"auth_server_error,omitempty"`
// UserError is used to store a permanent error that indicates the end of
// this token's usable lifespan.
UserError string `json:"user_error,omitempty"`
// TransientErrorsSinceLastIssue is a counter of the number of transient
// errors encountered since the last time the token was successfully issued
// (either originally or by refresh).
TransientErrorsSinceLastIssue int `json:"transient_errors_since_last_issue,omitempty"`
// If TransientErrorsSinceLastIssue > 0, this holds the last transient error
// encountered to include as a warning (if the token is still valid) or
// error on the response.
LastTransientError string `json:"last_transient_error,omitempty"`
// If the most recent exchange did not succeed, this holds the time that
// exchange occurred.
LastAttemptedIssueTime time.Time `json:"last_attempted_issue_time,omitempty"`
}
func (ace *AuthCodeEntry) SetToken(ctx context.Context, tok *provider.Token) {
ace.Token = tok
ace.LastIssueTime = clockctx.Clock(ctx).Now()
if ace.MaximumExpirySeconds != 0 {
maximumExpiry := ace.LastIssueTime.Add(time.Duration(ace.MaximumExpirySeconds) * time.Second)
if ace.Expiry.IsZero() || ace.Expiry.After(maximumExpiry) {
ace.Expiry = maximumExpiry
}
}
ace.AuthServerError = ""
ace.UserError = ""
ace.TransientErrorsSinceLastIssue = 0
ace.LastTransientError = ""
ace.LastAttemptedIssueTime = time.Time{}
}
func (ace *AuthCodeEntry) SetAuthServerError(ctx context.Context, err string) {
ace.AuthServerError = err
ace.LastAttemptedIssueTime = clockctx.Clock(ctx).Now()
}
func (ace *AuthCodeEntry) SetUserError(ctx context.Context, err string) {
ace.AuthServerError = ""
ace.UserError = err
ace.LastAttemptedIssueTime = clockctx.Clock(ctx).Now()
}
func (ace *AuthCodeEntry) SetTransientError(ctx context.Context, err string) {
ace.AuthServerError = ""
ace.TransientErrorsSinceLastIssue++
ace.LastTransientError = err
ace.LastAttemptedIssueTime = clockctx.Clock(ctx).Now()
}
// TokenIssued indicates whether a token has been issued at all.
//
// For certain grant types, like device code flow, we may not have an access
// token yet. In that case, we must wait for a polling process to update this
// value. A temporary error will be returned.
func (ace *AuthCodeEntry) TokenIssued() bool {
return ace.Token != nil && ace.AccessToken != ""
}
type DeviceAuthEntry struct {
DeviceCode string `json:"device_code"`
Interval int32 `json:"interval"`
LastAttemptedIssueTime time.Time `json:"last_attempted_issue_time"`
ProviderOptions map[string]string `json:"provider_options"`
}
func (dae *DeviceAuthEntry) ShouldPoll(ctx context.Context) bool {
return dae.LastAttemptedIssueTime.Add(time.Duration(dae.Interval) * time.Second).Before(clockctx.Clock(ctx).Now())
}
type AuthCodeKey string
var _ AuthCodeKeyer = AuthCodeKey("")
func (ack AuthCodeKey) AuthCodeKey() string { return authCodeKeyPrefix + string(ack) }
func (ack AuthCodeKey) DeviceAuthKey() string { return deviceAuthKeyPrefix + string(ack) }
func AuthCodeName(name string) AuthCodeKeyer {
hash := sha1.Sum([]byte(name))
first, second, rest := hash[:2], hash[2:4], hash[4:]
return AuthCodeKey(fmt.Sprintf("%x/%x/%x", first, second, rest))
}
type LockedAuthCodeManager struct {
storage logical.Storage
keyer AuthCodeKeyer
}
func (lacm *LockedAuthCodeManager) ReadAuthCodeEntry(ctx context.Context) (*AuthCodeEntry, error) {
se, err := lacm.storage.Get(ctx, lacm.keyer.AuthCodeKey())
if err != nil {
return nil, err
} else if se == nil {
return nil, nil
}
entry := &AuthCodeEntry{}
if err := se.DecodeJSON(entry); err != nil {
return nil, err
}
// UPGRADING (v2): Set the server name to the default legacy server if it is
// not present here.
if entry.AuthServerName == "" {
entry.AuthServerName = LegacyAuthServerName
}
return entry, nil
}
func (lacm *LockedAuthCodeManager) ReadDeviceAuthEntry(ctx context.Context) (*DeviceAuthEntry, error) {
se, err := lacm.storage.Get(ctx, lacm.keyer.DeviceAuthKey())
if err != nil {
return nil, err
} else if se == nil {
return nil, nil
}
entry := &DeviceAuthEntry{}
if err := se.DecodeJSON(entry); err != nil {
return nil, err
}
return entry, nil
}
func (lacm *LockedAuthCodeManager) WriteAuthCodeEntry(ctx context.Context, entry *AuthCodeEntry) error {
se, err := logical.StorageEntryJSON(lacm.keyer.AuthCodeKey(), entry)
if err != nil {
return err
}
return lacm.storage.Put(ctx, se)
}
func (lacm *LockedAuthCodeManager) WriteDeviceAuthEntry(ctx context.Context, entry *DeviceAuthEntry) error {
se, err := logical.StorageEntryJSON(lacm.keyer.DeviceAuthKey(), entry)
if err != nil {
return err
}
return lacm.storage.Put(ctx, se)
}
func (lacm *LockedAuthCodeManager) DeleteAuthCodeEntry(ctx context.Context) error {
return lacm.storage.Delete(ctx, lacm.keyer.AuthCodeKey())
}
func (lacm *LockedAuthCodeManager) DeleteDeviceAuthEntry(ctx context.Context) error {
return lacm.storage.Delete(ctx, lacm.keyer.DeviceAuthKey())
}
type LockedAuthCodeHolder struct {
keyer AuthCodeKeyer
}
func (lach *LockedAuthCodeHolder) Manager(storage logical.Storage) *LockedAuthCodeManager {
return &LockedAuthCodeManager{
storage: storage,
keyer: lach.keyer,
}
}
type AuthCodeLocker interface {
WithLock(AuthCodeKeyer, func(*LockedAuthCodeHolder) error) error
}
type AuthCodeManager struct {
storage logical.Storage
locker AuthCodeLocker
}
func (acm *AuthCodeManager) ReadAuthCodeEntry(ctx context.Context, keyer AuthCodeKeyer) (*AuthCodeEntry, error) {
var entry *AuthCodeEntry
err := acm.locker.WithLock(keyer, func(lach *LockedAuthCodeHolder) (err error) {
entry, err = lach.Manager(acm.storage).ReadAuthCodeEntry(ctx)
return
})
return entry, err
}
func (acm *AuthCodeManager) ReadDeviceAuthEntry(ctx context.Context, keyer AuthCodeKeyer) (*DeviceAuthEntry, error) {
var entry *DeviceAuthEntry
err := acm.locker.WithLock(keyer, func(lach *LockedAuthCodeHolder) (err error) {
entry, err = lach.Manager(acm.storage).ReadDeviceAuthEntry(ctx)
return
})
return entry, err
}
func (acm *AuthCodeManager) WriteAuthCodeEntry(ctx context.Context, keyer AuthCodeKeyer, entry *AuthCodeEntry) error {
return acm.locker.WithLock(keyer, func(lach *LockedAuthCodeHolder) error {
return lach.Manager(acm.storage).WriteAuthCodeEntry(ctx, entry)
})
}
func (acm *AuthCodeManager) WriteDeviceAuthEntry(ctx context.Context, keyer AuthCodeKeyer, entry *DeviceAuthEntry) error {
return acm.locker.WithLock(keyer, func(lach *LockedAuthCodeHolder) error {
return lach.Manager(acm.storage).WriteDeviceAuthEntry(ctx, entry)
})
}
func (acm *AuthCodeManager) DeleteAuthCodeEntry(ctx context.Context, keyer AuthCodeKeyer) error {
return acm.locker.WithLock(keyer, func(lach *LockedAuthCodeHolder) error {
return lach.Manager(acm.storage).DeleteAuthCodeEntry(ctx)
})
}
func (acm *AuthCodeManager) DeleteDeviceAuthEntry(ctx context.Context, keyer AuthCodeKeyer) error {
return acm.locker.WithLock(keyer, func(lach *LockedAuthCodeHolder) error {
return lach.Manager(acm.storage).DeleteDeviceAuthEntry(ctx)
})
}
func (acm *AuthCodeManager) ForEachAuthCodeKey(ctx context.Context, fn func(AuthCodeKeyer) error) error {
view := logical.NewStorageView(acm.storage, authCodeKeyPrefix)
return vaultext.ScanView(ctx, view, func(path string) error { return fn(AuthCodeKey(path)) })
}
func (acm *AuthCodeManager) ForEachDeviceAuthKey(ctx context.Context, fn func(AuthCodeKeyer) error) error {
view := logical.NewStorageView(acm.storage, deviceAuthKeyPrefix)
return vaultext.ScanView(ctx, view, func(path string) error { return fn(AuthCodeKey(path)) })
}
type AuthCodeHolder struct {
locks []*locksutil.LockEntry
}
func (ach *AuthCodeHolder) WithLock(keyer AuthCodeKeyer, fn func(*LockedAuthCodeHolder) error) error {
lock := locksutil.LockForKey(ach.locks, keyer.AuthCodeKey())
lock.Lock()
defer lock.Unlock()
return fn(&LockedAuthCodeHolder{
keyer: keyer,
})
}
func (ach *AuthCodeHolder) Manager(storage logical.Storage) *AuthCodeManager {
return &AuthCodeManager{
storage: storage,
locker: ach,
}
}