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/filesession"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
)
//nolint: gochecknoinits
@ -27,7 +28,7 @@ func init() {
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 (
cmd = cobra.Command{
Args: cobra.NoArgs,

View File

@ -13,6 +13,7 @@ import (
"go.pinniped.dev/internal/here"
"go.pinniped.dev/pkg/oidcclient"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
)
func TestLoginOIDCCommand(t *testing.T) {
@ -92,12 +93,12 @@ func TestLoginOIDCCommand(t *testing.T) {
gotClientID string
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
gotClientID = clientID
gotOptions = opts
return &oidcclient.Token{
IDToken: &oidcclient.IDToken{
return &oidctypes.Token{
IDToken: &oidctypes.IDToken{
Token: "test-id-token",
Expiry: metav1.NewTime(time1),
},

View File

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

View File

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

View File

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

View File

@ -15,8 +15,8 @@ import (
"go.pinniped.dev/internal/httputil/httperr"
"go.pinniped.dev/internal/oidc/provider"
"go.pinniped.dev/pkg/oidcclient"
"go.pinniped.dev/pkg/oidcclient/nonce"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
"go.pinniped.dev/pkg/oidcclient/pkce"
)
@ -59,46 +59,46 @@ func (p *ProviderConfig) GetGroupsClaim() string {
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())
if err != nil {
return oidcclient.Token{}, nil, err
return oidctypes.Token{}, nil, err
}
idTok, hasIDTok := tok.Extra("id_token").(string)
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)
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 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 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{}
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{
AccessToken: &oidcclient.AccessToken{
return oidctypes.Token{
AccessToken: &oidctypes.AccessToken{
Token: tok.AccessToken,
Type: tok.TokenType,
Expiry: metav1.NewTime(tok.Expiry),
},
RefreshToken: &oidcclient.RefreshToken{
RefreshToken: &oidctypes.RefreshToken{
Token: tok.RefreshToken,
},
IDToken: &oidcclient.IDToken{
IDToken: &oidctypes.IDToken{
Token: idTok,
Expiry: metav1.NewTime(validated.Expiry),
},

View File

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

View File

@ -17,6 +17,7 @@ import (
"sigs.k8s.io/yaml"
"go.pinniped.dev/pkg/oidcclient"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
)
var (
@ -48,7 +49,7 @@ type (
Key oidcclient.SessionCacheKey `json:"key"`
CreationTimestamp metav1.Time `json:"creationTimestamp"`
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"
"go.pinniped.dev/pkg/oidcclient"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
)
// 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()),
LastUsedTimestamp: metav1.NewTime(time.Date(2020, 10, 20, 18, 45, 31, 0, time.UTC).Local()),
Tokens: oidcclient.Token{
AccessToken: &oidcclient.AccessToken{
Tokens: oidctypes.Token{
AccessToken: &oidctypes.AccessToken{
Token: "test-access-token",
Type: "Bearer",
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",
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",
},
},
@ -139,8 +140,8 @@ func TestNormalized(t *testing.T) {
// ID token is empty, but not nil.
{
LastUsedTimestamp: metav1.NewTime(now),
Tokens: oidcclient.Token{
IDToken: &oidcclient.IDToken{
Tokens: oidctypes.Token{
IDToken: &oidctypes.IDToken{
Token: "",
Expiry: metav1.NewTime(now.Add(1 * time.Minute)),
},
@ -149,8 +150,8 @@ func TestNormalized(t *testing.T) {
// ID token is expired.
{
LastUsedTimestamp: metav1.NewTime(now),
Tokens: oidcclient.Token{
IDToken: &oidcclient.IDToken{
Tokens: oidctypes.Token{
IDToken: &oidctypes.IDToken{
Token: "test-id-token",
Expiry: metav1.NewTime(now.Add(-1 * time.Minute)),
},
@ -159,8 +160,8 @@ func TestNormalized(t *testing.T) {
// Access token is empty, but not nil.
{
LastUsedTimestamp: metav1.NewTime(now),
Tokens: oidcclient.Token{
AccessToken: &oidcclient.AccessToken{
Tokens: oidctypes.Token{
AccessToken: &oidctypes.AccessToken{
Token: "",
Expiry: metav1.NewTime(now.Add(1 * time.Minute)),
},
@ -169,8 +170,8 @@ func TestNormalized(t *testing.T) {
// Access token is expired.
{
LastUsedTimestamp: metav1.NewTime(now),
Tokens: oidcclient.Token{
AccessToken: &oidcclient.AccessToken{
Tokens: oidctypes.Token{
AccessToken: &oidctypes.AccessToken{
Token: "test-access-token",
Expiry: metav1.NewTime(now.Add(-1 * time.Minute)),
},
@ -179,8 +180,8 @@ func TestNormalized(t *testing.T) {
// Refresh token is empty, but not nil.
{
LastUsedTimestamp: metav1.NewTime(now),
Tokens: oidcclient.Token{
RefreshToken: &oidcclient.RefreshToken{
Tokens: oidctypes.Token{
RefreshToken: &oidctypes.RefreshToken{
Token: "",
},
},
@ -188,8 +189,8 @@ func TestNormalized(t *testing.T) {
// Session has a refresh token but it hasn't been used in >90 days.
{
LastUsedTimestamp: metav1.NewTime(now.AddDate(-1, 0, 0)),
Tokens: oidcclient.Token{
RefreshToken: &oidcclient.RefreshToken{
Tokens: oidctypes.Token{
RefreshToken: &oidctypes.RefreshToken{
Token: "test-refresh-token",
},
},
@ -198,8 +199,8 @@ func TestNormalized(t *testing.T) {
{
CreationTimestamp: metav1.NewTime(now.Add(-1 * time.Hour)),
LastUsedTimestamp: metav1.NewTime(now),
Tokens: oidcclient.Token{
RefreshToken: &oidcclient.RefreshToken{
Tokens: oidctypes.Token{
RefreshToken: &oidctypes.RefreshToken{
Token: "test-refresh-token2",
},
},
@ -207,8 +208,8 @@ func TestNormalized(t *testing.T) {
{
CreationTimestamp: metav1.NewTime(now.Add(-2 * time.Hour)),
LastUsedTimestamp: metav1.NewTime(now),
Tokens: oidcclient.Token{
RefreshToken: &oidcclient.RefreshToken{
Tokens: oidctypes.Token{
RefreshToken: &oidctypes.RefreshToken{
Token: "test-refresh-token1",
},
},
@ -222,8 +223,8 @@ func TestNormalized(t *testing.T) {
{
CreationTimestamp: metav1.NewTime(now.Add(-2 * time.Hour)),
LastUsedTimestamp: metav1.NewTime(now),
Tokens: oidcclient.Token{
RefreshToken: &oidcclient.RefreshToken{
Tokens: oidctypes.Token{
RefreshToken: &oidctypes.RefreshToken{
Token: "test-refresh-token1",
},
},
@ -231,8 +232,8 @@ func TestNormalized(t *testing.T) {
{
CreationTimestamp: metav1.NewTime(now.Add(-1 * time.Hour)),
LastUsedTimestamp: metav1.NewTime(now),
Tokens: oidcclient.Token{
RefreshToken: &oidcclient.RefreshToken{
Tokens: oidctypes.Token{
RefreshToken: &oidctypes.RefreshToken{
Token: "test-refresh-token2",
},
},

View File

@ -16,6 +16,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"go.pinniped.dev/pkg/oidcclient"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
)
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.
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 _, err := os.Stat(c.path); errors.Is(err, os.ErrNotExist) {
return nil
}
// 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) {
if entry := cache.lookup(key); entry != nil {
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
// 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.
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))

View File

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

View File

@ -21,6 +21,7 @@ import (
"go.pinniped.dev/internal/httputil/httperr"
"go.pinniped.dev/internal/httputil/securityheader"
"go.pinniped.dev/pkg/oidcclient/nonce"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
"go.pinniped.dev/pkg/oidcclient/pkce"
"go.pinniped.dev/pkg/oidcclient/state"
)
@ -68,7 +69,7 @@ type handlerState struct {
}
type callbackResult struct {
token *Token
token *oidctypes.Token
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.
func WithSessionCache(cache SessionCache) Option {
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.
type nopCache struct{}
func (*nopCache) GetToken(SessionCacheKey) *Token { return nil }
func (*nopCache) PutToken(SessionCacheKey, *Token) {}
func (*nopCache) GetToken(SessionCacheKey) *oidctypes.Token { return nil }
func (*nopCache) PutToken(SessionCacheKey, *oidctypes.Token) {}
type discoveryI interface {
Endpoint() oauth2.Endpoint
@ -144,7 +158,7 @@ type discoveryI interface {
}
// 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{
issuer: issuer,
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)
defer cancel()
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
}
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)
if !hasIDTok {
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 &Token{
AccessToken: &AccessToken{
return &oidctypes.Token{
AccessToken: &oidctypes.AccessToken{
Token: tok.AccessToken,
Type: tok.TokenType,
Expiry: metav1.NewTime(tok.Expiry),
},
RefreshToken: &RefreshToken{
RefreshToken: &oidctypes.RefreshToken{
Token: tok.RefreshToken,
},
IDToken: &IDToken{
IDToken: &oidctypes.IDToken{
Token: idTok,
Expiry: metav1.NewTime(validated.Expiry),
},

View File

@ -25,6 +25,7 @@ import (
"go.pinniped.dev/internal/mocks/mockkeyset"
"go.pinniped.dev/internal/testutil"
"go.pinniped.dev/pkg/oidcclient/nonce"
"go.pinniped.dev/pkg/oidcclient/oidctypes"
"go.pinniped.dev/pkg/oidcclient/pkce"
"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.
type mockSessionCache struct {
t *testing.T
getReturnsToken *Token
getReturnsToken *oidctypes.Token
sawGetKeys []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.sawGetKeys = append(m.sawGetKeys, key)
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.sawPutKeys = append(m.sawPutKeys, key)
m.sawPutTokens = append(m.sawPutTokens, token)
@ -55,15 +56,15 @@ func TestLogin(t *testing.T) {
time1Unix := int64(2075807775)
require.Equal(t, time1Unix, time1.Add(2*time.Minute).Unix())
testToken := Token{
AccessToken: &AccessToken{
testToken := oidctypes.Token{
AccessToken: &oidctypes.AccessToken{
Token: "test-access-token",
Expiry: metav1.NewTime(time1.Add(1 * time.Minute)),
},
RefreshToken: &RefreshToken{
RefreshToken: &oidctypes.RefreshToken{
Token: "test-refresh-token",
},
IDToken: &IDToken{
IDToken: &oidctypes.IDToken{
// 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
Token: "eyJhbGciOiJSUzI1NiIsImtpZCI6InRlc3Qta2lkIiwidHlwIjoiSldUIn0.eyJhdWQiOiJ0ZXN0LWNsaWVudC1pZCIsImV4cCI6MjA3NTgwNzc3NSwiaWF0IjoxNjAzMzk5NTY4LCJpc3MiOiJ0ZXN0LWlzc3VlciIsImp0aSI6InRlc3QtanRpIiwibmJmIjoxNjAzMzk5NTY4LCJzdWIiOiJ0ZXN0LXVzZXIifQ.CdwUWQb6xELeFlC4u84K4rzks7YiDJiXxIo_SaRvCHBijxtil812RBRfPuAyYKJlGwFx1g-JYvkUg69X5NmvmLXkaOdHIKUAT7Nqa7yqd1xOAP9IlFj9qZM3Q7s8gWWW9da-_ryagzN4fyGfNfYeGhzIriSMaVpuBGz1eg6f-6VuuulnoiOpl8A0l50u0MdRjjsxRHuiR2loIhUxoIQQ9xN8w53UiP0R1uz8_uV0_K93RSq37aPjsnCXRLwUUb3azkRVe6B9EUW1ihthQ-KfRaU1iq2rY1m5UqNzf0NqDXCrN5SF-GVxOhKXJTsN4-PABfJBjqxg6dGUGeIa2JhFcA",
@ -145,7 +146,7 @@ func TestLogin(t *testing.T) {
issuer string
clientID string
wantErr string
wantToken *Token
wantToken *oidctypes.Token
}{
{
name: "option error",
@ -192,8 +193,8 @@ func TestLogin(t *testing.T) {
clientID: "test-client-id",
opt: func(t *testing.T) Option {
return func(h *handlerState) error {
cache := &mockSessionCache{t: t, getReturnsToken: &Token{
IDToken: &IDToken{
cache := &mockSessionCache{t: t, getReturnsToken: &oidctypes.Token{
IDToken: &oidctypes.IDToken{
Token: "test-id-token",
Expiry: metav1.NewTime(time.Now()), // less than Now() + minIDTokenValidity
},
@ -247,12 +248,12 @@ func TestLogin(t *testing.T) {
clientID: "test-client-id",
opt: func(t *testing.T) Option {
return func(h *handlerState) error {
cache := &mockSessionCache{t: t, getReturnsToken: &Token{
IDToken: &IDToken{
cache := &mockSessionCache{t: t, getReturnsToken: &oidctypes.Token{
IDToken: &oidctypes.IDToken{
Token: "expired-test-id-token",
Expiry: metav1.Now(), // less than Now() + minIDTokenValidity
},
RefreshToken: &RefreshToken{Token: "test-refresh-token"},
RefreshToken: &oidctypes.RefreshToken{Token: "test-refresh-token"},
}}
t.Cleanup(func() {
cacheKey := SessionCacheKey{
@ -284,12 +285,12 @@ func TestLogin(t *testing.T) {
clientID: "test-client-id",
opt: func(t *testing.T) Option {
return func(h *handlerState) error {
cache := &mockSessionCache{t: t, getReturnsToken: &Token{
IDToken: &IDToken{
cache := &mockSessionCache{t: t, getReturnsToken: &oidctypes.Token{
IDToken: &oidctypes.IDToken{
Token: "expired-test-id-token",
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() {
require.Empty(t, cache.sawPutKeys)
@ -314,12 +315,12 @@ func TestLogin(t *testing.T) {
clientID: "not-the-test-client-id",
opt: func(t *testing.T) Option {
return func(h *handlerState) error {
cache := &mockSessionCache{t: t, getReturnsToken: &Token{
IDToken: &IDToken{
cache := &mockSessionCache{t: t, getReturnsToken: &oidctypes.Token{
IDToken: &oidctypes.IDToken{
Token: "expired-test-id-token",
Expiry: metav1.Now(), // less than Now() + minIDTokenValidity
},
RefreshToken: &RefreshToken{Token: "test-refresh-token"},
RefreshToken: &oidctypes.RefreshToken{Token: "test-refresh-token"},
}}
t.Cleanup(func() {
require.Empty(t, cache.sawPutKeys)
@ -414,7 +415,7 @@ func TestLogin(t *testing.T) {
t.Cleanup(func() {
require.Equal(t, []SessionCacheKey{cacheKey}, cache.sawGetKeys)
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, WithClient(&http.Client{Timeout: 10 * time.Second})(h))

View File

@ -1,11 +1,10 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package oidcclient
// Package oidctypes provides core data types for OIDC token structures.
package oidctypes
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
import v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// AccessToken is an OAuth2 access token.
type AccessToken struct {
@ -16,7 +15,7 @@ type AccessToken struct {
Type string `json:"type,omitempty"`
// 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.
@ -31,7 +30,7 @@ type IDToken struct {
Token string `json:"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.
@ -47,16 +46,3 @@ type Token struct {
// IDToken is an OpenID Connect ID token.
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)
}