Use a custom type for our static CLI client (smaller change).

Before this change, we used the `fosite.DefaultOpenIDConnectClient{}` struct, which implements the  `fosite.Client` and `fosite.OpenIDConnectClient` interfaces. For a future change, we also need to implement some additional optional interfaces, so we can no longer use the provided default types. Instead, we now use a custom `clientregistry.Client{}` struct, which implements all the requisite interfaces and can be extended to handle the new functionality (in a future change).

There is also a new `clientregistry.StaticRegistry{}` struct, which implements the `fosite.ClientManager` and looks up our single static client. We could potentially extend this in the future with a registry backed by Kubernetes API, for example.

This should be 100% refactor, with no user-observable change.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
This commit is contained in:
Matt Moyer 2021-06-15 11:27:30 -05:00
parent 1a610022cf
commit 551249fb69
No known key found for this signature in database
GPG Key ID: EAE88AD172C5AE2D
17 changed files with 356 additions and 215 deletions

View File

@ -1,4 +1,4 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package accesstoken package accesstoken
@ -17,6 +17,7 @@ import (
"go.pinniped.dev/internal/constable" "go.pinniped.dev/internal/constable"
"go.pinniped.dev/internal/crud" "go.pinniped.dev/internal/crud"
"go.pinniped.dev/internal/fositestorage" "go.pinniped.dev/internal/fositestorage"
"go.pinniped.dev/internal/oidc/clientregistry"
) )
const ( const (
@ -108,7 +109,7 @@ func (a *accessTokenStorage) getSession(ctx context.Context, signature string) (
func newValidEmptyAccessTokenSession() *session { func newValidEmptyAccessTokenSession() *session {
return &session{ return &session{
Request: &fosite.Request{ Request: &fosite.Request{
Client: &fosite.DefaultOpenIDConnectClient{}, Client: &clientregistry.Client{},
Session: &openid.DefaultSession{}, Session: &openid.DefaultSession{},
}, },
} }

View File

@ -1,4 +1,4 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package accesstoken package accesstoken
@ -20,6 +20,8 @@ import (
"k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/fake"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1" corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
coretesting "k8s.io/client-go/testing" coretesting "k8s.io/client-go/testing"
"go.pinniped.dev/internal/oidc/clientregistry"
) )
const namespace = "test-ns" const namespace = "test-ns"
@ -63,24 +65,25 @@ func TestAccessTokenStorage(t *testing.T) {
request := &fosite.Request{ request := &fosite.Request{
ID: "abcd-1", ID: "abcd-1",
RequestedAt: time.Time{}, RequestedAt: time.Time{},
Client: &fosite.DefaultOpenIDConnectClient{ Client: &clientregistry.Client{
DefaultClient: &fosite.DefaultClient{ DefaultOpenIDConnectClient: fosite.DefaultOpenIDConnectClient{
ID: "pinny", DefaultClient: &fosite.DefaultClient{
Secret: nil, ID: "pinny",
RedirectURIs: nil, Secret: nil,
GrantTypes: nil, RedirectURIs: nil,
ResponseTypes: nil, GrantTypes: nil,
Scopes: nil, ResponseTypes: nil,
Audience: nil, Scopes: nil,
Public: true, Audience: nil,
}, Public: true,
JSONWebKeysURI: "where", },
JSONWebKeys: nil, JSONWebKeysURI: "where",
TokenEndpointAuthMethod: "something", JSONWebKeys: nil,
RequestURIs: nil, TokenEndpointAuthMethod: "something",
RequestObjectSigningAlgorithm: "", RequestURIs: nil,
TokenEndpointAuthSigningAlgorithm: "", RequestObjectSigningAlgorithm: "",
}, TokenEndpointAuthSigningAlgorithm: "",
}},
RequestedScope: nil, RequestedScope: nil,
GrantedScope: nil, GrantedScope: nil,
Form: url.Values{"key": []string{"val"}}, Form: url.Values{"key": []string{"val"}},
@ -138,13 +141,15 @@ func TestAccessTokenStorageRevocation(t *testing.T) {
request := &fosite.Request{ request := &fosite.Request{
ID: "abcd-1", ID: "abcd-1",
RequestedAt: time.Time{}, RequestedAt: time.Time{},
Client: &fosite.DefaultOpenIDConnectClient{ Client: &clientregistry.Client{
DefaultClient: &fosite.DefaultClient{ DefaultOpenIDConnectClient: fosite.DefaultOpenIDConnectClient{
ID: "pinny", DefaultClient: &fosite.DefaultClient{
Public: true, ID: "pinny",
Public: true,
},
JSONWebKeysURI: "where",
TokenEndpointAuthMethod: "something",
}, },
JSONWebKeysURI: "where",
TokenEndpointAuthMethod: "something",
}, },
Form: url.Values{"key": []string{"val"}}, Form: url.Values{"key": []string{"val"}},
Session: &openid.DefaultSession{ Session: &openid.DefaultSession{
@ -238,7 +243,7 @@ func TestCreateWithWrongRequesterDataTypes(t *testing.T) {
request := &fosite.Request{ request := &fosite.Request{
Session: nil, Session: nil,
Client: &fosite.DefaultOpenIDConnectClient{}, Client: &clientregistry.Client{},
} }
err := storage.CreateAccessTokenSession(ctx, "signature-doesnt-matter", request) err := storage.CreateAccessTokenSession(ctx, "signature-doesnt-matter", request)
require.EqualError(t, err, "requester's session must be of type openid.DefaultSession") require.EqualError(t, err, "requester's session must be of type openid.DefaultSession")
@ -248,7 +253,7 @@ func TestCreateWithWrongRequesterDataTypes(t *testing.T) {
Client: nil, Client: nil,
} }
err = storage.CreateAccessTokenSession(ctx, "signature-doesnt-matter", request) err = storage.CreateAccessTokenSession(ctx, "signature-doesnt-matter", request)
require.EqualError(t, err, "requester's client must be of type fosite.DefaultOpenIDConnectClient") require.EqualError(t, err, "requester's client must be of type clientregistry.Client")
} }
func TestCreateWithoutRequesterID(t *testing.T) { func TestCreateWithoutRequesterID(t *testing.T) {
@ -257,7 +262,7 @@ func TestCreateWithoutRequesterID(t *testing.T) {
request := &fosite.Request{ request := &fosite.Request{
ID: "", // empty ID ID: "", // empty ID
Session: &openid.DefaultSession{}, Session: &openid.DefaultSession{},
Client: &fosite.DefaultOpenIDConnectClient{}, Client: &clientregistry.Client{},
} }
err := storage.CreateAccessTokenSession(ctx, "signature-doesnt-matter", request) err := storage.CreateAccessTokenSession(ctx, "signature-doesnt-matter", request)
require.NoError(t, err) require.NoError(t, err)

View File

@ -1,4 +1,4 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package authorizationcode package authorizationcode
@ -18,6 +18,7 @@ import (
"go.pinniped.dev/internal/constable" "go.pinniped.dev/internal/constable"
"go.pinniped.dev/internal/crud" "go.pinniped.dev/internal/crud"
"go.pinniped.dev/internal/fositestorage" "go.pinniped.dev/internal/fositestorage"
"go.pinniped.dev/internal/oidc/clientregistry"
) )
const ( const (
@ -137,7 +138,7 @@ func (a *authorizeCodeStorage) getSession(ctx context.Context, signature string)
func NewValidEmptyAuthorizeCodeSession() *AuthorizeCodeSession { func NewValidEmptyAuthorizeCodeSession() *AuthorizeCodeSession {
return &AuthorizeCodeSession{ return &AuthorizeCodeSession{
Request: &fosite.Request{ Request: &fosite.Request{
Client: &fosite.DefaultOpenIDConnectClient{}, Client: &clientregistry.Client{},
Session: &openid.DefaultSession{}, Session: &openid.DefaultSession{},
}, },
} }

View File

@ -1,4 +1,4 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package authorizationcode package authorizationcode
@ -33,6 +33,7 @@ import (
kubetesting "k8s.io/client-go/testing" kubetesting "k8s.io/client-go/testing"
"go.pinniped.dev/internal/fositestorage" "go.pinniped.dev/internal/fositestorage"
"go.pinniped.dev/internal/oidc/clientregistry"
) )
const namespace = "test-ns" const namespace = "test-ns"
@ -92,23 +93,25 @@ func TestAuthorizationCodeStorage(t *testing.T) {
request := &fosite.Request{ request := &fosite.Request{
ID: "abcd-1", ID: "abcd-1",
RequestedAt: time.Time{}, RequestedAt: time.Time{},
Client: &fosite.DefaultOpenIDConnectClient{ Client: &clientregistry.Client{
DefaultClient: &fosite.DefaultClient{ DefaultOpenIDConnectClient: fosite.DefaultOpenIDConnectClient{
ID: "pinny", DefaultClient: &fosite.DefaultClient{
Secret: nil, ID: "pinny",
RedirectURIs: nil, Secret: nil,
GrantTypes: nil, RedirectURIs: nil,
ResponseTypes: nil, GrantTypes: nil,
Scopes: nil, ResponseTypes: nil,
Audience: nil, Scopes: nil,
Public: true, Audience: nil,
Public: true,
},
JSONWebKeysURI: "where",
JSONWebKeys: nil,
TokenEndpointAuthMethod: "something",
RequestURIs: nil,
RequestObjectSigningAlgorithm: "",
TokenEndpointAuthSigningAlgorithm: "",
}, },
JSONWebKeysURI: "where",
JSONWebKeys: nil,
TokenEndpointAuthMethod: "something",
RequestURIs: nil,
RequestObjectSigningAlgorithm: "",
TokenEndpointAuthSigningAlgorithm: "",
}, },
RequestedScope: nil, RequestedScope: nil,
GrantedScope: nil, GrantedScope: nil,
@ -169,7 +172,7 @@ func TestInvalidateWhenConflictOnUpdateHappens(t *testing.T) {
request := &fosite.Request{ request := &fosite.Request{
ID: "some-request-id", ID: "some-request-id",
Client: &fosite.DefaultOpenIDConnectClient{}, Client: &clientregistry.Client{},
Session: &openid.DefaultSession{}, Session: &openid.DefaultSession{},
} }
err := storage.CreateAuthorizeCodeSession(ctx, "fancy-signature", request) err := storage.CreateAuthorizeCodeSession(ctx, "fancy-signature", request)
@ -240,7 +243,7 @@ func TestCreateWithWrongRequesterDataTypes(t *testing.T) {
request := &fosite.Request{ request := &fosite.Request{
Session: nil, Session: nil,
Client: &fosite.DefaultOpenIDConnectClient{}, Client: &clientregistry.Client{},
} }
err := storage.CreateAuthorizeCodeSession(ctx, "signature-doesnt-matter", request) err := storage.CreateAuthorizeCodeSession(ctx, "signature-doesnt-matter", request)
require.EqualError(t, err, "requester's session must be of type openid.DefaultSession") require.EqualError(t, err, "requester's session must be of type openid.DefaultSession")
@ -250,7 +253,7 @@ func TestCreateWithWrongRequesterDataTypes(t *testing.T) {
Client: nil, Client: nil,
} }
err = storage.CreateAuthorizeCodeSession(ctx, "signature-doesnt-matter", request) err = storage.CreateAuthorizeCodeSession(ctx, "signature-doesnt-matter", request)
require.EqualError(t, err, "requester's client must be of type fosite.DefaultOpenIDConnectClient") require.EqualError(t, err, "requester's client must be of type clientregistry.Client")
} }
func makeTestSubject() (context.Context, *fake.Clientset, corev1client.SecretInterface, oauth2.AuthorizeCodeStorage) { func makeTestSubject() (context.Context, *fake.Clientset, corev1client.SecretInterface, oauth2.AuthorizeCodeStorage) {
@ -270,7 +273,7 @@ func TestFuzzAndJSONNewValidEmptyAuthorizeCodeSession(t *testing.T) {
require.Equal(t, validSession.Request, extractedRequest) require.Equal(t, validSession.Request, extractedRequest)
// checked above // checked above
defaultClient := validSession.Request.Client.(*fosite.DefaultOpenIDConnectClient) defaultClient := validSession.Request.Client.(*clientregistry.Client)
defaultSession := validSession.Request.Session.(*openid.DefaultSession) defaultSession := validSession.Request.Session.(*openid.DefaultSession)
// makes it easier to use a raw string // makes it easier to use a raw string

View File

@ -1,4 +1,4 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package fositestorage package fositestorage
@ -8,11 +8,12 @@ import (
"github.com/ory/fosite/handler/openid" "github.com/ory/fosite/handler/openid"
"go.pinniped.dev/internal/constable" "go.pinniped.dev/internal/constable"
"go.pinniped.dev/internal/oidc/clientregistry"
) )
const ( const (
ErrInvalidRequestType = constable.Error("requester must be of type fosite.Request") ErrInvalidRequestType = constable.Error("requester must be of type fosite.Request")
ErrInvalidClientType = constable.Error("requester's client must be of type fosite.DefaultOpenIDConnectClient") ErrInvalidClientType = constable.Error("requester's client must be of type clientregistry.Client")
ErrInvalidSessionType = constable.Error("requester's session must be of type openid.DefaultSession") ErrInvalidSessionType = constable.Error("requester's session must be of type openid.DefaultSession")
StorageRequestIDLabelName = "storage.pinniped.dev/request-id" //nolint:gosec // this is not a credential StorageRequestIDLabelName = "storage.pinniped.dev/request-id" //nolint:gosec // this is not a credential
) )
@ -22,7 +23,7 @@ func ValidateAndExtractAuthorizeRequest(requester fosite.Requester) (*fosite.Req
if !ok1 { if !ok1 {
return nil, ErrInvalidRequestType return nil, ErrInvalidRequestType
} }
_, ok2 := request.Client.(*fosite.DefaultOpenIDConnectClient) _, ok2 := request.Client.(*clientregistry.Client)
if !ok2 { if !ok2 {
return nil, ErrInvalidClientType return nil, ErrInvalidClientType
} }

View File

@ -1,4 +1,4 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package openidconnect package openidconnect
@ -17,6 +17,7 @@ import (
"go.pinniped.dev/internal/constable" "go.pinniped.dev/internal/constable"
"go.pinniped.dev/internal/crud" "go.pinniped.dev/internal/crud"
"go.pinniped.dev/internal/fositestorage" "go.pinniped.dev/internal/fositestorage"
"go.pinniped.dev/internal/oidc/clientregistry"
) )
const ( const (
@ -110,7 +111,7 @@ func (a *openIDConnectRequestStorage) getSession(ctx context.Context, signature
func newValidEmptyOIDCSession() *session { func newValidEmptyOIDCSession() *session {
return &session{ return &session{
Request: &fosite.Request{ Request: &fosite.Request{
Client: &fosite.DefaultOpenIDConnectClient{}, Client: &clientregistry.Client{},
Session: &openid.DefaultSession{}, Session: &openid.DefaultSession{},
}, },
} }

View File

@ -1,4 +1,4 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package openidconnect package openidconnect
@ -20,6 +20,8 @@ import (
"k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/fake"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1" corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
coretesting "k8s.io/client-go/testing" coretesting "k8s.io/client-go/testing"
"go.pinniped.dev/internal/oidc/clientregistry"
) )
const namespace = "test-ns" const namespace = "test-ns"
@ -62,23 +64,25 @@ func TestOpenIdConnectStorage(t *testing.T) {
request := &fosite.Request{ request := &fosite.Request{
ID: "abcd-1", ID: "abcd-1",
RequestedAt: time.Time{}, RequestedAt: time.Time{},
Client: &fosite.DefaultOpenIDConnectClient{ Client: &clientregistry.Client{
DefaultClient: &fosite.DefaultClient{ DefaultOpenIDConnectClient: fosite.DefaultOpenIDConnectClient{
ID: "pinny", DefaultClient: &fosite.DefaultClient{
Secret: nil, ID: "pinny",
RedirectURIs: nil, Secret: nil,
GrantTypes: nil, RedirectURIs: nil,
ResponseTypes: nil, GrantTypes: nil,
Scopes: nil, ResponseTypes: nil,
Audience: nil, Scopes: nil,
Public: true, Audience: nil,
Public: true,
},
JSONWebKeysURI: "where",
JSONWebKeys: nil,
TokenEndpointAuthMethod: "something",
RequestURIs: nil,
RequestObjectSigningAlgorithm: "",
TokenEndpointAuthSigningAlgorithm: "",
}, },
JSONWebKeysURI: "where",
JSONWebKeys: nil,
TokenEndpointAuthMethod: "something",
RequestURIs: nil,
RequestObjectSigningAlgorithm: "",
TokenEndpointAuthSigningAlgorithm: "",
}, },
RequestedScope: nil, RequestedScope: nil,
GrantedScope: nil, GrantedScope: nil,
@ -176,7 +180,7 @@ func TestCreateWithWrongRequesterDataTypes(t *testing.T) {
request := &fosite.Request{ request := &fosite.Request{
Session: nil, Session: nil,
Client: &fosite.DefaultOpenIDConnectClient{}, Client: &clientregistry.Client{},
} }
err := storage.CreateOpenIDConnectSession(ctx, "authcode.signature-doesnt-matter", request) err := storage.CreateOpenIDConnectSession(ctx, "authcode.signature-doesnt-matter", request)
require.EqualError(t, err, "requester's session must be of type openid.DefaultSession") require.EqualError(t, err, "requester's session must be of type openid.DefaultSession")
@ -186,7 +190,7 @@ func TestCreateWithWrongRequesterDataTypes(t *testing.T) {
Client: nil, Client: nil,
} }
err = storage.CreateOpenIDConnectSession(ctx, "authcode.signature-doesnt-matter", request) err = storage.CreateOpenIDConnectSession(ctx, "authcode.signature-doesnt-matter", request)
require.EqualError(t, err, "requester's client must be of type fosite.DefaultOpenIDConnectClient") require.EqualError(t, err, "requester's client must be of type clientregistry.Client")
} }
func TestAuthcodeHasNoDot(t *testing.T) { func TestAuthcodeHasNoDot(t *testing.T) {

View File

@ -1,4 +1,4 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package pkce package pkce
@ -17,6 +17,7 @@ import (
"go.pinniped.dev/internal/constable" "go.pinniped.dev/internal/constable"
"go.pinniped.dev/internal/crud" "go.pinniped.dev/internal/crud"
"go.pinniped.dev/internal/fositestorage" "go.pinniped.dev/internal/fositestorage"
"go.pinniped.dev/internal/oidc/clientregistry"
) )
const ( const (
@ -94,7 +95,7 @@ func (a *pkceStorage) getSession(ctx context.Context, signature string) (*sessio
func newValidEmptyPKCESession() *session { func newValidEmptyPKCESession() *session {
return &session{ return &session{
Request: &fosite.Request{ Request: &fosite.Request{
Client: &fosite.DefaultOpenIDConnectClient{}, Client: &clientregistry.Client{},
Session: &openid.DefaultSession{}, Session: &openid.DefaultSession{},
}, },
} }

View File

@ -1,4 +1,4 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package pkce package pkce
@ -21,6 +21,8 @@ import (
"k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/fake"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1" corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
coretesting "k8s.io/client-go/testing" coretesting "k8s.io/client-go/testing"
"go.pinniped.dev/internal/oidc/clientregistry"
) )
const namespace = "test-ns" const namespace = "test-ns"
@ -63,23 +65,25 @@ func TestPKCEStorage(t *testing.T) {
request := &fosite.Request{ request := &fosite.Request{
ID: "abcd-1", ID: "abcd-1",
RequestedAt: time.Time{}, RequestedAt: time.Time{},
Client: &fosite.DefaultOpenIDConnectClient{ Client: &clientregistry.Client{
DefaultClient: &fosite.DefaultClient{ DefaultOpenIDConnectClient: fosite.DefaultOpenIDConnectClient{
ID: "pinny", DefaultClient: &fosite.DefaultClient{
Secret: nil, ID: "pinny",
RedirectURIs: nil, Secret: nil,
GrantTypes: nil, RedirectURIs: nil,
ResponseTypes: nil, GrantTypes: nil,
Scopes: nil, ResponseTypes: nil,
Audience: nil, Scopes: nil,
Public: true, Audience: nil,
Public: true,
},
JSONWebKeysURI: "where",
JSONWebKeys: nil,
TokenEndpointAuthMethod: "something",
RequestURIs: nil,
RequestObjectSigningAlgorithm: "",
TokenEndpointAuthSigningAlgorithm: "",
}, },
JSONWebKeysURI: "where",
JSONWebKeys: nil,
TokenEndpointAuthMethod: "something",
RequestURIs: nil,
RequestObjectSigningAlgorithm: "",
TokenEndpointAuthSigningAlgorithm: "",
}, },
RequestedScope: nil, RequestedScope: nil,
GrantedScope: nil, GrantedScope: nil,
@ -183,7 +187,7 @@ func TestCreateWithWrongRequesterDataTypes(t *testing.T) {
request := &fosite.Request{ request := &fosite.Request{
Session: nil, Session: nil,
Client: &fosite.DefaultOpenIDConnectClient{}, Client: &clientregistry.Client{},
} }
err := storage.CreatePKCERequestSession(ctx, "signature-doesnt-matter", request) err := storage.CreatePKCERequestSession(ctx, "signature-doesnt-matter", request)
require.EqualError(t, err, "requester's session must be of type openid.DefaultSession") require.EqualError(t, err, "requester's session must be of type openid.DefaultSession")
@ -193,7 +197,7 @@ func TestCreateWithWrongRequesterDataTypes(t *testing.T) {
Client: nil, Client: nil,
} }
err = storage.CreatePKCERequestSession(ctx, "signature-doesnt-matter", request) err = storage.CreatePKCERequestSession(ctx, "signature-doesnt-matter", request)
require.EqualError(t, err, "requester's client must be of type fosite.DefaultOpenIDConnectClient") require.EqualError(t, err, "requester's client must be of type clientregistry.Client")
} }
func makeTestSubject() (context.Context, *fake.Clientset, corev1client.SecretInterface, pkce.PKCERequestStorage) { func makeTestSubject() (context.Context, *fake.Clientset, corev1client.SecretInterface, pkce.PKCERequestStorage) {

View File

@ -1,4 +1,4 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package refreshtoken package refreshtoken
@ -17,6 +17,7 @@ import (
"go.pinniped.dev/internal/constable" "go.pinniped.dev/internal/constable"
"go.pinniped.dev/internal/crud" "go.pinniped.dev/internal/crud"
"go.pinniped.dev/internal/fositestorage" "go.pinniped.dev/internal/fositestorage"
"go.pinniped.dev/internal/oidc/clientregistry"
) )
const ( const (
@ -108,7 +109,7 @@ func (a *refreshTokenStorage) getSession(ctx context.Context, signature string)
func newValidEmptyRefreshTokenSession() *session { func newValidEmptyRefreshTokenSession() *session {
return &session{ return &session{
Request: &fosite.Request{ Request: &fosite.Request{
Client: &fosite.DefaultOpenIDConnectClient{}, Client: &clientregistry.Client{},
Session: &openid.DefaultSession{}, Session: &openid.DefaultSession{},
}, },
} }

View File

@ -1,4 +1,4 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved. // Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package refreshtoken package refreshtoken
@ -20,6 +20,8 @@ import (
"k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/fake"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1" corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
coretesting "k8s.io/client-go/testing" coretesting "k8s.io/client-go/testing"
"go.pinniped.dev/internal/oidc/clientregistry"
) )
const namespace = "test-ns" const namespace = "test-ns"
@ -62,23 +64,25 @@ func TestRefreshTokenStorage(t *testing.T) {
request := &fosite.Request{ request := &fosite.Request{
ID: "abcd-1", ID: "abcd-1",
RequestedAt: time.Time{}, RequestedAt: time.Time{},
Client: &fosite.DefaultOpenIDConnectClient{ Client: &clientregistry.Client{
DefaultClient: &fosite.DefaultClient{ DefaultOpenIDConnectClient: fosite.DefaultOpenIDConnectClient{
ID: "pinny", DefaultClient: &fosite.DefaultClient{
Secret: nil, ID: "pinny",
RedirectURIs: nil, Secret: nil,
GrantTypes: nil, RedirectURIs: nil,
ResponseTypes: nil, GrantTypes: nil,
Scopes: nil, ResponseTypes: nil,
Audience: nil, Scopes: nil,
Public: true, Audience: nil,
Public: true,
},
JSONWebKeysURI: "where",
JSONWebKeys: nil,
TokenEndpointAuthMethod: "something",
RequestURIs: nil,
RequestObjectSigningAlgorithm: "",
TokenEndpointAuthSigningAlgorithm: "",
}, },
JSONWebKeysURI: "where",
JSONWebKeys: nil,
TokenEndpointAuthMethod: "something",
RequestURIs: nil,
RequestObjectSigningAlgorithm: "",
TokenEndpointAuthSigningAlgorithm: "",
}, },
RequestedScope: nil, RequestedScope: nil,
GrantedScope: nil, GrantedScope: nil,
@ -137,13 +141,15 @@ func TestRefreshTokenStorageRevocation(t *testing.T) {
request := &fosite.Request{ request := &fosite.Request{
ID: "abcd-1", ID: "abcd-1",
RequestedAt: time.Time{}, RequestedAt: time.Time{},
Client: &fosite.DefaultOpenIDConnectClient{ Client: &clientregistry.Client{
DefaultClient: &fosite.DefaultClient{ DefaultOpenIDConnectClient: fosite.DefaultOpenIDConnectClient{
ID: "pinny", DefaultClient: &fosite.DefaultClient{
Public: true, ID: "pinny",
Public: true,
},
JSONWebKeysURI: "where",
TokenEndpointAuthMethod: "something",
}, },
JSONWebKeysURI: "where",
TokenEndpointAuthMethod: "something",
}, },
Form: url.Values{"key": []string{"val"}}, Form: url.Values{"key": []string{"val"}},
Session: &openid.DefaultSession{ Session: &openid.DefaultSession{
@ -237,7 +243,7 @@ func TestCreateWithWrongRequesterDataTypes(t *testing.T) {
request := &fosite.Request{ request := &fosite.Request{
Session: nil, Session: nil,
Client: &fosite.DefaultOpenIDConnectClient{}, Client: &clientregistry.Client{},
} }
err := storage.CreateRefreshTokenSession(ctx, "signature-doesnt-matter", request) err := storage.CreateRefreshTokenSession(ctx, "signature-doesnt-matter", request)
require.EqualError(t, err, "requester's session must be of type openid.DefaultSession") require.EqualError(t, err, "requester's session must be of type openid.DefaultSession")
@ -247,7 +253,7 @@ func TestCreateWithWrongRequesterDataTypes(t *testing.T) {
Client: nil, Client: nil,
} }
err = storage.CreateRefreshTokenSession(ctx, "signature-doesnt-matter", request) err = storage.CreateRefreshTokenSession(ctx, "signature-doesnt-matter", request)
require.EqualError(t, err, "requester's client must be of type fosite.DefaultOpenIDConnectClient") require.EqualError(t, err, "requester's client must be of type clientregistry.Client")
} }
func TestCreateWithoutRequesterID(t *testing.T) { func TestCreateWithoutRequesterID(t *testing.T) {
@ -256,7 +262,7 @@ func TestCreateWithoutRequesterID(t *testing.T) {
request := &fosite.Request{ request := &fosite.Request{
ID: "", // empty ID ID: "", // empty ID
Session: &openid.DefaultSession{}, Session: &openid.DefaultSession{},
Client: &fosite.DefaultOpenIDConnectClient{}, Client: &clientregistry.Client{},
} }
err := storage.CreateRefreshTokenSession(ctx, "signature-doesnt-matter", request) err := storage.CreateRefreshTokenSession(ctx, "signature-doesnt-matter", request)
require.NoError(t, err) require.NoError(t, err)

View File

@ -0,0 +1,94 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Package clientregistry defines Pinniped's OAuth2/OIDC clients.
package clientregistry
import (
"context"
"fmt"
"time"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/ory/fosite"
)
// Client represents a Pinniped OAuth/OIDC client.
type Client struct {
fosite.DefaultOpenIDConnectClient
}
// It implements both the base and OIDC client interfaces of Fosite.
var (
_ fosite.Client = (*Client)(nil)
_ fosite.OpenIDConnectClient = (*Client)(nil)
)
// StaticClientManager is a fosite.ClientManager with statically-defined clients.
type StaticClientManager struct{}
var _ fosite.ClientManager = (*StaticClientManager)(nil)
// GetClient returns a static client specified by the given ID.
//
// It returns a fosite.ErrNotFound if an unknown client is specified.
func (StaticClientManager) GetClient(_ context.Context, id string) (fosite.Client, error) {
switch id {
case "pinniped-cli":
return PinnipedCLI(), nil
default:
return nil, fosite.ErrNotFound.WithDescription("no such client")
}
}
// ClientAssertionJWTValid returns an error if the JTI is
// known or the DB check failed and nil if the JTI is not known.
//
// This functionality is not supported by the StaticClientManager.
func (StaticClientManager) ClientAssertionJWTValid(ctx context.Context, jti string) error {
return fmt.Errorf("not implemented")
}
// SetClientAssertionJWT marks a JTI as known for the given
// expiry time. Before inserting the new JTI, it will clean
// up any existing JTIs that have expired as those tokens can
// not be replayed due to the expiry.
//
// This functionality is not supported by the StaticClientManager.
func (StaticClientManager) SetClientAssertionJWT(ctx context.Context, jti string, exp time.Time) error {
return fmt.Errorf("not implemented")
}
// PinnipedCLI returns the static Client corresponding to the Pinniped CLI.
func PinnipedCLI() *Client {
return &Client{
DefaultOpenIDConnectClient: fosite.DefaultOpenIDConnectClient{
DefaultClient: &fosite.DefaultClient{
ID: "pinniped-cli",
Secret: nil,
RedirectURIs: []string{"http://127.0.0.1/callback"},
GrantTypes: fosite.Arguments{
"authorization_code",
"refresh_token",
"urn:ietf:params:oauth:grant-type:token-exchange",
},
ResponseTypes: []string{"code"},
Scopes: fosite.Arguments{
oidc.ScopeOpenID,
oidc.ScopeOfflineAccess,
"profile",
"email",
"pinniped:request-audience",
},
Audience: nil,
Public: true,
},
RequestURIs: nil,
JSONWebKeys: nil,
JSONWebKeysURI: "",
RequestObjectSigningAlgorithm: "",
TokenEndpointAuthSigningAlgorithm: oidc.RS256,
TokenEndpointAuthMethod: "none",
},
}
}

View File

@ -0,0 +1,95 @@
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package clientregistry
import (
"context"
"encoding/json"
"testing"
"time"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/ory/fosite"
"github.com/stretchr/testify/require"
)
func TestStaticRegistry(t *testing.T) {
ctx := context.Background()
t.Run("unimplemented methods", func(t *testing.T) {
registry := StaticClientManager{}
require.EqualError(t, registry.ClientAssertionJWTValid(ctx, "some-token-id"), "not implemented")
require.EqualError(t, registry.SetClientAssertionJWT(ctx, "some-token-id", time.Now()), "not implemented")
})
t.Run("not found", func(t *testing.T) {
registry := StaticClientManager{}
got, err := registry.GetClient(ctx, "does-not-exist")
require.Error(t, err)
require.Nil(t, got)
rfcErr := fosite.ErrorToRFC6749Error(err)
require.NotNil(t, rfcErr)
require.Equal(t, rfcErr.CodeField, 404)
require.Equal(t, rfcErr.GetDescription(), "no such client")
})
t.Run("pinniped CLI", func(t *testing.T) {
registry := StaticClientManager{}
got, err := registry.GetClient(ctx, "pinniped-cli")
require.NoError(t, err)
require.NotNil(t, got)
require.IsType(t, &Client{}, got)
})
}
func TestPinnipedCLI(t *testing.T) {
c := PinnipedCLI()
require.Equal(t, "pinniped-cli", c.GetID())
require.Nil(t, c.GetHashedSecret())
require.Equal(t, []string{"http://127.0.0.1/callback"}, c.GetRedirectURIs())
require.Equal(t, fosite.Arguments{"authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:token-exchange"}, c.GetGrantTypes())
require.Equal(t, fosite.Arguments{"code"}, c.GetResponseTypes())
require.Equal(t, fosite.Arguments{oidc.ScopeOpenID, oidc.ScopeOfflineAccess, "profile", "email", "pinniped:request-audience"}, c.GetScopes())
require.True(t, c.IsPublic())
require.Nil(t, c.GetAudience())
require.Nil(t, c.GetRequestURIs())
require.Nil(t, c.GetJSONWebKeys())
require.Equal(t, "", c.GetJSONWebKeysURI())
require.Equal(t, "", c.GetRequestObjectSigningAlgorithm())
require.Equal(t, "none", c.GetTokenEndpointAuthMethod())
require.Equal(t, "RS256", c.GetTokenEndpointAuthSigningAlgorithm())
marshaled, err := json.Marshal(c)
require.NoError(t, err)
require.JSONEq(t, `
{
"id": "pinniped-cli",
"redirect_uris": [
"http://127.0.0.1/callback"
],
"grant_types": [
"authorization_code",
"refresh_token",
"urn:ietf:params:oauth:grant-type:token-exchange"
],
"response_types": [
"code"
],
"scopes": [
"openid",
"offline_access",
"profile",
"email",
"pinniped:request-audience"
],
"audience": null,
"public": true,
"jwks_uri": "",
"jwks": null,
"token_endpoint_auth_method": "none",
"request_uris": null,
"request_object_signing_alg": "",
"token_endpoint_auth_signing_alg": "RS256"
}`, string(marshaled))
}

View File

@ -13,18 +13,17 @@ import (
fositepkce "github.com/ory/fosite/handler/pkce" fositepkce "github.com/ory/fosite/handler/pkce"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1" corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
"go.pinniped.dev/internal/constable"
"go.pinniped.dev/internal/fositestorage/accesstoken" "go.pinniped.dev/internal/fositestorage/accesstoken"
"go.pinniped.dev/internal/fositestorage/authorizationcode" "go.pinniped.dev/internal/fositestorage/authorizationcode"
"go.pinniped.dev/internal/fositestorage/openidconnect" "go.pinniped.dev/internal/fositestorage/openidconnect"
"go.pinniped.dev/internal/fositestorage/pkce" "go.pinniped.dev/internal/fositestorage/pkce"
"go.pinniped.dev/internal/fositestorage/refreshtoken" "go.pinniped.dev/internal/fositestorage/refreshtoken"
"go.pinniped.dev/internal/fositestoragei" "go.pinniped.dev/internal/fositestoragei"
"go.pinniped.dev/internal/oidc/clientregistry"
) )
const errKubeStorageNotImplemented = constable.Error("KubeStorage does not implement this method. It should not have been called.")
type KubeStorage struct { type KubeStorage struct {
clientManager fosite.ClientManager
authorizationCodeStorage oauth2.AuthorizeCodeStorage authorizationCodeStorage oauth2.AuthorizeCodeStorage
pkceStorage fositepkce.PKCERequestStorage pkceStorage fositepkce.PKCERequestStorage
oidcStorage openid.OpenIDConnectRequestStorage oidcStorage openid.OpenIDConnectRequestStorage
@ -37,6 +36,7 @@ var _ fositestoragei.AllFositeStorage = &KubeStorage{}
func NewKubeStorage(secrets corev1client.SecretInterface, timeoutsConfiguration TimeoutsConfiguration) *KubeStorage { func NewKubeStorage(secrets corev1client.SecretInterface, timeoutsConfiguration TimeoutsConfiguration) *KubeStorage {
nowFunc := time.Now nowFunc := time.Now
return &KubeStorage{ return &KubeStorage{
clientManager: &clientregistry.StaticClientManager{},
authorizationCodeStorage: authorizationcode.New(secrets, nowFunc, timeoutsConfiguration.AuthorizationCodeSessionStorageLifetime), authorizationCodeStorage: authorizationcode.New(secrets, nowFunc, timeoutsConfiguration.AuthorizationCodeSessionStorageLifetime),
pkceStorage: pkce.New(secrets, nowFunc, timeoutsConfiguration.PKCESessionStorageLifetime), pkceStorage: pkce.New(secrets, nowFunc, timeoutsConfiguration.PKCESessionStorageLifetime),
oidcStorage: openidconnect.New(secrets, nowFunc, timeoutsConfiguration.OIDCSessionStorageLifetime), oidcStorage: openidconnect.New(secrets, nowFunc, timeoutsConfiguration.OIDCSessionStorageLifetime),
@ -183,26 +183,15 @@ func (k KubeStorage) RevokeRefreshToken(ctx context.Context, requestID string) e
// //
// OAuth client definitions: // OAuth client definitions:
// //
// For the time being, we only allow a single pre-defined client, so we do not need to interact with any underlying
// storage mechanism to fetch them.
//
func (KubeStorage) GetClient(_ context.Context, id string) (fosite.Client, error) { func (k KubeStorage) GetClient(ctx context.Context, id string) (fosite.Client, error) {
client := PinnipedCLIOIDCClient() return k.clientManager.GetClient(ctx, id)
if client.ID == id {
return client, nil
}
return nil, fosite.ErrNotFound
} }
// func (k KubeStorage) ClientAssertionJWTValid(ctx context.Context, jti string) error {
// Unused interface methods. return k.clientManager.ClientAssertionJWTValid(ctx, jti)
//
func (KubeStorage) ClientAssertionJWTValid(_ context.Context, _ string) error {
return errKubeStorageNotImplemented
} }
func (KubeStorage) SetClientAssertionJWT(_ context.Context, _ string, _ time.Time) error { func (k KubeStorage) SetClientAssertionJWT(ctx context.Context, jti string, exp time.Time) error {
return errKubeStorageNotImplemented return k.clientManager.SetClientAssertionJWT(ctx, jti, exp)
} }

View File

@ -5,17 +5,19 @@ package oidc
import ( import (
"context" "context"
"time"
"github.com/ory/fosite" "github.com/ory/fosite"
"go.pinniped.dev/internal/constable" "go.pinniped.dev/internal/constable"
"go.pinniped.dev/internal/fositestoragei" "go.pinniped.dev/internal/fositestoragei"
"go.pinniped.dev/internal/oidc/clientregistry"
) )
const errNullStorageNotImplemented = constable.Error("NullStorage does not implement this method. It should not have been called.") const errNullStorageNotImplemented = constable.Error("NullStorage does not implement this method. It should not have been called.")
type NullStorage struct{} type NullStorage struct {
clientregistry.StaticClientManager
}
var _ fositestoragei.AllFositeStorage = &NullStorage{} var _ fositestoragei.AllFositeStorage = &NullStorage{}
@ -86,19 +88,3 @@ func (NullStorage) GetAuthorizeCodeSession(_ context.Context, _ string, _ fosite
func (NullStorage) InvalidateAuthorizeCodeSession(_ context.Context, _ string) (err error) { func (NullStorage) InvalidateAuthorizeCodeSession(_ context.Context, _ string) (err error) {
return errNullStorageNotImplemented return errNullStorageNotImplemented
} }
func (NullStorage) GetClient(_ context.Context, id string) (fosite.Client, error) {
client := PinnipedCLIOIDCClient()
if client.ID == id {
return client, nil
}
return nil, fosite.ErrNotFound
}
func (NullStorage) ClientAssertionJWTValid(_ context.Context, _ string) error {
return errNullStorageNotImplemented
}
func (NullStorage) SetClientAssertionJWT(_ context.Context, _ string, _ time.Time) error {
return errNullStorageNotImplemented
}

View File

@ -1,37 +0,0 @@
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package oidc
import (
"context"
"testing"
"github.com/ory/fosite"
"github.com/stretchr/testify/require"
)
func TestNullStorage_GetClient(t *testing.T) {
storage := NullStorage{}
client, err := storage.GetClient(context.Background(), "some-other-client")
require.Equal(t, fosite.ErrNotFound, err)
require.Zero(t, client)
client, err = storage.GetClient(context.Background(), "pinniped-cli")
require.NoError(t, err)
require.Equal(t,
&fosite.DefaultOpenIDConnectClient{
DefaultClient: &fosite.DefaultClient{
ID: "pinniped-cli",
Public: true,
RedirectURIs: []string{"http://127.0.0.1/callback"},
ResponseTypes: []string{"code"},
GrantTypes: []string{"authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:token-exchange"},
Scopes: []string{"openid", "offline_access", "profile", "email", "pinniped:request-audience"},
},
TokenEndpointAuthMethod: "none",
},
client,
)
}

View File

@ -98,20 +98,6 @@ type UpstreamStateParamData struct {
FormatVersion string `json:"v"` FormatVersion string `json:"v"`
} }
func PinnipedCLIOIDCClient() *fosite.DefaultOpenIDConnectClient {
return &fosite.DefaultOpenIDConnectClient{
DefaultClient: &fosite.DefaultClient{
ID: "pinniped-cli",
Public: true,
RedirectURIs: []string{"http://127.0.0.1/callback"},
ResponseTypes: []string{"code"},
GrantTypes: []string{"authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:token-exchange"},
Scopes: []string{coreosoidc.ScopeOpenID, coreosoidc.ScopeOfflineAccess, "profile", "email", "pinniped:request-audience"},
},
TokenEndpointAuthMethod: "none",
}
}
type TimeoutsConfiguration struct { type TimeoutsConfiguration struct {
// The length of time that our state param that we encrypt and pass to the upstream OIDC IDP should be considered // The length of time that our state param that we encrypt and pass to the upstream OIDC IDP should be considered
// valid. If a state param generated by the authorize endpoint is sent to the callback endpoint after this much // valid. If a state param generated by the authorize endpoint is sent to the callback endpoint after this much