master
go 81 lines 1.94 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package cloudauth
4
5 import (
6 "context"
7 "errors"
8 "fmt"
9 "slices"
10 "sync"
11 "time"
12
13 "github.com/Azure/azure-sdk-for-go/sdk/azcore"
14 "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
15 )
16
17 const (
18 DefaultTokenRefreshMargin = 5 * time.Minute
19 )
20
21 type TokenProvider struct {
22 mu sync.Mutex
23 cred azcore.TokenCredential
24 scopes []string
25 refreshMargin time.Duration
26 now func() time.Time
27
28 cachedToken string
29 cachedExpiry time.Time
30 }
31
32 func NewTokenProvider(cred azcore.TokenCredential, scopes []string, refreshMargin time.Duration) (*TokenProvider, error) {
33 if cred == nil {
34 return nil, errors.New("token credential is nil")
35 }
36 if len(scopes) == 0 {
37 return nil, errors.New("token scopes are required")
38 }
39 if slices.Contains(scopes, "") {
40 return nil, errors.New("token scopes contain an empty value")
41 }
42 if refreshMargin <= 0 {
43 refreshMargin = DefaultTokenRefreshMargin
44 }
45
46 return &TokenProvider{
47 cred: cred,
48 scopes: scopes,
49 refreshMargin: refreshMargin,
50 now: time.Now,
51 }, nil
52 }
53
54 func (p *TokenProvider) Token(ctx context.Context) (string, time.Time, error) {
55 p.mu.Lock()
56 defer p.mu.Unlock()
57
58 now := p.now()
59 if p.cachedToken != "" && now.Add(p.refreshMargin).Before(p.cachedExpiry) {
60 return p.cachedToken, p.cachedExpiry, nil
61 }
62
63 token, err := p.cred.GetToken(ctx, policy.TokenRequestOptions{Scopes: p.scopes})
64 if err != nil {
65 // Fall back to cached token if it hasn't expired yet
66 // Re-evaluate time after refresh attempt, because GetToken may block.
67 fallbackNow := p.now()
68 if p.cachedToken != "" && fallbackNow.Before(p.cachedExpiry) {
69 return p.cachedToken, p.cachedExpiry, nil
70 }
71 return "", time.Time{}, err
72 }
73 if token.Token == "" {
74 return "", time.Time{}, fmt.Errorf("received empty token for scopes %v", p.scopes)
75 }
76
77 p.cachedToken = token.Token
78 p.cachedExpiry = token.ExpiresOn
79
80 return p.cachedToken, p.cachedExpiry, nil
81 }