From d32583dd7f8e866983c2ca23bc989e6afdec3a9d Mon Sep 17 00:00:00 2001 From: Matt Moyer Date: Mon, 30 Nov 2020 17:02:03 -0600 Subject: [PATCH] Move OIDC Token structs into a new `oidctypes` package. Signed-off-by: Matt Moyer --- cmd/pinniped/cmd/login_oidc.go | 3 +- cmd/pinniped/cmd/login_oidc_test.go | 7 +- .../oidc/callback/callback_handler_test.go | 6 +- internal/oidc/oidctestutil/oidc.go | 6 +- .../provider/dynamic_upstream_idp_provider.go | 4 +- internal/upstreamoidc/upstreamoidc.go | 24 +++--- internal/upstreamoidc/upstreamoidc_test.go | 22 ++--- pkg/oidcclient/filesession/cachefile.go | 3 +- pkg/oidcclient/filesession/cachefile_test.go | 49 +++++------ pkg/oidcclient/filesession/filesession.go | 7 +- .../filesession/filesession_test.go | 85 ++++++++++--------- pkg/oidcclient/login.go | 34 +++++--- pkg/oidcclient/login_test.go | 43 +++++----- .../{types.go => oidctypes/oidctypes.go} | 24 ++---- 14 files changed, 162 insertions(+), 155 deletions(-) rename pkg/oidcclient/{types.go => oidctypes/oidctypes.go} (69%) diff --git a/cmd/pinniped/cmd/login_oidc.go b/cmd/pinniped/cmd/login_oidc.go index 1677c00f..c8d00662 100644 --- a/cmd/pinniped/cmd/login_oidc.go +++ b/cmd/pinniped/cmd/login_oidc.go @@ -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, diff --git a/cmd/pinniped/cmd/login_oidc_test.go b/cmd/pinniped/cmd/login_oidc_test.go index 3a61934d..37cfac4e 100644 --- a/cmd/pinniped/cmd/login_oidc_test.go +++ b/cmd/pinniped/cmd/login_oidc_test.go @@ -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), }, diff --git a/internal/oidc/callback/callback_handler_test.go b/internal/oidc/callback/callback_handler_test.go index 69072067..23656da2 100644 --- a/internal/oidc/callback/callback_handler_test.go +++ b/internal/oidc/callback/callback_handler_test.go @@ -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 }, } } diff --git a/internal/oidc/oidctestutil/oidc.go b/internal/oidc/oidctestutil/oidc.go index a9b6acbd..43a7147f 100644 --- a/internal/oidc/oidctestutil/oidc.go +++ b/internal/oidc/oidctestutil/oidc.go @@ -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) } diff --git a/internal/oidc/provider/dynamic_upstream_idp_provider.go b/internal/oidc/provider/dynamic_upstream_idp_provider.go index 50ba17ca..0c08708c 100644 --- a/internal/oidc/provider/dynamic_upstream_idp_provider.go +++ b/internal/oidc/provider/dynamic_upstream_idp_provider.go @@ -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 { diff --git a/internal/upstreamoidc/upstreamoidc.go b/internal/upstreamoidc/upstreamoidc.go index 13a800b0..b44f02bc 100644 --- a/internal/upstreamoidc/upstreamoidc.go +++ b/internal/upstreamoidc/upstreamoidc.go @@ -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), }, diff --git a/internal/upstreamoidc/upstreamoidc_test.go b/internal/upstreamoidc/upstreamoidc_test.go index d3cf77ac..3f3eed2e 100644 --- a/internal/upstreamoidc/upstreamoidc_test.go +++ b/internal/upstreamoidc/upstreamoidc_test.go @@ -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 } diff --git a/pkg/oidcclient/filesession/cachefile.go b/pkg/oidcclient/filesession/cachefile.go index 3629ca5f..9ea46bc0 100644 --- a/pkg/oidcclient/filesession/cachefile.go +++ b/pkg/oidcclient/filesession/cachefile.go @@ -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"` } ) diff --git a/pkg/oidcclient/filesession/cachefile_test.go b/pkg/oidcclient/filesession/cachefile_test.go index 0ddcdf9b..a881d7d4 100644 --- a/pkg/oidcclient/filesession/cachefile_test.go +++ b/pkg/oidcclient/filesession/cachefile_test.go @@ -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", }, }, diff --git a/pkg/oidcclient/filesession/filesession.go b/pkg/oidcclient/filesession/filesession.go index 47e0f761..151fde71 100644 --- a/pkg/oidcclient/filesession/filesession.go +++ b/pkg/oidcclient/filesession/filesession.go @@ -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)) diff --git a/pkg/oidcclient/filesession/filesession_test.go b/pkg/oidcclient/filesession/filesession_test.go index 1b28e184..d172af80 100644 --- a/pkg/oidcclient/filesession/filesession_test.go +++ b/pkg/oidcclient/filesession/filesession_test.go @@ -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", }, }, diff --git a/pkg/oidcclient/login.go b/pkg/oidcclient/login.go index 0898f944..09d6949f 100644 --- a/pkg/oidcclient/login.go +++ b/pkg/oidcclient/login.go @@ -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), }, diff --git a/pkg/oidcclient/login_test.go b/pkg/oidcclient/login_test.go index 2b13752b..5bff0142 100644 --- a/pkg/oidcclient/login_test.go +++ b/pkg/oidcclient/login_test.go @@ -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)) diff --git a/pkg/oidcclient/types.go b/pkg/oidcclient/oidctypes/oidctypes.go similarity index 69% rename from pkg/oidcclient/types.go rename to pkg/oidcclient/oidctypes/oidctypes.go index 7fbf3a3f..94f5dcc9 100644 --- a/pkg/oidcclient/types.go +++ b/pkg/oidcclient/oidctypes/oidctypes.go @@ -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) -}