Move OIDC Token structs into a new oidctypes package.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
This commit is contained in:
Matt Moyer 2020-11-30 17:02:03 -06:00
parent d64acbb5a9
commit d32583dd7f
No known key found for this signature in database
GPG Key ID: EAE88AD172C5AE2D
14 changed files with 162 additions and 155 deletions

View File

@ -20,6 +20,7 @@ import (
"go.pinniped.dev/pkg/oidcclient" "go.pinniped.dev/pkg/oidcclient"
"go.pinniped.dev/pkg/oidcclient/filesession" "go.pinniped.dev/pkg/oidcclient/filesession"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
) )
//nolint: gochecknoinits //nolint: gochecknoinits
@ -27,7 +28,7 @@ func init() {
loginCmd.AddCommand(oidcLoginCommand(oidcclient.Login)) loginCmd.AddCommand(oidcLoginCommand(oidcclient.Login))
} }
func oidcLoginCommand(loginFunc func(issuer string, clientID string, opts ...oidcclient.Option) (*oidcclient.Token, error)) *cobra.Command { func oidcLoginCommand(loginFunc func(issuer string, clientID string, opts ...oidcclient.Option) (*oidctypes.Token, error)) *cobra.Command {
var ( var (
cmd = cobra.Command{ cmd = cobra.Command{
Args: cobra.NoArgs, Args: cobra.NoArgs,

View File

@ -13,6 +13,7 @@ import (
"go.pinniped.dev/internal/here" "go.pinniped.dev/internal/here"
"go.pinniped.dev/pkg/oidcclient" "go.pinniped.dev/pkg/oidcclient"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
) )
func TestLoginOIDCCommand(t *testing.T) { func TestLoginOIDCCommand(t *testing.T) {
@ -92,12 +93,12 @@ func TestLoginOIDCCommand(t *testing.T) {
gotClientID string gotClientID string
gotOptions []oidcclient.Option gotOptions []oidcclient.Option
) )
cmd := oidcLoginCommand(func(issuer string, clientID string, opts ...oidcclient.Option) (*oidcclient.Token, error) { cmd := oidcLoginCommand(func(issuer string, clientID string, opts ...oidcclient.Option) (*oidctypes.Token, error) {
gotIssuer = issuer gotIssuer = issuer
gotClientID = clientID gotClientID = clientID
gotOptions = opts gotOptions = opts
return &oidcclient.Token{ return &oidctypes.Token{
IDToken: &oidcclient.IDToken{ IDToken: &oidctypes.IDToken{
Token: "test-id-token", Token: "test-id-token",
Expiry: metav1.NewTime(time1), Expiry: metav1.NewTime(time1),
}, },

View File

@ -23,8 +23,8 @@ import (
"go.pinniped.dev/internal/oidc" "go.pinniped.dev/internal/oidc"
"go.pinniped.dev/internal/oidc/oidctestutil" "go.pinniped.dev/internal/oidc/oidctestutil"
"go.pinniped.dev/internal/testutil" "go.pinniped.dev/internal/testutil"
"go.pinniped.dev/pkg/oidcclient"
"go.pinniped.dev/pkg/oidcclient/nonce" "go.pinniped.dev/pkg/oidcclient/nonce"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
"go.pinniped.dev/pkg/oidcclient/pkce" "go.pinniped.dev/pkg/oidcclient/pkce"
) )
@ -651,8 +651,8 @@ func (u *upstreamOIDCIdentityProviderBuilder) Build() oidctestutil.TestUpstreamO
UsernameClaim: u.usernameClaim, UsernameClaim: u.usernameClaim,
GroupsClaim: u.groupsClaim, GroupsClaim: u.groupsClaim,
Scopes: []string{"scope1", "scope2"}, Scopes: []string{"scope1", "scope2"},
ExchangeAuthcodeAndValidateTokensFunc: func(ctx context.Context, authcode string, pkceCodeVerifier pkce.Code, expectedIDTokenNonce nonce.Nonce) (oidcclient.Token, map[string]interface{}, error) { ExchangeAuthcodeAndValidateTokensFunc: func(ctx context.Context, authcode string, pkceCodeVerifier pkce.Code, expectedIDTokenNonce nonce.Nonce) (oidctypes.Token, map[string]interface{}, error) {
return oidcclient.Token{}, u.idToken, u.authcodeExchangeErr return oidctypes.Token{}, u.idToken, u.authcodeExchangeErr
}, },
} }
} }

View File

@ -8,8 +8,8 @@ import (
"net/url" "net/url"
"go.pinniped.dev/internal/oidc/provider" "go.pinniped.dev/internal/oidc/provider"
"go.pinniped.dev/pkg/oidcclient"
"go.pinniped.dev/pkg/oidcclient/nonce" "go.pinniped.dev/pkg/oidcclient/nonce"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
"go.pinniped.dev/pkg/oidcclient/pkce" "go.pinniped.dev/pkg/oidcclient/pkce"
) )
@ -36,7 +36,7 @@ type TestUpstreamOIDCIdentityProvider struct {
authcode string, authcode string,
pkceCodeVerifier pkce.Code, pkceCodeVerifier pkce.Code,
expectedIDTokenNonce nonce.Nonce, expectedIDTokenNonce nonce.Nonce,
) (oidcclient.Token, map[string]interface{}, error) ) (oidctypes.Token, map[string]interface{}, error)
exchangeAuthcodeAndValidateTokensCallCount int exchangeAuthcodeAndValidateTokensCallCount int
exchangeAuthcodeAndValidateTokensArgs []*ExchangeAuthcodeAndValidateTokenArgs exchangeAuthcodeAndValidateTokensArgs []*ExchangeAuthcodeAndValidateTokenArgs
@ -71,7 +71,7 @@ func (u *TestUpstreamOIDCIdentityProvider) ExchangeAuthcodeAndValidateTokens(
authcode string, authcode string,
pkceCodeVerifier pkce.Code, pkceCodeVerifier pkce.Code,
expectedIDTokenNonce nonce.Nonce, expectedIDTokenNonce nonce.Nonce,
) (oidcclient.Token, map[string]interface{}, error) { ) (oidctypes.Token, map[string]interface{}, error) {
if u.exchangeAuthcodeAndValidateTokensArgs == nil { if u.exchangeAuthcodeAndValidateTokensArgs == nil {
u.exchangeAuthcodeAndValidateTokensArgs = make([]*ExchangeAuthcodeAndValidateTokenArgs, 0) u.exchangeAuthcodeAndValidateTokensArgs = make([]*ExchangeAuthcodeAndValidateTokenArgs, 0)
} }

View File

@ -8,8 +8,8 @@ import (
"net/url" "net/url"
"sync" "sync"
"go.pinniped.dev/pkg/oidcclient"
"go.pinniped.dev/pkg/oidcclient/nonce" "go.pinniped.dev/pkg/oidcclient/nonce"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
"go.pinniped.dev/pkg/oidcclient/pkce" "go.pinniped.dev/pkg/oidcclient/pkce"
) )
@ -40,7 +40,7 @@ type UpstreamOIDCIdentityProviderI interface {
authcode string, authcode string,
pkceCodeVerifier pkce.Code, pkceCodeVerifier pkce.Code,
expectedIDTokenNonce nonce.Nonce, expectedIDTokenNonce nonce.Nonce,
) (tokens oidcclient.Token, parsedIDTokenClaims map[string]interface{}, err error) ) (tokens oidctypes.Token, parsedIDTokenClaims map[string]interface{}, err error)
} }
type DynamicUpstreamIDPProvider interface { type DynamicUpstreamIDPProvider interface {

View File

@ -15,8 +15,8 @@ import (
"go.pinniped.dev/internal/httputil/httperr" "go.pinniped.dev/internal/httputil/httperr"
"go.pinniped.dev/internal/oidc/provider" "go.pinniped.dev/internal/oidc/provider"
"go.pinniped.dev/pkg/oidcclient"
"go.pinniped.dev/pkg/oidcclient/nonce" "go.pinniped.dev/pkg/oidcclient/nonce"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
"go.pinniped.dev/pkg/oidcclient/pkce" "go.pinniped.dev/pkg/oidcclient/pkce"
) )
@ -59,46 +59,46 @@ func (p *ProviderConfig) GetGroupsClaim() string {
return p.GroupsClaim return p.GroupsClaim
} }
func (p *ProviderConfig) ExchangeAuthcodeAndValidateTokens(ctx context.Context, authcode string, pkceCodeVerifier pkce.Code, expectedIDTokenNonce nonce.Nonce) (oidcclient.Token, map[string]interface{}, error) { func (p *ProviderConfig) ExchangeAuthcodeAndValidateTokens(ctx context.Context, authcode string, pkceCodeVerifier pkce.Code, expectedIDTokenNonce nonce.Nonce) (oidctypes.Token, map[string]interface{}, error) {
tok, err := p.Config.Exchange(ctx, authcode, pkceCodeVerifier.Verifier()) tok, err := p.Config.Exchange(ctx, authcode, pkceCodeVerifier.Verifier())
if err != nil { if err != nil {
return oidcclient.Token{}, nil, err return oidctypes.Token{}, nil, err
} }
idTok, hasIDTok := tok.Extra("id_token").(string) idTok, hasIDTok := tok.Extra("id_token").(string)
if !hasIDTok { if !hasIDTok {
return oidcclient.Token{}, nil, httperr.New(http.StatusBadRequest, "received response missing ID token") return oidctypes.Token{}, nil, httperr.New(http.StatusBadRequest, "received response missing ID token")
} }
validated, err := p.Provider.Verifier(&oidc.Config{ClientID: p.GetClientID()}).Verify(ctx, idTok) validated, err := p.Provider.Verifier(&oidc.Config{ClientID: p.GetClientID()}).Verify(ctx, idTok)
if err != nil { if err != nil {
return oidcclient.Token{}, nil, httperr.Wrap(http.StatusBadRequest, "received invalid ID token", err) return oidctypes.Token{}, nil, httperr.Wrap(http.StatusBadRequest, "received invalid ID token", err)
} }
if validated.AccessTokenHash != "" { if validated.AccessTokenHash != "" {
if err := validated.VerifyAccessToken(tok.AccessToken); err != nil { if err := validated.VerifyAccessToken(tok.AccessToken); err != nil {
return oidcclient.Token{}, nil, httperr.Wrap(http.StatusBadRequest, "received invalid ID token", err) return oidctypes.Token{}, nil, httperr.Wrap(http.StatusBadRequest, "received invalid ID token", err)
} }
} }
if expectedIDTokenNonce != "" { if expectedIDTokenNonce != "" {
if err := expectedIDTokenNonce.Validate(validated); err != nil { if err := expectedIDTokenNonce.Validate(validated); err != nil {
return oidcclient.Token{}, nil, httperr.Wrap(http.StatusBadRequest, "received ID token with invalid nonce", err) return oidctypes.Token{}, nil, httperr.Wrap(http.StatusBadRequest, "received ID token with invalid nonce", err)
} }
} }
var validatedClaims map[string]interface{} var validatedClaims map[string]interface{}
if err := validated.Claims(&validatedClaims); err != nil { if err := validated.Claims(&validatedClaims); err != nil {
return oidcclient.Token{}, nil, httperr.Wrap(http.StatusInternalServerError, "could not unmarshal claims", err) return oidctypes.Token{}, nil, httperr.Wrap(http.StatusInternalServerError, "could not unmarshal claims", err)
} }
return oidcclient.Token{ return oidctypes.Token{
AccessToken: &oidcclient.AccessToken{ AccessToken: &oidctypes.AccessToken{
Token: tok.AccessToken, Token: tok.AccessToken,
Type: tok.TokenType, Type: tok.TokenType,
Expiry: metav1.NewTime(tok.Expiry), Expiry: metav1.NewTime(tok.Expiry),
}, },
RefreshToken: &oidcclient.RefreshToken{ RefreshToken: &oidctypes.RefreshToken{
Token: tok.RefreshToken, Token: tok.RefreshToken,
}, },
IDToken: &oidcclient.IDToken{ IDToken: &oidctypes.IDToken{
Token: idTok, Token: idTok,
Expiry: metav1.NewTime(validated.Expiry), Expiry: metav1.NewTime(validated.Expiry),
}, },

View File

@ -19,8 +19,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"go.pinniped.dev/internal/mocks/mockkeyset" "go.pinniped.dev/internal/mocks/mockkeyset"
"go.pinniped.dev/pkg/oidcclient"
"go.pinniped.dev/pkg/oidcclient/nonce" "go.pinniped.dev/pkg/oidcclient/nonce"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
) )
func TestProviderConfig(t *testing.T) { func TestProviderConfig(t *testing.T) {
@ -62,7 +62,7 @@ func TestProviderConfig(t *testing.T) {
expectNonce nonce.Nonce expectNonce nonce.Nonce
returnIDTok string returnIDTok string
wantErr string wantErr string
wantToken oidcclient.Token wantToken oidctypes.Token
wantClaims map[string]interface{} wantClaims map[string]interface{}
}{ }{
{ {
@ -99,15 +99,15 @@ func TestProviderConfig(t *testing.T) {
authCode: "valid", authCode: "valid",
expectNonce: "", expectNonce: "",
returnIDTok: invalidNonceIDToken, returnIDTok: invalidNonceIDToken,
wantToken: oidcclient.Token{ wantToken: oidctypes.Token{
AccessToken: &oidcclient.AccessToken{ AccessToken: &oidctypes.AccessToken{
Token: "test-access-token", Token: "test-access-token",
Expiry: metav1.Time{}, Expiry: metav1.Time{},
}, },
RefreshToken: &oidcclient.RefreshToken{ RefreshToken: &oidctypes.RefreshToken{
Token: "test-refresh-token", Token: "test-refresh-token",
}, },
IDToken: &oidcclient.IDToken{ IDToken: &oidctypes.IDToken{
Token: invalidNonceIDToken, Token: invalidNonceIDToken,
Expiry: metav1.Time{}, Expiry: metav1.Time{},
}, },
@ -117,15 +117,15 @@ func TestProviderConfig(t *testing.T) {
name: "valid", name: "valid",
authCode: "valid", authCode: "valid",
returnIDTok: validIDToken, returnIDTok: validIDToken,
wantToken: oidcclient.Token{ wantToken: oidctypes.Token{
AccessToken: &oidcclient.AccessToken{ AccessToken: &oidctypes.AccessToken{
Token: "test-access-token", Token: "test-access-token",
Expiry: metav1.Time{}, Expiry: metav1.Time{},
}, },
RefreshToken: &oidcclient.RefreshToken{ RefreshToken: &oidctypes.RefreshToken{
Token: "test-refresh-token", Token: "test-refresh-token",
}, },
IDToken: &oidcclient.IDToken{ IDToken: &oidctypes.IDToken{
Token: validIDToken, Token: validIDToken,
Expiry: metav1.Time{}, Expiry: metav1.Time{},
}, },
@ -184,7 +184,7 @@ func TestProviderConfig(t *testing.T) {
tok, claims, err := p.ExchangeAuthcodeAndValidateTokens(ctx, tt.authCode, "test-pkce", tt.expectNonce) tok, claims, err := p.ExchangeAuthcodeAndValidateTokens(ctx, tt.authCode, "test-pkce", tt.expectNonce)
if tt.wantErr != "" { if tt.wantErr != "" {
require.EqualError(t, err, tt.wantErr) require.EqualError(t, err, tt.wantErr)
require.Equal(t, oidcclient.Token{}, tok) require.Equal(t, oidctypes.Token{}, tok)
require.Nil(t, claims) require.Nil(t, claims)
return return
} }

View File

@ -17,6 +17,7 @@ import (
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"go.pinniped.dev/pkg/oidcclient" "go.pinniped.dev/pkg/oidcclient"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
) )
var ( var (
@ -48,7 +49,7 @@ type (
Key oidcclient.SessionCacheKey `json:"key"` Key oidcclient.SessionCacheKey `json:"key"`
CreationTimestamp metav1.Time `json:"creationTimestamp"` CreationTimestamp metav1.Time `json:"creationTimestamp"`
LastUsedTimestamp metav1.Time `json:"lastUsedTimestamp"` LastUsedTimestamp metav1.Time `json:"lastUsedTimestamp"`
Tokens oidcclient.Token `json:"tokens"` Tokens oidctypes.Token `json:"tokens"`
} }
) )

View File

@ -12,6 +12,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"go.pinniped.dev/pkg/oidcclient" "go.pinniped.dev/pkg/oidcclient"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
) )
// validSession should be the same data as `testdata/valid.yaml`. // validSession should be the same data as `testdata/valid.yaml`.
@ -27,17 +28,17 @@ var validSession = sessionCache{
}, },
CreationTimestamp: metav1.NewTime(time.Date(2020, 10, 20, 18, 42, 7, 0, time.UTC).Local()), CreationTimestamp: metav1.NewTime(time.Date(2020, 10, 20, 18, 42, 7, 0, time.UTC).Local()),
LastUsedTimestamp: metav1.NewTime(time.Date(2020, 10, 20, 18, 45, 31, 0, time.UTC).Local()), LastUsedTimestamp: metav1.NewTime(time.Date(2020, 10, 20, 18, 45, 31, 0, time.UTC).Local()),
Tokens: oidcclient.Token{ Tokens: oidctypes.Token{
AccessToken: &oidcclient.AccessToken{ AccessToken: &oidctypes.AccessToken{
Token: "test-access-token", Token: "test-access-token",
Type: "Bearer", Type: "Bearer",
Expiry: metav1.NewTime(time.Date(2020, 10, 20, 19, 46, 30, 0, time.UTC).Local()), Expiry: metav1.NewTime(time.Date(2020, 10, 20, 19, 46, 30, 0, time.UTC).Local()),
}, },
IDToken: &oidcclient.IDToken{ IDToken: &oidctypes.IDToken{
Token: "test-id-token", Token: "test-id-token",
Expiry: metav1.NewTime(time.Date(2020, 10, 20, 19, 42, 07, 0, time.UTC).Local()), Expiry: metav1.NewTime(time.Date(2020, 10, 20, 19, 42, 07, 0, time.UTC).Local()),
}, },
RefreshToken: &oidcclient.RefreshToken{ RefreshToken: &oidctypes.RefreshToken{
Token: "test-refresh-token", Token: "test-refresh-token",
}, },
}, },
@ -139,8 +140,8 @@ func TestNormalized(t *testing.T) {
// ID token is empty, but not nil. // ID token is empty, but not nil.
{ {
LastUsedTimestamp: metav1.NewTime(now), LastUsedTimestamp: metav1.NewTime(now),
Tokens: oidcclient.Token{ Tokens: oidctypes.Token{
IDToken: &oidcclient.IDToken{ IDToken: &oidctypes.IDToken{
Token: "", Token: "",
Expiry: metav1.NewTime(now.Add(1 * time.Minute)), Expiry: metav1.NewTime(now.Add(1 * time.Minute)),
}, },
@ -149,8 +150,8 @@ func TestNormalized(t *testing.T) {
// ID token is expired. // ID token is expired.
{ {
LastUsedTimestamp: metav1.NewTime(now), LastUsedTimestamp: metav1.NewTime(now),
Tokens: oidcclient.Token{ Tokens: oidctypes.Token{
IDToken: &oidcclient.IDToken{ IDToken: &oidctypes.IDToken{
Token: "test-id-token", Token: "test-id-token",
Expiry: metav1.NewTime(now.Add(-1 * time.Minute)), Expiry: metav1.NewTime(now.Add(-1 * time.Minute)),
}, },
@ -159,8 +160,8 @@ func TestNormalized(t *testing.T) {
// Access token is empty, but not nil. // Access token is empty, but not nil.
{ {
LastUsedTimestamp: metav1.NewTime(now), LastUsedTimestamp: metav1.NewTime(now),
Tokens: oidcclient.Token{ Tokens: oidctypes.Token{
AccessToken: &oidcclient.AccessToken{ AccessToken: &oidctypes.AccessToken{
Token: "", Token: "",
Expiry: metav1.NewTime(now.Add(1 * time.Minute)), Expiry: metav1.NewTime(now.Add(1 * time.Minute)),
}, },
@ -169,8 +170,8 @@ func TestNormalized(t *testing.T) {
// Access token is expired. // Access token is expired.
{ {
LastUsedTimestamp: metav1.NewTime(now), LastUsedTimestamp: metav1.NewTime(now),
Tokens: oidcclient.Token{ Tokens: oidctypes.Token{
AccessToken: &oidcclient.AccessToken{ AccessToken: &oidctypes.AccessToken{
Token: "test-access-token", Token: "test-access-token",
Expiry: metav1.NewTime(now.Add(-1 * time.Minute)), Expiry: metav1.NewTime(now.Add(-1 * time.Minute)),
}, },
@ -179,8 +180,8 @@ func TestNormalized(t *testing.T) {
// Refresh token is empty, but not nil. // Refresh token is empty, but not nil.
{ {
LastUsedTimestamp: metav1.NewTime(now), LastUsedTimestamp: metav1.NewTime(now),
Tokens: oidcclient.Token{ Tokens: oidctypes.Token{
RefreshToken: &oidcclient.RefreshToken{ RefreshToken: &oidctypes.RefreshToken{
Token: "", Token: "",
}, },
}, },
@ -188,8 +189,8 @@ func TestNormalized(t *testing.T) {
// Session has a refresh token but it hasn't been used in >90 days. // Session has a refresh token but it hasn't been used in >90 days.
{ {
LastUsedTimestamp: metav1.NewTime(now.AddDate(-1, 0, 0)), LastUsedTimestamp: metav1.NewTime(now.AddDate(-1, 0, 0)),
Tokens: oidcclient.Token{ Tokens: oidctypes.Token{
RefreshToken: &oidcclient.RefreshToken{ RefreshToken: &oidctypes.RefreshToken{
Token: "test-refresh-token", Token: "test-refresh-token",
}, },
}, },
@ -198,8 +199,8 @@ func TestNormalized(t *testing.T) {
{ {
CreationTimestamp: metav1.NewTime(now.Add(-1 * time.Hour)), CreationTimestamp: metav1.NewTime(now.Add(-1 * time.Hour)),
LastUsedTimestamp: metav1.NewTime(now), LastUsedTimestamp: metav1.NewTime(now),
Tokens: oidcclient.Token{ Tokens: oidctypes.Token{
RefreshToken: &oidcclient.RefreshToken{ RefreshToken: &oidctypes.RefreshToken{
Token: "test-refresh-token2", Token: "test-refresh-token2",
}, },
}, },
@ -207,8 +208,8 @@ func TestNormalized(t *testing.T) {
{ {
CreationTimestamp: metav1.NewTime(now.Add(-2 * time.Hour)), CreationTimestamp: metav1.NewTime(now.Add(-2 * time.Hour)),
LastUsedTimestamp: metav1.NewTime(now), LastUsedTimestamp: metav1.NewTime(now),
Tokens: oidcclient.Token{ Tokens: oidctypes.Token{
RefreshToken: &oidcclient.RefreshToken{ RefreshToken: &oidctypes.RefreshToken{
Token: "test-refresh-token1", Token: "test-refresh-token1",
}, },
}, },
@ -222,8 +223,8 @@ func TestNormalized(t *testing.T) {
{ {
CreationTimestamp: metav1.NewTime(now.Add(-2 * time.Hour)), CreationTimestamp: metav1.NewTime(now.Add(-2 * time.Hour)),
LastUsedTimestamp: metav1.NewTime(now), LastUsedTimestamp: metav1.NewTime(now),
Tokens: oidcclient.Token{ Tokens: oidctypes.Token{
RefreshToken: &oidcclient.RefreshToken{ RefreshToken: &oidctypes.RefreshToken{
Token: "test-refresh-token1", Token: "test-refresh-token1",
}, },
}, },
@ -231,8 +232,8 @@ func TestNormalized(t *testing.T) {
{ {
CreationTimestamp: metav1.NewTime(now.Add(-1 * time.Hour)), CreationTimestamp: metav1.NewTime(now.Add(-1 * time.Hour)),
LastUsedTimestamp: metav1.NewTime(now), LastUsedTimestamp: metav1.NewTime(now),
Tokens: oidcclient.Token{ Tokens: oidctypes.Token{
RefreshToken: &oidcclient.RefreshToken{ RefreshToken: &oidctypes.RefreshToken{
Token: "test-refresh-token2", Token: "test-refresh-token2",
}, },
}, },

View File

@ -16,6 +16,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"go.pinniped.dev/pkg/oidcclient" "go.pinniped.dev/pkg/oidcclient"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
) )
const ( const (
@ -65,14 +66,14 @@ type Cache struct {
} }
// GetToken looks up the cached data for the given parameters. It may return nil if no valid matching session is cached. // GetToken looks up the cached data for the given parameters. It may return nil if no valid matching session is cached.
func (c *Cache) GetToken(key oidcclient.SessionCacheKey) *oidcclient.Token { func (c *Cache) GetToken(key oidcclient.SessionCacheKey) *oidctypes.Token {
// If the cache file does not exist, exit immediately with no error log // If the cache file does not exist, exit immediately with no error log
if _, err := os.Stat(c.path); errors.Is(err, os.ErrNotExist) { if _, err := os.Stat(c.path); errors.Is(err, os.ErrNotExist) {
return nil return nil
} }
// Read the cache and lookup the matching entry. If one exists, update its last used timestamp and return it. // Read the cache and lookup the matching entry. If one exists, update its last used timestamp and return it.
var result *oidcclient.Token var result *oidctypes.Token
c.withCache(func(cache *sessionCache) { c.withCache(func(cache *sessionCache) {
if entry := cache.lookup(key); entry != nil { if entry := cache.lookup(key); entry != nil {
result = &entry.Tokens result = &entry.Tokens
@ -84,7 +85,7 @@ func (c *Cache) GetToken(key oidcclient.SessionCacheKey) *oidcclient.Token {
// PutToken stores the provided token into the session cache under the given parameters. It does not return an error // PutToken stores the provided token into the session cache under the given parameters. It does not return an error
// but may silently fail to update the session cache. // but may silently fail to update the session cache.
func (c *Cache) PutToken(key oidcclient.SessionCacheKey, token *oidcclient.Token) { func (c *Cache) PutToken(key oidcclient.SessionCacheKey, token *oidctypes.Token) {
// Create the cache directory if it does not exist. // Create the cache directory if it does not exist.
if err := os.MkdirAll(filepath.Dir(c.path), 0700); err != nil && !errors.Is(err, os.ErrExist) { if err := os.MkdirAll(filepath.Dir(c.path), 0700); err != nil && !errors.Is(err, os.ErrExist) {
c.errReporter(fmt.Errorf("could not create session cache directory: %w", err)) c.errReporter(fmt.Errorf("could not create session cache directory: %w", err))

View File

@ -16,6 +16,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"go.pinniped.dev/pkg/oidcclient" "go.pinniped.dev/pkg/oidcclient"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
) )
func TestNew(t *testing.T) { func TestNew(t *testing.T) {
@ -37,7 +38,7 @@ func TestGetToken(t *testing.T) {
trylockFunc func(*testing.T) error trylockFunc func(*testing.T) error
unlockFunc func(*testing.T) error unlockFunc func(*testing.T) error
key oidcclient.SessionCacheKey key oidcclient.SessionCacheKey
want *oidcclient.Token want *oidctypes.Token
wantErrors []string wantErrors []string
wantTestFile func(t *testing.T, tmp string) wantTestFile func(t *testing.T, tmp string)
}{ }{
@ -98,17 +99,17 @@ func TestGetToken(t *testing.T) {
}, },
CreationTimestamp: metav1.NewTime(now.Add(-2 * time.Hour)), CreationTimestamp: metav1.NewTime(now.Add(-2 * time.Hour)),
LastUsedTimestamp: metav1.NewTime(now.Add(-1 * time.Hour)), LastUsedTimestamp: metav1.NewTime(now.Add(-1 * time.Hour)),
Tokens: oidcclient.Token{ Tokens: oidctypes.Token{
AccessToken: &oidcclient.AccessToken{ AccessToken: &oidctypes.AccessToken{
Token: "test-access-token", Token: "test-access-token",
Type: "Bearer", Type: "Bearer",
Expiry: metav1.NewTime(now.Add(1 * time.Hour)), Expiry: metav1.NewTime(now.Add(1 * time.Hour)),
}, },
IDToken: &oidcclient.IDToken{ IDToken: &oidctypes.IDToken{
Token: "test-id-token", Token: "test-id-token",
Expiry: metav1.NewTime(now.Add(1 * time.Hour)), Expiry: metav1.NewTime(now.Add(1 * time.Hour)),
}, },
RefreshToken: &oidcclient.RefreshToken{ RefreshToken: &oidctypes.RefreshToken{
Token: "test-refresh-token", Token: "test-refresh-token",
}, },
}, },
@ -136,17 +137,17 @@ func TestGetToken(t *testing.T) {
}, },
CreationTimestamp: metav1.NewTime(now.Add(-2 * time.Hour)), CreationTimestamp: metav1.NewTime(now.Add(-2 * time.Hour)),
LastUsedTimestamp: metav1.NewTime(now.Add(-1 * time.Hour)), LastUsedTimestamp: metav1.NewTime(now.Add(-1 * time.Hour)),
Tokens: oidcclient.Token{ Tokens: oidctypes.Token{
AccessToken: &oidcclient.AccessToken{ AccessToken: &oidctypes.AccessToken{
Token: "test-access-token", Token: "test-access-token",
Type: "Bearer", Type: "Bearer",
Expiry: metav1.NewTime(now.Add(1 * time.Hour)), Expiry: metav1.NewTime(now.Add(1 * time.Hour)),
}, },
IDToken: &oidcclient.IDToken{ IDToken: &oidctypes.IDToken{
Token: "test-id-token", Token: "test-id-token",
Expiry: metav1.NewTime(now.Add(1 * time.Hour)), Expiry: metav1.NewTime(now.Add(1 * time.Hour)),
}, },
RefreshToken: &oidcclient.RefreshToken{ RefreshToken: &oidctypes.RefreshToken{
Token: "test-refresh-token", Token: "test-refresh-token",
}, },
}, },
@ -160,17 +161,17 @@ func TestGetToken(t *testing.T) {
RedirectURI: "http://localhost:0/callback", RedirectURI: "http://localhost:0/callback",
}, },
wantErrors: []string{}, wantErrors: []string{},
want: &oidcclient.Token{ want: &oidctypes.Token{
AccessToken: &oidcclient.AccessToken{ AccessToken: &oidctypes.AccessToken{
Token: "test-access-token", Token: "test-access-token",
Type: "Bearer", Type: "Bearer",
Expiry: metav1.NewTime(now.Add(1 * time.Hour).Local()), Expiry: metav1.NewTime(now.Add(1 * time.Hour).Local()),
}, },
IDToken: &oidcclient.IDToken{ IDToken: &oidctypes.IDToken{
Token: "test-id-token", Token: "test-id-token",
Expiry: metav1.NewTime(now.Add(1 * time.Hour).Local()), Expiry: metav1.NewTime(now.Add(1 * time.Hour).Local()),
}, },
RefreshToken: &oidcclient.RefreshToken{ RefreshToken: &oidctypes.RefreshToken{
Token: "test-refresh-token", Token: "test-refresh-token",
}, },
}, },
@ -218,7 +219,7 @@ func TestPutToken(t *testing.T) {
name string name string
makeTestFile func(t *testing.T, tmp string) makeTestFile func(t *testing.T, tmp string)
key oidcclient.SessionCacheKey key oidcclient.SessionCacheKey
token *oidcclient.Token token *oidctypes.Token
wantErrors []string wantErrors []string
wantTestFile func(t *testing.T, tmp string) wantTestFile func(t *testing.T, tmp string)
}{ }{
@ -244,17 +245,17 @@ func TestPutToken(t *testing.T) {
}, },
CreationTimestamp: metav1.NewTime(now.Add(-2 * time.Hour)), CreationTimestamp: metav1.NewTime(now.Add(-2 * time.Hour)),
LastUsedTimestamp: metav1.NewTime(now.Add(-1 * time.Hour)), LastUsedTimestamp: metav1.NewTime(now.Add(-1 * time.Hour)),
Tokens: oidcclient.Token{ Tokens: oidctypes.Token{
AccessToken: &oidcclient.AccessToken{ AccessToken: &oidctypes.AccessToken{
Token: "old-access-token", Token: "old-access-token",
Type: "Bearer", Type: "Bearer",
Expiry: metav1.NewTime(now.Add(1 * time.Hour)), Expiry: metav1.NewTime(now.Add(1 * time.Hour)),
}, },
IDToken: &oidcclient.IDToken{ IDToken: &oidctypes.IDToken{
Token: "old-id-token", Token: "old-id-token",
Expiry: metav1.NewTime(now.Add(1 * time.Hour)), Expiry: metav1.NewTime(now.Add(1 * time.Hour)),
}, },
RefreshToken: &oidcclient.RefreshToken{ RefreshToken: &oidctypes.RefreshToken{
Token: "old-refresh-token", Token: "old-refresh-token",
}, },
}, },
@ -268,17 +269,17 @@ func TestPutToken(t *testing.T) {
Scopes: []string{"email", "offline_access", "openid", "profile"}, Scopes: []string{"email", "offline_access", "openid", "profile"},
RedirectURI: "http://localhost:0/callback", RedirectURI: "http://localhost:0/callback",
}, },
token: &oidcclient.Token{ token: &oidctypes.Token{
AccessToken: &oidcclient.AccessToken{ AccessToken: &oidctypes.AccessToken{
Token: "new-access-token", Token: "new-access-token",
Type: "Bearer", Type: "Bearer",
Expiry: metav1.NewTime(now.Add(2 * time.Hour).Local()), Expiry: metav1.NewTime(now.Add(2 * time.Hour).Local()),
}, },
IDToken: &oidcclient.IDToken{ IDToken: &oidctypes.IDToken{
Token: "new-id-token", Token: "new-id-token",
Expiry: metav1.NewTime(now.Add(2 * time.Hour).Local()), Expiry: metav1.NewTime(now.Add(2 * time.Hour).Local()),
}, },
RefreshToken: &oidcclient.RefreshToken{ RefreshToken: &oidctypes.RefreshToken{
Token: "new-refresh-token", Token: "new-refresh-token",
}, },
}, },
@ -287,17 +288,17 @@ func TestPutToken(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Len(t, cache.Sessions, 1) require.Len(t, cache.Sessions, 1)
require.Less(t, time.Since(cache.Sessions[0].LastUsedTimestamp.Time).Nanoseconds(), (5 * time.Second).Nanoseconds()) require.Less(t, time.Since(cache.Sessions[0].LastUsedTimestamp.Time).Nanoseconds(), (5 * time.Second).Nanoseconds())
require.Equal(t, oidcclient.Token{ require.Equal(t, oidctypes.Token{
AccessToken: &oidcclient.AccessToken{ AccessToken: &oidctypes.AccessToken{
Token: "new-access-token", Token: "new-access-token",
Type: "Bearer", Type: "Bearer",
Expiry: metav1.NewTime(now.Add(2 * time.Hour).Local()), Expiry: metav1.NewTime(now.Add(2 * time.Hour).Local()),
}, },
IDToken: &oidcclient.IDToken{ IDToken: &oidctypes.IDToken{
Token: "new-id-token", Token: "new-id-token",
Expiry: metav1.NewTime(now.Add(2 * time.Hour).Local()), Expiry: metav1.NewTime(now.Add(2 * time.Hour).Local()),
}, },
RefreshToken: &oidcclient.RefreshToken{ RefreshToken: &oidctypes.RefreshToken{
Token: "new-refresh-token", Token: "new-refresh-token",
}, },
}, cache.Sessions[0].Tokens) }, cache.Sessions[0].Tokens)
@ -316,17 +317,17 @@ func TestPutToken(t *testing.T) {
}, },
CreationTimestamp: metav1.NewTime(now.Add(-2 * time.Hour)), CreationTimestamp: metav1.NewTime(now.Add(-2 * time.Hour)),
LastUsedTimestamp: metav1.NewTime(now.Add(-1 * time.Hour)), LastUsedTimestamp: metav1.NewTime(now.Add(-1 * time.Hour)),
Tokens: oidcclient.Token{ Tokens: oidctypes.Token{
AccessToken: &oidcclient.AccessToken{ AccessToken: &oidctypes.AccessToken{
Token: "old-access-token", Token: "old-access-token",
Type: "Bearer", Type: "Bearer",
Expiry: metav1.NewTime(now.Add(1 * time.Hour)), Expiry: metav1.NewTime(now.Add(1 * time.Hour)),
}, },
IDToken: &oidcclient.IDToken{ IDToken: &oidctypes.IDToken{
Token: "old-id-token", Token: "old-id-token",
Expiry: metav1.NewTime(now.Add(1 * time.Hour)), Expiry: metav1.NewTime(now.Add(1 * time.Hour)),
}, },
RefreshToken: &oidcclient.RefreshToken{ RefreshToken: &oidctypes.RefreshToken{
Token: "old-refresh-token", Token: "old-refresh-token",
}, },
}, },
@ -340,17 +341,17 @@ func TestPutToken(t *testing.T) {
Scopes: []string{"email", "offline_access", "openid", "profile"}, Scopes: []string{"email", "offline_access", "openid", "profile"},
RedirectURI: "http://localhost:0/callback", RedirectURI: "http://localhost:0/callback",
}, },
token: &oidcclient.Token{ token: &oidctypes.Token{
AccessToken: &oidcclient.AccessToken{ AccessToken: &oidctypes.AccessToken{
Token: "new-access-token", Token: "new-access-token",
Type: "Bearer", Type: "Bearer",
Expiry: metav1.NewTime(now.Add(2 * time.Hour).Local()), Expiry: metav1.NewTime(now.Add(2 * time.Hour).Local()),
}, },
IDToken: &oidcclient.IDToken{ IDToken: &oidctypes.IDToken{
Token: "new-id-token", Token: "new-id-token",
Expiry: metav1.NewTime(now.Add(2 * time.Hour).Local()), Expiry: metav1.NewTime(now.Add(2 * time.Hour).Local()),
}, },
RefreshToken: &oidcclient.RefreshToken{ RefreshToken: &oidctypes.RefreshToken{
Token: "new-refresh-token", Token: "new-refresh-token",
}, },
}, },
@ -359,17 +360,17 @@ func TestPutToken(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Len(t, cache.Sessions, 2) require.Len(t, cache.Sessions, 2)
require.Less(t, time.Since(cache.Sessions[1].LastUsedTimestamp.Time).Nanoseconds(), (5 * time.Second).Nanoseconds()) require.Less(t, time.Since(cache.Sessions[1].LastUsedTimestamp.Time).Nanoseconds(), (5 * time.Second).Nanoseconds())
require.Equal(t, oidcclient.Token{ require.Equal(t, oidctypes.Token{
AccessToken: &oidcclient.AccessToken{ AccessToken: &oidctypes.AccessToken{
Token: "new-access-token", Token: "new-access-token",
Type: "Bearer", Type: "Bearer",
Expiry: metav1.NewTime(now.Add(2 * time.Hour).Local()), Expiry: metav1.NewTime(now.Add(2 * time.Hour).Local()),
}, },
IDToken: &oidcclient.IDToken{ IDToken: &oidctypes.IDToken{
Token: "new-id-token", Token: "new-id-token",
Expiry: metav1.NewTime(now.Add(2 * time.Hour).Local()), Expiry: metav1.NewTime(now.Add(2 * time.Hour).Local()),
}, },
RefreshToken: &oidcclient.RefreshToken{ RefreshToken: &oidctypes.RefreshToken{
Token: "new-refresh-token", Token: "new-refresh-token",
}, },
}, cache.Sessions[1].Tokens) }, cache.Sessions[1].Tokens)
@ -388,17 +389,17 @@ func TestPutToken(t *testing.T) {
Scopes: []string{"email", "offline_access", "openid", "profile"}, Scopes: []string{"email", "offline_access", "openid", "profile"},
RedirectURI: "http://localhost:0/callback", RedirectURI: "http://localhost:0/callback",
}, },
token: &oidcclient.Token{ token: &oidctypes.Token{
AccessToken: &oidcclient.AccessToken{ AccessToken: &oidctypes.AccessToken{
Token: "new-access-token", Token: "new-access-token",
Type: "Bearer", Type: "Bearer",
Expiry: metav1.NewTime(now.Add(2 * time.Hour).Local()), Expiry: metav1.NewTime(now.Add(2 * time.Hour).Local()),
}, },
IDToken: &oidcclient.IDToken{ IDToken: &oidctypes.IDToken{
Token: "new-id-token", Token: "new-id-token",
Expiry: metav1.NewTime(now.Add(2 * time.Hour).Local()), Expiry: metav1.NewTime(now.Add(2 * time.Hour).Local()),
}, },
RefreshToken: &oidcclient.RefreshToken{ RefreshToken: &oidctypes.RefreshToken{
Token: "new-refresh-token", Token: "new-refresh-token",
}, },
}, },

View File

@ -21,6 +21,7 @@ import (
"go.pinniped.dev/internal/httputil/httperr" "go.pinniped.dev/internal/httputil/httperr"
"go.pinniped.dev/internal/httputil/securityheader" "go.pinniped.dev/internal/httputil/securityheader"
"go.pinniped.dev/pkg/oidcclient/nonce" "go.pinniped.dev/pkg/oidcclient/nonce"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
"go.pinniped.dev/pkg/oidcclient/pkce" "go.pinniped.dev/pkg/oidcclient/pkce"
"go.pinniped.dev/pkg/oidcclient/state" "go.pinniped.dev/pkg/oidcclient/state"
) )
@ -68,7 +69,7 @@ type handlerState struct {
} }
type callbackResult struct { type callbackResult struct {
token *Token token *oidctypes.Token
err error err error
} }
@ -116,6 +117,19 @@ func WithBrowserOpen(openURL func(url string) error) Option {
} }
} }
// SessionCacheKey contains the data used to select a valid session cache entry.
type SessionCacheKey struct {
Issuer string `json:"issuer"`
ClientID string `json:"clientID"`
Scopes []string `json:"scopes"`
RedirectURI string `json:"redirect_uri"`
}
type SessionCache interface {
GetToken(SessionCacheKey) *oidctypes.Token
PutToken(SessionCacheKey, *oidctypes.Token)
}
// WithSessionCache sets the session cache backend for storing and retrieving previously-issued ID tokens and refresh tokens. // WithSessionCache sets the session cache backend for storing and retrieving previously-issued ID tokens and refresh tokens.
func WithSessionCache(cache SessionCache) Option { func WithSessionCache(cache SessionCache) Option {
return func(h *handlerState) error { return func(h *handlerState) error {
@ -135,8 +149,8 @@ func WithClient(httpClient *http.Client) Option {
// nopCache is a SessionCache that doesn't actually do anything. // nopCache is a SessionCache that doesn't actually do anything.
type nopCache struct{} type nopCache struct{}
func (*nopCache) GetToken(SessionCacheKey) *Token { return nil } func (*nopCache) GetToken(SessionCacheKey) *oidctypes.Token { return nil }
func (*nopCache) PutToken(SessionCacheKey, *Token) {} func (*nopCache) PutToken(SessionCacheKey, *oidctypes.Token) {}
type discoveryI interface { type discoveryI interface {
Endpoint() oauth2.Endpoint Endpoint() oauth2.Endpoint
@ -144,7 +158,7 @@ type discoveryI interface {
} }
// Login performs an OAuth2/OIDC authorization code login using a localhost listener. // Login performs an OAuth2/OIDC authorization code login using a localhost listener.
func Login(issuer string, clientID string, opts ...Option) (*Token, error) { func Login(issuer string, clientID string, opts ...Option) (*oidctypes.Token, error) {
h := handlerState{ h := handlerState{
issuer: issuer, issuer: issuer,
clientID: clientID, clientID: clientID,
@ -274,7 +288,7 @@ func Login(issuer string, clientID string, opts ...Option) (*Token, error) {
} }
} }
func (h *handlerState) handleRefresh(ctx context.Context, refreshToken *RefreshToken) (*Token, error) { func (h *handlerState) handleRefresh(ctx context.Context, refreshToken *oidctypes.RefreshToken) (*oidctypes.Token, error) {
ctx, cancel := context.WithTimeout(ctx, refreshTimeout) ctx, cancel := context.WithTimeout(ctx, refreshTimeout)
defer cancel() defer cancel()
refreshSource := h.oauth2Config.TokenSource(ctx, &oauth2.Token{RefreshToken: refreshToken.Token}) refreshSource := h.oauth2Config.TokenSource(ctx, &oauth2.Token{RefreshToken: refreshToken.Token})
@ -331,7 +345,7 @@ func (h *handlerState) handleAuthCodeCallback(w http.ResponseWriter, r *http.Req
return nil return nil
} }
func (h *handlerState) validateToken(ctx context.Context, tok *oauth2.Token, checkNonce bool) (*Token, error) { func (h *handlerState) validateToken(ctx context.Context, tok *oauth2.Token, checkNonce bool) (*oidctypes.Token, error) {
idTok, hasIDTok := tok.Extra("id_token").(string) idTok, hasIDTok := tok.Extra("id_token").(string)
if !hasIDTok { if !hasIDTok {
return nil, httperr.New(http.StatusBadRequest, "received response missing ID token") return nil, httperr.New(http.StatusBadRequest, "received response missing ID token")
@ -350,16 +364,16 @@ func (h *handlerState) validateToken(ctx context.Context, tok *oauth2.Token, che
return nil, httperr.Wrap(http.StatusBadRequest, "received ID token with invalid nonce", err) return nil, httperr.Wrap(http.StatusBadRequest, "received ID token with invalid nonce", err)
} }
} }
return &Token{ return &oidctypes.Token{
AccessToken: &AccessToken{ AccessToken: &oidctypes.AccessToken{
Token: tok.AccessToken, Token: tok.AccessToken,
Type: tok.TokenType, Type: tok.TokenType,
Expiry: metav1.NewTime(tok.Expiry), Expiry: metav1.NewTime(tok.Expiry),
}, },
RefreshToken: &RefreshToken{ RefreshToken: &oidctypes.RefreshToken{
Token: tok.RefreshToken, Token: tok.RefreshToken,
}, },
IDToken: &IDToken{ IDToken: &oidctypes.IDToken{
Token: idTok, Token: idTok,
Expiry: metav1.NewTime(validated.Expiry), Expiry: metav1.NewTime(validated.Expiry),
}, },

View File

@ -25,6 +25,7 @@ import (
"go.pinniped.dev/internal/mocks/mockkeyset" "go.pinniped.dev/internal/mocks/mockkeyset"
"go.pinniped.dev/internal/testutil" "go.pinniped.dev/internal/testutil"
"go.pinniped.dev/pkg/oidcclient/nonce" "go.pinniped.dev/pkg/oidcclient/nonce"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
"go.pinniped.dev/pkg/oidcclient/pkce" "go.pinniped.dev/pkg/oidcclient/pkce"
"go.pinniped.dev/pkg/oidcclient/state" "go.pinniped.dev/pkg/oidcclient/state"
) )
@ -32,19 +33,19 @@ import (
// mockSessionCache exists to avoid an import cycle if we generate mocks into another package. // mockSessionCache exists to avoid an import cycle if we generate mocks into another package.
type mockSessionCache struct { type mockSessionCache struct {
t *testing.T t *testing.T
getReturnsToken *Token getReturnsToken *oidctypes.Token
sawGetKeys []SessionCacheKey sawGetKeys []SessionCacheKey
sawPutKeys []SessionCacheKey sawPutKeys []SessionCacheKey
sawPutTokens []*Token sawPutTokens []*oidctypes.Token
} }
func (m *mockSessionCache) GetToken(key SessionCacheKey) *Token { func (m *mockSessionCache) GetToken(key SessionCacheKey) *oidctypes.Token {
m.t.Logf("saw mock session cache GetToken() with client ID %s", key.ClientID) m.t.Logf("saw mock session cache GetToken() with client ID %s", key.ClientID)
m.sawGetKeys = append(m.sawGetKeys, key) m.sawGetKeys = append(m.sawGetKeys, key)
return m.getReturnsToken return m.getReturnsToken
} }
func (m *mockSessionCache) PutToken(key SessionCacheKey, token *Token) { func (m *mockSessionCache) PutToken(key SessionCacheKey, token *oidctypes.Token) {
m.t.Logf("saw mock session cache PutToken() with client ID %s and ID token %s", key.ClientID, token.IDToken.Token) m.t.Logf("saw mock session cache PutToken() with client ID %s and ID token %s", key.ClientID, token.IDToken.Token)
m.sawPutKeys = append(m.sawPutKeys, key) m.sawPutKeys = append(m.sawPutKeys, key)
m.sawPutTokens = append(m.sawPutTokens, token) m.sawPutTokens = append(m.sawPutTokens, token)
@ -55,15 +56,15 @@ func TestLogin(t *testing.T) {
time1Unix := int64(2075807775) time1Unix := int64(2075807775)
require.Equal(t, time1Unix, time1.Add(2*time.Minute).Unix()) require.Equal(t, time1Unix, time1.Add(2*time.Minute).Unix())
testToken := Token{ testToken := oidctypes.Token{
AccessToken: &AccessToken{ AccessToken: &oidctypes.AccessToken{
Token: "test-access-token", Token: "test-access-token",
Expiry: metav1.NewTime(time1.Add(1 * time.Minute)), Expiry: metav1.NewTime(time1.Add(1 * time.Minute)),
}, },
RefreshToken: &RefreshToken{ RefreshToken: &oidctypes.RefreshToken{
Token: "test-refresh-token", Token: "test-refresh-token",
}, },
IDToken: &IDToken{ IDToken: &oidctypes.IDToken{
// Test JWT generated with https://smallstep.com/docs/cli/crypto/jwt/ (using time1Unix from above): // Test JWT generated with https://smallstep.com/docs/cli/crypto/jwt/ (using time1Unix from above):
// step crypto keypair key.pub key.priv --kty RSA --no-password --insecure --force && echo '{}' | step crypto jwt sign --key key.priv --aud test-client-id --sub test-user --subtle --kid="test-kid" --jti="test-jti" --exp 2075807775 // step crypto keypair key.pub key.priv --kty RSA --no-password --insecure --force && echo '{}' | step crypto jwt sign --key key.priv --aud test-client-id --sub test-user --subtle --kid="test-kid" --jti="test-jti" --exp 2075807775
Token: "eyJhbGciOiJSUzI1NiIsImtpZCI6InRlc3Qta2lkIiwidHlwIjoiSldUIn0.eyJhdWQiOiJ0ZXN0LWNsaWVudC1pZCIsImV4cCI6MjA3NTgwNzc3NSwiaWF0IjoxNjAzMzk5NTY4LCJpc3MiOiJ0ZXN0LWlzc3VlciIsImp0aSI6InRlc3QtanRpIiwibmJmIjoxNjAzMzk5NTY4LCJzdWIiOiJ0ZXN0LXVzZXIifQ.CdwUWQb6xELeFlC4u84K4rzks7YiDJiXxIo_SaRvCHBijxtil812RBRfPuAyYKJlGwFx1g-JYvkUg69X5NmvmLXkaOdHIKUAT7Nqa7yqd1xOAP9IlFj9qZM3Q7s8gWWW9da-_ryagzN4fyGfNfYeGhzIriSMaVpuBGz1eg6f-6VuuulnoiOpl8A0l50u0MdRjjsxRHuiR2loIhUxoIQQ9xN8w53UiP0R1uz8_uV0_K93RSq37aPjsnCXRLwUUb3azkRVe6B9EUW1ihthQ-KfRaU1iq2rY1m5UqNzf0NqDXCrN5SF-GVxOhKXJTsN4-PABfJBjqxg6dGUGeIa2JhFcA", Token: "eyJhbGciOiJSUzI1NiIsImtpZCI6InRlc3Qta2lkIiwidHlwIjoiSldUIn0.eyJhdWQiOiJ0ZXN0LWNsaWVudC1pZCIsImV4cCI6MjA3NTgwNzc3NSwiaWF0IjoxNjAzMzk5NTY4LCJpc3MiOiJ0ZXN0LWlzc3VlciIsImp0aSI6InRlc3QtanRpIiwibmJmIjoxNjAzMzk5NTY4LCJzdWIiOiJ0ZXN0LXVzZXIifQ.CdwUWQb6xELeFlC4u84K4rzks7YiDJiXxIo_SaRvCHBijxtil812RBRfPuAyYKJlGwFx1g-JYvkUg69X5NmvmLXkaOdHIKUAT7Nqa7yqd1xOAP9IlFj9qZM3Q7s8gWWW9da-_ryagzN4fyGfNfYeGhzIriSMaVpuBGz1eg6f-6VuuulnoiOpl8A0l50u0MdRjjsxRHuiR2loIhUxoIQQ9xN8w53UiP0R1uz8_uV0_K93RSq37aPjsnCXRLwUUb3azkRVe6B9EUW1ihthQ-KfRaU1iq2rY1m5UqNzf0NqDXCrN5SF-GVxOhKXJTsN4-PABfJBjqxg6dGUGeIa2JhFcA",
@ -145,7 +146,7 @@ func TestLogin(t *testing.T) {
issuer string issuer string
clientID string clientID string
wantErr string wantErr string
wantToken *Token wantToken *oidctypes.Token
}{ }{
{ {
name: "option error", name: "option error",
@ -192,8 +193,8 @@ func TestLogin(t *testing.T) {
clientID: "test-client-id", clientID: "test-client-id",
opt: func(t *testing.T) Option { opt: func(t *testing.T) Option {
return func(h *handlerState) error { return func(h *handlerState) error {
cache := &mockSessionCache{t: t, getReturnsToken: &Token{ cache := &mockSessionCache{t: t, getReturnsToken: &oidctypes.Token{
IDToken: &IDToken{ IDToken: &oidctypes.IDToken{
Token: "test-id-token", Token: "test-id-token",
Expiry: metav1.NewTime(time.Now()), // less than Now() + minIDTokenValidity Expiry: metav1.NewTime(time.Now()), // less than Now() + minIDTokenValidity
}, },
@ -247,12 +248,12 @@ func TestLogin(t *testing.T) {
clientID: "test-client-id", clientID: "test-client-id",
opt: func(t *testing.T) Option { opt: func(t *testing.T) Option {
return func(h *handlerState) error { return func(h *handlerState) error {
cache := &mockSessionCache{t: t, getReturnsToken: &Token{ cache := &mockSessionCache{t: t, getReturnsToken: &oidctypes.Token{
IDToken: &IDToken{ IDToken: &oidctypes.IDToken{
Token: "expired-test-id-token", Token: "expired-test-id-token",
Expiry: metav1.Now(), // less than Now() + minIDTokenValidity Expiry: metav1.Now(), // less than Now() + minIDTokenValidity
}, },
RefreshToken: &RefreshToken{Token: "test-refresh-token"}, RefreshToken: &oidctypes.RefreshToken{Token: "test-refresh-token"},
}} }}
t.Cleanup(func() { t.Cleanup(func() {
cacheKey := SessionCacheKey{ cacheKey := SessionCacheKey{
@ -284,12 +285,12 @@ func TestLogin(t *testing.T) {
clientID: "test-client-id", clientID: "test-client-id",
opt: func(t *testing.T) Option { opt: func(t *testing.T) Option {
return func(h *handlerState) error { return func(h *handlerState) error {
cache := &mockSessionCache{t: t, getReturnsToken: &Token{ cache := &mockSessionCache{t: t, getReturnsToken: &oidctypes.Token{
IDToken: &IDToken{ IDToken: &oidctypes.IDToken{
Token: "expired-test-id-token", Token: "expired-test-id-token",
Expiry: metav1.Now(), // less than Now() + minIDTokenValidity Expiry: metav1.Now(), // less than Now() + minIDTokenValidity
}, },
RefreshToken: &RefreshToken{Token: "test-refresh-token-returning-invalid-id-token"}, RefreshToken: &oidctypes.RefreshToken{Token: "test-refresh-token-returning-invalid-id-token"},
}} }}
t.Cleanup(func() { t.Cleanup(func() {
require.Empty(t, cache.sawPutKeys) require.Empty(t, cache.sawPutKeys)
@ -314,12 +315,12 @@ func TestLogin(t *testing.T) {
clientID: "not-the-test-client-id", clientID: "not-the-test-client-id",
opt: func(t *testing.T) Option { opt: func(t *testing.T) Option {
return func(h *handlerState) error { return func(h *handlerState) error {
cache := &mockSessionCache{t: t, getReturnsToken: &Token{ cache := &mockSessionCache{t: t, getReturnsToken: &oidctypes.Token{
IDToken: &IDToken{ IDToken: &oidctypes.IDToken{
Token: "expired-test-id-token", Token: "expired-test-id-token",
Expiry: metav1.Now(), // less than Now() + minIDTokenValidity Expiry: metav1.Now(), // less than Now() + minIDTokenValidity
}, },
RefreshToken: &RefreshToken{Token: "test-refresh-token"}, RefreshToken: &oidctypes.RefreshToken{Token: "test-refresh-token"},
}} }}
t.Cleanup(func() { t.Cleanup(func() {
require.Empty(t, cache.sawPutKeys) require.Empty(t, cache.sawPutKeys)
@ -414,7 +415,7 @@ func TestLogin(t *testing.T) {
t.Cleanup(func() { t.Cleanup(func() {
require.Equal(t, []SessionCacheKey{cacheKey}, cache.sawGetKeys) require.Equal(t, []SessionCacheKey{cacheKey}, cache.sawGetKeys)
require.Equal(t, []SessionCacheKey{cacheKey}, cache.sawPutKeys) require.Equal(t, []SessionCacheKey{cacheKey}, cache.sawPutKeys)
require.Equal(t, []*Token{&testToken}, cache.sawPutTokens) require.Equal(t, []*oidctypes.Token{&testToken}, cache.sawPutTokens)
}) })
require.NoError(t, WithSessionCache(cache)(h)) require.NoError(t, WithSessionCache(cache)(h))
require.NoError(t, WithClient(&http.Client{Timeout: 10 * time.Second})(h)) require.NoError(t, WithClient(&http.Client{Timeout: 10 * time.Second})(h))

View File

@ -1,11 +1,10 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved. // Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package oidcclient // Package oidctypes provides core data types for OIDC token structures.
package oidctypes
import ( import v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// AccessToken is an OAuth2 access token. // AccessToken is an OAuth2 access token.
type AccessToken struct { type AccessToken struct {
@ -16,7 +15,7 @@ type AccessToken struct {
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
// Expiry is the optional expiration time of the access token. // Expiry is the optional expiration time of the access token.
Expiry metav1.Time `json:"expiryTimestamp,omitempty"` Expiry v1.Time `json:"expiryTimestamp,omitempty"`
} }
// RefreshToken is an OAuth2 refresh token. // RefreshToken is an OAuth2 refresh token.
@ -31,7 +30,7 @@ type IDToken struct {
Token string `json:"token"` Token string `json:"token"`
// Expiry is the optional expiration time of the ID token. // Expiry is the optional expiration time of the ID token.
Expiry metav1.Time `json:"expiryTimestamp,omitempty"` Expiry v1.Time `json:"expiryTimestamp,omitempty"`
} }
// Token contains the elements of an OIDC session. // Token contains the elements of an OIDC session.
@ -47,16 +46,3 @@ type Token struct {
// IDToken is an OpenID Connect ID token. // IDToken is an OpenID Connect ID token.
IDToken *IDToken `json:"id,omitempty"` IDToken *IDToken `json:"id,omitempty"`
} }
// SessionCacheKey contains the data used to select a valid session cache entry.
type SessionCacheKey struct {
Issuer string `json:"issuer"`
ClientID string `json:"clientID"`
Scopes []string `json:"scopes"`
RedirectURI string `json:"redirect_uri"`
}
type SessionCache interface {
GetToken(SessionCacheKey) *Token
PutToken(SessionCacheKey, *Token)
}