/
cache.go
103 lines (94 loc) · 2.3 KB
/
cache.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
package openid
import (
"fmt"
"net/http"
"sync"
"log/slog"
"github.com/rancher/opni/pkg/logger"
)
type UserInfo struct {
raw map[string]interface{}
identifyingClaim string
}
func (uid *UserInfo) UserID() (string, error) {
if v, ok := uid.raw[uid.identifyingClaim]; ok {
return fmt.Sprint(v), nil
}
return "", fmt.Errorf("identifying claim %q not found in user info", uid.identifyingClaim)
}
type UserInfoCache struct {
ClientOptions
cache map[string]*UserInfo // key=access token
knownUsers map[string]string // key=user id, value=access token
mu sync.Mutex
config *OpenidConfig
wellKnown *WellKnownConfiguration
logger *slog.Logger
}
func NewUserInfoCache(
config *OpenidConfig,
logger *slog.Logger,
opts ...ClientOption,
) (*UserInfoCache, error) {
options := ClientOptions{
client: http.DefaultClient,
}
options.apply(opts...)
wellKnown, err := config.GetWellKnownConfiguration()
if err != nil {
return nil, err
}
if config.IdentifyingClaim == "" {
return nil, fmt.Errorf("no identifying claim set")
}
return &UserInfoCache{
ClientOptions: options,
cache: make(map[string]*UserInfo),
knownUsers: make(map[string]string),
config: config,
wellKnown: wellKnown,
logger: logger,
}, nil
}
func (c *UserInfoCache) Get(accessToken string) (*UserInfo, error) {
lg := c.logger
c.mu.Lock()
defer c.mu.Unlock()
if info, ok := c.cache[accessToken]; ok {
return info, nil
}
lg.Debug("fetching user info from openid provider")
rawUserInfo, err := FetchUserInfo(c.wellKnown.UserinfoEndpoint, accessToken,
WithHTTPClient(c.client),
)
if err != nil {
lg.With(
logger.Err(err),
).Error("failed to fetch user info")
return nil, err
}
info := &UserInfo{
raw: rawUserInfo,
identifyingClaim: c.config.IdentifyingClaim,
}
id, err := info.UserID()
if err != nil {
lg.With(
logger.Err(err),
).Error("user info is invalid")
return nil, err
}
if previousAccessToken, ok := c.knownUsers[id]; ok {
if previousAccessToken != accessToken {
lg.With(
info.identifyingClaim, id,
).Debug("user access token was refreshed")
c.knownUsers[id] = accessToken
delete(c.cache, previousAccessToken)
}
} else {
c.knownUsers[id] = accessToken
}
c.cache[accessToken] = info
return info, nil
}