/
accesstoken.go
144 lines (117 loc) · 4.75 KB
/
accesstoken.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
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package accesstoken
import (
"context"
"fmt"
"time"
"github.com/ory/fosite"
"github.com/ory/fosite/handler/oauth2"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
"go.pinniped.dev/internal/constable"
"go.pinniped.dev/internal/crud"
"go.pinniped.dev/internal/federationdomain/clientregistry"
"go.pinniped.dev/internal/federationdomain/timeouts"
"go.pinniped.dev/internal/fositestorage"
"go.pinniped.dev/internal/psession"
)
const (
TypeLabelValue = "access-token"
ErrInvalidAccessTokenRequestVersion = constable.Error("access token request data has wrong version")
ErrInvalidAccessTokenRequestData = constable.Error("access token request data must be present")
// Version 1 was the initial release of storage.
// Version 2 is when we switched to storing psession.PinnipedSession inside the fosite request.
// Version 3 is when we added the Username field to the psession.CustomSessionData.
// Version 4 is when fosite added json tags to their openid.DefaultSession struct.
// Version 5 is when we added the UpstreamUsername and UpstreamGroups fields to psession.CustomSessionData.
// Version 6 is when we upgraded fosite in Dec 2023.
// Version 7 is when OIDCClients were given configurable ID token lifetimes.
accessTokenStorageVersion = "7"
)
type RevocationStorage interface {
oauth2.AccessTokenStorage
RevokeAccessToken(ctx context.Context, requestID string) error
}
var _ RevocationStorage = &accessTokenStorage{}
type accessTokenStorage struct {
storage crud.Storage
lifetime timeouts.StorageLifetime
}
type Session struct {
Request *fosite.Request `json:"request"`
Version string `json:"version"`
}
func New(secrets corev1client.SecretInterface, clock func() time.Time, sessionStorageLifetime timeouts.StorageLifetime) RevocationStorage {
return &accessTokenStorage{storage: crud.New(TypeLabelValue, secrets, clock), lifetime: sessionStorageLifetime}
}
// ReadFromSecret reads the contents of a Secret as a Session.
func ReadFromSecret(secret *corev1.Secret) (*Session, error) {
session := newValidEmptyAccessTokenSession()
err := crud.FromSecret(TypeLabelValue, secret, session)
if err != nil {
return nil, err
}
if session.Version != accessTokenStorageVersion {
return nil, fmt.Errorf("%w: access token session has version %s instead of %s",
ErrInvalidAccessTokenRequestVersion, session.Version, accessTokenStorageVersion)
}
if session.Request.ID == "" {
return nil, fmt.Errorf("malformed access token session: %w", ErrInvalidAccessTokenRequestData)
}
return session, nil
}
func (a *accessTokenStorage) RevokeAccessToken(ctx context.Context, requestID string) error {
return a.storage.DeleteByLabel(ctx, fositestorage.StorageRequestIDLabelName, requestID)
}
func (a *accessTokenStorage) CreateAccessTokenSession(ctx context.Context, signature string, requester fosite.Requester) error {
request, err := fositestorage.ValidateAndExtractAuthorizeRequest(requester)
if err != nil {
return err
}
_, err = a.storage.Create(ctx,
signature,
&Session{Request: request, Version: accessTokenStorageVersion},
map[string]string{fositestorage.StorageRequestIDLabelName: requester.GetID()},
nil,
a.lifetime(requester),
)
return err
}
func (a *accessTokenStorage) GetAccessTokenSession(ctx context.Context, signature string, _ fosite.Session) (fosite.Requester, error) {
session, _, err := a.getSession(ctx, signature)
if err != nil {
return nil, err
}
return session.Request, err
}
func (a *accessTokenStorage) DeleteAccessTokenSession(ctx context.Context, signature string) error {
return a.storage.Delete(ctx, signature)
}
func (a *accessTokenStorage) getSession(ctx context.Context, signature string) (*Session, string, error) {
session := newValidEmptyAccessTokenSession()
rv, err := a.storage.Get(ctx, signature, session)
if errors.IsNotFound(err) {
return nil, "", fosite.ErrNotFound.WithWrap(err).WithDebug(err.Error())
}
if err != nil {
return nil, "", fmt.Errorf("failed to get access token session for %s: %w", signature, err)
}
if version := session.Version; version != accessTokenStorageVersion {
return nil, "", fmt.Errorf("%w: access token session for %s has version %s instead of %s",
ErrInvalidAccessTokenRequestVersion, signature, version, accessTokenStorageVersion)
}
if session.Request.ID == "" {
return nil, "", fmt.Errorf("malformed access token session for %s: %w", signature, ErrInvalidAccessTokenRequestData)
}
return session, rv, nil
}
func newValidEmptyAccessTokenSession() *Session {
return &Session{
Request: &fosite.Request{
Client: &clientregistry.Client{},
Session: &psession.PinnipedSession{},
},
}
}