2023-01-13 22:56:40 +00:00
|
|
|
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
|
2021-04-09 00:28:01 +00:00
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
|
|
package oidctestutil
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"crypto"
|
|
|
|
"crypto/ecdsa"
|
|
|
|
"fmt"
|
|
|
|
"net/url"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
coreosoidc "github.com/coreos/go-oidc/v3/oidc"
|
2022-04-26 22:30:39 +00:00
|
|
|
"github.com/gorilla/securecookie"
|
2021-04-09 00:28:01 +00:00
|
|
|
"github.com/ory/fosite"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"golang.org/x/oauth2"
|
|
|
|
"gopkg.in/square/go-jose.v2"
|
2022-01-18 23:34:19 +00:00
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
2021-04-09 00:28:01 +00:00
|
|
|
"k8s.io/apimachinery/pkg/labels"
|
2021-10-08 22:48:21 +00:00
|
|
|
"k8s.io/apimachinery/pkg/types"
|
2021-04-09 00:28:01 +00:00
|
|
|
"k8s.io/client-go/kubernetes/fake"
|
|
|
|
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
2022-07-21 16:26:00 +00:00
|
|
|
kubetesting "k8s.io/client-go/testing"
|
2022-06-15 20:41:22 +00:00
|
|
|
"k8s.io/utils/strings/slices"
|
2021-04-09 00:28:01 +00:00
|
|
|
|
2021-11-03 17:33:22 +00:00
|
|
|
"go.pinniped.dev/internal/authenticators"
|
2021-04-09 00:28:01 +00:00
|
|
|
"go.pinniped.dev/internal/crud"
|
|
|
|
"go.pinniped.dev/internal/fositestorage/authorizationcode"
|
|
|
|
"go.pinniped.dev/internal/fositestorage/openidconnect"
|
|
|
|
pkce2 "go.pinniped.dev/internal/fositestorage/pkce"
|
|
|
|
"go.pinniped.dev/internal/fositestoragei"
|
2023-06-13 19:26:59 +00:00
|
|
|
"go.pinniped.dev/internal/idtransform"
|
2021-04-09 00:28:01 +00:00
|
|
|
"go.pinniped.dev/internal/oidc/provider"
|
2023-05-08 21:07:38 +00:00
|
|
|
"go.pinniped.dev/internal/oidc/provider/upstreamprovider"
|
2021-10-06 22:28:13 +00:00
|
|
|
"go.pinniped.dev/internal/psession"
|
2021-04-09 00:28:01 +00:00
|
|
|
"go.pinniped.dev/internal/testutil"
|
|
|
|
"go.pinniped.dev/pkg/oidcclient/nonce"
|
|
|
|
"go.pinniped.dev/pkg/oidcclient/oidctypes"
|
|
|
|
"go.pinniped.dev/pkg/oidcclient/pkce"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Test helpers for the OIDC package.
|
|
|
|
|
2021-04-27 19:43:09 +00:00
|
|
|
// ExchangeAuthcodeAndValidateTokenArgs is used to spy on calls to
|
2021-04-09 00:28:01 +00:00
|
|
|
// TestUpstreamOIDCIdentityProvider.ExchangeAuthcodeAndValidateTokensFunc().
|
|
|
|
type ExchangeAuthcodeAndValidateTokenArgs struct {
|
|
|
|
Ctx context.Context
|
|
|
|
Authcode string
|
|
|
|
PKCECodeVerifier pkce.Code
|
|
|
|
ExpectedIDTokenNonce nonce.Nonce
|
|
|
|
RedirectURI string
|
|
|
|
}
|
|
|
|
|
2021-08-13 00:53:14 +00:00
|
|
|
// PasswordCredentialsGrantAndValidateTokensArgs is used to spy on calls to
|
|
|
|
// TestUpstreamOIDCIdentityProvider.PasswordCredentialsGrantAndValidateTokensFunc().
|
|
|
|
type PasswordCredentialsGrantAndValidateTokensArgs struct {
|
|
|
|
Ctx context.Context
|
|
|
|
Username string
|
|
|
|
Password string
|
|
|
|
}
|
|
|
|
|
2021-10-13 19:31:20 +00:00
|
|
|
// PerformRefreshArgs is used to spy on calls to
|
|
|
|
// TestUpstreamOIDCIdentityProvider.PerformRefreshFunc().
|
|
|
|
type PerformRefreshArgs struct {
|
2021-10-25 21:25:43 +00:00
|
|
|
Ctx context.Context
|
|
|
|
RefreshToken string
|
|
|
|
DN string
|
|
|
|
ExpectedUsername string
|
|
|
|
ExpectedSubject string
|
2021-10-13 19:31:20 +00:00
|
|
|
}
|
|
|
|
|
2021-12-03 21:44:24 +00:00
|
|
|
// RevokeTokenArgs is used to spy on calls to
|
|
|
|
// TestUpstreamOIDCIdentityProvider.RevokeTokenArgsFunc().
|
|
|
|
type RevokeTokenArgs struct {
|
|
|
|
Ctx context.Context
|
|
|
|
Token string
|
2023-05-08 21:07:38 +00:00
|
|
|
TokenType upstreamprovider.RevocableTokenType
|
2021-10-22 21:32:26 +00:00
|
|
|
}
|
|
|
|
|
2022-01-13 02:05:10 +00:00
|
|
|
// ValidateTokenAndMergeWithUserInfoArgs is used to spy on calls to
|
|
|
|
// TestUpstreamOIDCIdentityProvider.ValidateTokenAndMergeWithUserInfoFunc().
|
|
|
|
type ValidateTokenAndMergeWithUserInfoArgs struct {
|
2021-10-13 19:31:20 +00:00
|
|
|
Ctx context.Context
|
|
|
|
Tok *oauth2.Token
|
|
|
|
ExpectedIDTokenNonce nonce.Nonce
|
2022-01-13 02:05:10 +00:00
|
|
|
RequireIDToken bool
|
|
|
|
RequireUserInfo bool
|
2021-10-13 19:31:20 +00:00
|
|
|
}
|
|
|
|
|
2021-12-14 00:40:13 +00:00
|
|
|
type ValidateRefreshArgs struct {
|
|
|
|
Ctx context.Context
|
|
|
|
Tok *oauth2.Token
|
2023-05-08 21:07:38 +00:00
|
|
|
StoredAttributes upstreamprovider.RefreshAttributes
|
2021-10-13 19:31:20 +00:00
|
|
|
}
|
|
|
|
|
2023-06-13 19:26:59 +00:00
|
|
|
func NewTestUpstreamLDAPIdentityProviderBuilder() *TestUpstreamLDAPIdentityProviderBuilder {
|
|
|
|
return &TestUpstreamLDAPIdentityProviderBuilder{}
|
|
|
|
}
|
|
|
|
|
|
|
|
type TestUpstreamLDAPIdentityProviderBuilder struct {
|
|
|
|
name string
|
|
|
|
resourceUID types.UID
|
|
|
|
url *url.URL
|
|
|
|
authenticateFunc func(ctx context.Context, username, password string) (*authenticators.Response, bool, error)
|
|
|
|
performRefreshCallCount int
|
|
|
|
performRefreshArgs []*PerformRefreshArgs
|
|
|
|
performRefreshErr error
|
|
|
|
performRefreshGroups []string
|
|
|
|
displayNameForFederationDomain string
|
|
|
|
transformsForFederationDomain *idtransform.TransformationPipeline
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TestUpstreamLDAPIdentityProviderBuilder) WithName(name string) *TestUpstreamLDAPIdentityProviderBuilder {
|
|
|
|
t.name = name
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TestUpstreamLDAPIdentityProviderBuilder) WithResourceUID(uid types.UID) *TestUpstreamLDAPIdentityProviderBuilder {
|
|
|
|
t.resourceUID = uid
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TestUpstreamLDAPIdentityProviderBuilder) WithURL(url *url.URL) *TestUpstreamLDAPIdentityProviderBuilder {
|
|
|
|
t.url = url
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TestUpstreamLDAPIdentityProviderBuilder) WithAuthenticateFunc(f func(ctx context.Context, username, password string) (*authenticators.Response, bool, error)) *TestUpstreamLDAPIdentityProviderBuilder {
|
|
|
|
t.authenticateFunc = f
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TestUpstreamLDAPIdentityProviderBuilder) WithPerformRefreshCallCount(count int) *TestUpstreamLDAPIdentityProviderBuilder {
|
|
|
|
t.performRefreshCallCount = count
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TestUpstreamLDAPIdentityProviderBuilder) WithPerformRefreshArgs(args []*PerformRefreshArgs) *TestUpstreamLDAPIdentityProviderBuilder {
|
|
|
|
t.performRefreshArgs = args
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TestUpstreamLDAPIdentityProviderBuilder) WithPerformRefreshErr(err error) *TestUpstreamLDAPIdentityProviderBuilder {
|
|
|
|
t.performRefreshErr = err
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TestUpstreamLDAPIdentityProviderBuilder) WithPerformRefreshGroups(groups []string) *TestUpstreamLDAPIdentityProviderBuilder {
|
|
|
|
t.performRefreshGroups = groups
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TestUpstreamLDAPIdentityProviderBuilder) WithDisplayNameForFederationDomain(displayName string) *TestUpstreamLDAPIdentityProviderBuilder {
|
|
|
|
t.displayNameForFederationDomain = displayName
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TestUpstreamLDAPIdentityProviderBuilder) WithTransformsForFederationDomain(transforms *idtransform.TransformationPipeline) *TestUpstreamLDAPIdentityProviderBuilder {
|
|
|
|
t.transformsForFederationDomain = transforms
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TestUpstreamLDAPIdentityProviderBuilder) Build() *TestUpstreamLDAPIdentityProvider {
|
|
|
|
if t.displayNameForFederationDomain == "" {
|
|
|
|
// default it to the CR name
|
|
|
|
t.displayNameForFederationDomain = t.name
|
|
|
|
}
|
|
|
|
if t.transformsForFederationDomain == nil {
|
|
|
|
// default to an empty pipeline
|
|
|
|
t.transformsForFederationDomain = idtransform.NewTransformationPipeline()
|
|
|
|
}
|
|
|
|
return &TestUpstreamLDAPIdentityProvider{
|
|
|
|
Name: t.name,
|
|
|
|
ResourceUID: t.resourceUID,
|
|
|
|
URL: t.url,
|
|
|
|
AuthenticateFunc: t.authenticateFunc,
|
|
|
|
performRefreshCallCount: t.performRefreshCallCount,
|
|
|
|
performRefreshArgs: t.performRefreshArgs,
|
|
|
|
PerformRefreshErr: t.performRefreshErr,
|
|
|
|
PerformRefreshGroups: t.performRefreshGroups,
|
|
|
|
DisplayNameForFederationDomain: t.displayNameForFederationDomain,
|
|
|
|
TransformsForFederationDomain: t.transformsForFederationDomain,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-09 00:28:01 +00:00
|
|
|
type TestUpstreamLDAPIdentityProvider struct {
|
2023-06-13 19:26:59 +00:00
|
|
|
Name string
|
|
|
|
ResourceUID types.UID
|
|
|
|
URL *url.URL
|
|
|
|
AuthenticateFunc func(ctx context.Context, username, password string) (*authenticators.Response, bool, error)
|
|
|
|
performRefreshCallCount int
|
|
|
|
performRefreshArgs []*PerformRefreshArgs
|
|
|
|
PerformRefreshErr error
|
|
|
|
PerformRefreshGroups []string
|
|
|
|
DisplayNameForFederationDomain string
|
|
|
|
TransformsForFederationDomain *idtransform.TransformationPipeline
|
2021-04-09 00:28:01 +00:00
|
|
|
}
|
|
|
|
|
2023-05-08 21:07:38 +00:00
|
|
|
var _ upstreamprovider.UpstreamLDAPIdentityProviderI = &TestUpstreamLDAPIdentityProvider{}
|
2021-05-27 00:04:20 +00:00
|
|
|
|
2021-10-08 22:48:21 +00:00
|
|
|
func (u *TestUpstreamLDAPIdentityProvider) GetResourceUID() types.UID {
|
|
|
|
return u.ResourceUID
|
|
|
|
}
|
|
|
|
|
2021-04-09 00:28:01 +00:00
|
|
|
func (u *TestUpstreamLDAPIdentityProvider) GetName() string {
|
|
|
|
return u.Name
|
|
|
|
}
|
|
|
|
|
2022-06-22 17:58:08 +00:00
|
|
|
func (u *TestUpstreamLDAPIdentityProvider) AuthenticateUser(ctx context.Context, username, password string, grantedScopes []string) (*authenticators.Response, bool, error) {
|
2021-04-09 00:28:01 +00:00
|
|
|
return u.AuthenticateFunc(ctx, username, password)
|
|
|
|
}
|
|
|
|
|
2021-05-27 00:04:20 +00:00
|
|
|
func (u *TestUpstreamLDAPIdentityProvider) GetURL() *url.URL {
|
2021-04-09 00:28:01 +00:00
|
|
|
return u.URL
|
|
|
|
}
|
|
|
|
|
2023-05-08 21:07:38 +00:00
|
|
|
func (u *TestUpstreamLDAPIdentityProvider) PerformRefresh(ctx context.Context, storedRefreshAttributes upstreamprovider.RefreshAttributes) ([]string, error) {
|
2021-10-22 20:57:30 +00:00
|
|
|
if u.performRefreshArgs == nil {
|
|
|
|
u.performRefreshArgs = make([]*PerformRefreshArgs, 0)
|
|
|
|
}
|
|
|
|
u.performRefreshCallCount++
|
|
|
|
u.performRefreshArgs = append(u.performRefreshArgs, &PerformRefreshArgs{
|
2021-10-25 21:25:43 +00:00
|
|
|
Ctx: ctx,
|
2021-10-28 19:00:56 +00:00
|
|
|
DN: storedRefreshAttributes.DN,
|
|
|
|
ExpectedUsername: storedRefreshAttributes.Username,
|
|
|
|
ExpectedSubject: storedRefreshAttributes.Subject,
|
2021-10-22 20:57:30 +00:00
|
|
|
})
|
|
|
|
if u.PerformRefreshErr != nil {
|
2022-01-26 00:19:56 +00:00
|
|
|
return nil, u.PerformRefreshErr
|
2021-10-22 20:57:30 +00:00
|
|
|
}
|
2022-01-26 00:19:56 +00:00
|
|
|
return u.PerformRefreshGroups, nil
|
2021-10-22 20:57:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (u *TestUpstreamLDAPIdentityProvider) PerformRefreshCallCount() int {
|
|
|
|
return u.performRefreshCallCount
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *TestUpstreamLDAPIdentityProvider) PerformRefreshArgs(call int) *PerformRefreshArgs {
|
|
|
|
if u.performRefreshArgs == nil {
|
|
|
|
u.performRefreshArgs = make([]*PerformRefreshArgs, 0)
|
|
|
|
}
|
|
|
|
return u.performRefreshArgs[call]
|
|
|
|
}
|
|
|
|
|
2021-04-09 00:28:01 +00:00
|
|
|
type TestUpstreamOIDCIdentityProvider struct {
|
2023-06-13 19:26:59 +00:00
|
|
|
Name string
|
|
|
|
ClientID string
|
|
|
|
ResourceUID types.UID
|
|
|
|
AuthorizationURL url.URL
|
|
|
|
UserInfoURL bool
|
|
|
|
RevocationURL *url.URL
|
|
|
|
UsernameClaim string
|
|
|
|
GroupsClaim string
|
|
|
|
Scopes []string
|
|
|
|
AdditionalAuthcodeParams map[string]string
|
|
|
|
AdditionalClaimMappings map[string]string
|
|
|
|
AllowPasswordGrant bool
|
|
|
|
DisplayNameForFederationDomain string
|
|
|
|
TransformsForFederationDomain *idtransform.TransformationPipeline
|
2021-08-13 00:53:14 +00:00
|
|
|
|
2021-04-09 00:28:01 +00:00
|
|
|
ExchangeAuthcodeAndValidateTokensFunc func(
|
|
|
|
ctx context.Context,
|
|
|
|
authcode string,
|
|
|
|
pkceCodeVerifier pkce.Code,
|
|
|
|
expectedIDTokenNonce nonce.Nonce,
|
|
|
|
) (*oidctypes.Token, error)
|
|
|
|
|
2021-08-13 00:53:14 +00:00
|
|
|
PasswordCredentialsGrantAndValidateTokensFunc func(
|
|
|
|
ctx context.Context,
|
|
|
|
username string,
|
|
|
|
password string,
|
|
|
|
) (*oidctypes.Token, error)
|
|
|
|
|
2021-10-13 19:31:20 +00:00
|
|
|
PerformRefreshFunc func(ctx context.Context, refreshToken string) (*oauth2.Token, error)
|
|
|
|
|
2023-05-08 21:07:38 +00:00
|
|
|
RevokeTokenFunc func(ctx context.Context, refreshToken string, tokenType upstreamprovider.RevocableTokenType) error
|
2021-10-22 21:32:26 +00:00
|
|
|
|
2022-01-13 02:05:10 +00:00
|
|
|
ValidateTokenAndMergeWithUserInfoFunc func(ctx context.Context, tok *oauth2.Token, expectedIDTokenNonce nonce.Nonce) (*oidctypes.Token, error)
|
2021-10-13 19:31:20 +00:00
|
|
|
|
2021-08-13 00:53:14 +00:00
|
|
|
exchangeAuthcodeAndValidateTokensCallCount int
|
|
|
|
exchangeAuthcodeAndValidateTokensArgs []*ExchangeAuthcodeAndValidateTokenArgs
|
|
|
|
passwordCredentialsGrantAndValidateTokensCallCount int
|
|
|
|
passwordCredentialsGrantAndValidateTokensArgs []*PasswordCredentialsGrantAndValidateTokensArgs
|
2021-10-13 19:31:20 +00:00
|
|
|
performRefreshCallCount int
|
|
|
|
performRefreshArgs []*PerformRefreshArgs
|
2021-12-03 21:44:24 +00:00
|
|
|
revokeTokenCallCount int
|
|
|
|
revokeTokenArgs []*RevokeTokenArgs
|
2022-01-13 02:05:10 +00:00
|
|
|
validateTokenAndMergeWithUserInfoCallCount int
|
|
|
|
validateTokenAndMergeWithUserInfoArgs []*ValidateTokenAndMergeWithUserInfoArgs
|
2021-04-09 00:28:01 +00:00
|
|
|
}
|
|
|
|
|
2023-05-08 21:07:38 +00:00
|
|
|
var _ upstreamprovider.UpstreamOIDCIdentityProviderI = &TestUpstreamOIDCIdentityProvider{}
|
2021-10-08 22:48:21 +00:00
|
|
|
|
|
|
|
func (u *TestUpstreamOIDCIdentityProvider) GetResourceUID() types.UID {
|
|
|
|
return u.ResourceUID
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *TestUpstreamOIDCIdentityProvider) GetAdditionalAuthcodeParams() map[string]string {
|
|
|
|
return u.AdditionalAuthcodeParams
|
|
|
|
}
|
|
|
|
|
2022-09-20 21:54:10 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProvider) GetAdditionalClaimMappings() map[string]string {
|
|
|
|
return u.AdditionalClaimMappings
|
|
|
|
}
|
|
|
|
|
2021-04-09 00:28:01 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProvider) GetName() string {
|
|
|
|
return u.Name
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *TestUpstreamOIDCIdentityProvider) GetClientID() string {
|
|
|
|
return u.ClientID
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *TestUpstreamOIDCIdentityProvider) GetAuthorizationURL() *url.URL {
|
|
|
|
return &u.AuthorizationURL
|
|
|
|
}
|
|
|
|
|
2022-01-11 23:40:38 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProvider) HasUserInfoURL() bool {
|
|
|
|
return u.UserInfoURL
|
|
|
|
}
|
|
|
|
|
2021-10-22 21:32:26 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProvider) GetRevocationURL() *url.URL {
|
|
|
|
return u.RevocationURL
|
|
|
|
}
|
|
|
|
|
2021-04-09 00:28:01 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProvider) GetScopes() []string {
|
|
|
|
return u.Scopes
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *TestUpstreamOIDCIdentityProvider) GetUsernameClaim() string {
|
|
|
|
return u.UsernameClaim
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *TestUpstreamOIDCIdentityProvider) GetGroupsClaim() string {
|
|
|
|
return u.GroupsClaim
|
|
|
|
}
|
|
|
|
|
2021-08-12 17:00:18 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProvider) AllowsPasswordGrant() bool {
|
|
|
|
return u.AllowPasswordGrant
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *TestUpstreamOIDCIdentityProvider) PasswordCredentialsGrantAndValidateTokens(ctx context.Context, username, password string) (*oidctypes.Token, error) {
|
2021-08-13 00:53:14 +00:00
|
|
|
u.passwordCredentialsGrantAndValidateTokensCallCount++
|
|
|
|
u.passwordCredentialsGrantAndValidateTokensArgs = append(u.passwordCredentialsGrantAndValidateTokensArgs, &PasswordCredentialsGrantAndValidateTokensArgs{
|
|
|
|
Ctx: ctx,
|
|
|
|
Username: username,
|
|
|
|
Password: password,
|
|
|
|
})
|
|
|
|
return u.PasswordCredentialsGrantAndValidateTokensFunc(ctx, username, password)
|
2021-08-12 17:00:18 +00:00
|
|
|
}
|
|
|
|
|
2021-04-09 00:28:01 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProvider) ExchangeAuthcodeAndValidateTokens(
|
|
|
|
ctx context.Context,
|
|
|
|
authcode string,
|
|
|
|
pkceCodeVerifier pkce.Code,
|
|
|
|
expectedIDTokenNonce nonce.Nonce,
|
|
|
|
redirectURI string,
|
|
|
|
) (*oidctypes.Token, error) {
|
|
|
|
if u.exchangeAuthcodeAndValidateTokensArgs == nil {
|
|
|
|
u.exchangeAuthcodeAndValidateTokensArgs = make([]*ExchangeAuthcodeAndValidateTokenArgs, 0)
|
|
|
|
}
|
|
|
|
u.exchangeAuthcodeAndValidateTokensCallCount++
|
|
|
|
u.exchangeAuthcodeAndValidateTokensArgs = append(u.exchangeAuthcodeAndValidateTokensArgs, &ExchangeAuthcodeAndValidateTokenArgs{
|
|
|
|
Ctx: ctx,
|
|
|
|
Authcode: authcode,
|
|
|
|
PKCECodeVerifier: pkceCodeVerifier,
|
|
|
|
ExpectedIDTokenNonce: expectedIDTokenNonce,
|
|
|
|
RedirectURI: redirectURI,
|
|
|
|
})
|
|
|
|
return u.ExchangeAuthcodeAndValidateTokensFunc(ctx, authcode, pkceCodeVerifier, expectedIDTokenNonce)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *TestUpstreamOIDCIdentityProvider) ExchangeAuthcodeAndValidateTokensCallCount() int {
|
|
|
|
return u.exchangeAuthcodeAndValidateTokensCallCount
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *TestUpstreamOIDCIdentityProvider) ExchangeAuthcodeAndValidateTokensArgs(call int) *ExchangeAuthcodeAndValidateTokenArgs {
|
|
|
|
if u.exchangeAuthcodeAndValidateTokensArgs == nil {
|
|
|
|
u.exchangeAuthcodeAndValidateTokensArgs = make([]*ExchangeAuthcodeAndValidateTokenArgs, 0)
|
|
|
|
}
|
|
|
|
return u.exchangeAuthcodeAndValidateTokensArgs[call]
|
|
|
|
}
|
|
|
|
|
2021-10-13 19:31:20 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProvider) PerformRefresh(ctx context.Context, refreshToken string) (*oauth2.Token, error) {
|
|
|
|
if u.performRefreshArgs == nil {
|
|
|
|
u.performRefreshArgs = make([]*PerformRefreshArgs, 0)
|
|
|
|
}
|
|
|
|
u.performRefreshCallCount++
|
|
|
|
u.performRefreshArgs = append(u.performRefreshArgs, &PerformRefreshArgs{
|
|
|
|
Ctx: ctx,
|
|
|
|
RefreshToken: refreshToken,
|
|
|
|
})
|
|
|
|
return u.PerformRefreshFunc(ctx, refreshToken)
|
|
|
|
}
|
|
|
|
|
2023-05-08 21:07:38 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProvider) RevokeToken(ctx context.Context, token string, tokenType upstreamprovider.RevocableTokenType) error {
|
2021-12-03 21:44:24 +00:00
|
|
|
if u.revokeTokenArgs == nil {
|
|
|
|
u.revokeTokenArgs = make([]*RevokeTokenArgs, 0)
|
2021-10-22 21:32:26 +00:00
|
|
|
}
|
2021-12-03 21:44:24 +00:00
|
|
|
u.revokeTokenCallCount++
|
|
|
|
u.revokeTokenArgs = append(u.revokeTokenArgs, &RevokeTokenArgs{
|
|
|
|
Ctx: ctx,
|
|
|
|
Token: token,
|
|
|
|
TokenType: tokenType,
|
2021-10-22 21:32:26 +00:00
|
|
|
})
|
2021-12-03 21:44:24 +00:00
|
|
|
return u.RevokeTokenFunc(ctx, token, tokenType)
|
2021-10-22 21:32:26 +00:00
|
|
|
}
|
|
|
|
|
2021-10-13 19:31:20 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProvider) PerformRefreshCallCount() int {
|
|
|
|
return u.performRefreshCallCount
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *TestUpstreamOIDCIdentityProvider) PerformRefreshArgs(call int) *PerformRefreshArgs {
|
|
|
|
if u.performRefreshArgs == nil {
|
|
|
|
u.performRefreshArgs = make([]*PerformRefreshArgs, 0)
|
|
|
|
}
|
|
|
|
return u.performRefreshArgs[call]
|
|
|
|
}
|
|
|
|
|
2021-12-03 21:44:24 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProvider) RevokeTokenCallCount() int {
|
2021-10-22 21:32:26 +00:00
|
|
|
return u.performRefreshCallCount
|
|
|
|
}
|
|
|
|
|
2021-12-03 21:44:24 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProvider) RevokeTokenArgs(call int) *RevokeTokenArgs {
|
|
|
|
if u.revokeTokenArgs == nil {
|
|
|
|
u.revokeTokenArgs = make([]*RevokeTokenArgs, 0)
|
2021-10-22 21:32:26 +00:00
|
|
|
}
|
2021-12-03 21:44:24 +00:00
|
|
|
return u.revokeTokenArgs[call]
|
2021-10-22 21:32:26 +00:00
|
|
|
}
|
|
|
|
|
2022-01-13 02:05:10 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProvider) ValidateTokenAndMergeWithUserInfo(ctx context.Context, tok *oauth2.Token, expectedIDTokenNonce nonce.Nonce, requireIDToken bool, requireUserInfo bool) (*oidctypes.Token, error) {
|
|
|
|
if u.validateTokenAndMergeWithUserInfoArgs == nil {
|
|
|
|
u.validateTokenAndMergeWithUserInfoArgs = make([]*ValidateTokenAndMergeWithUserInfoArgs, 0)
|
2021-10-13 19:31:20 +00:00
|
|
|
}
|
2022-01-13 02:05:10 +00:00
|
|
|
u.validateTokenAndMergeWithUserInfoCallCount++
|
|
|
|
u.validateTokenAndMergeWithUserInfoArgs = append(u.validateTokenAndMergeWithUserInfoArgs, &ValidateTokenAndMergeWithUserInfoArgs{
|
2021-10-13 19:31:20 +00:00
|
|
|
Ctx: ctx,
|
|
|
|
Tok: tok,
|
|
|
|
ExpectedIDTokenNonce: expectedIDTokenNonce,
|
2022-01-13 02:05:10 +00:00
|
|
|
RequireIDToken: requireIDToken,
|
|
|
|
RequireUserInfo: requireUserInfo,
|
2021-10-13 19:31:20 +00:00
|
|
|
})
|
2022-01-13 02:05:10 +00:00
|
|
|
return u.ValidateTokenAndMergeWithUserInfoFunc(ctx, tok, expectedIDTokenNonce)
|
2021-10-13 19:31:20 +00:00
|
|
|
}
|
|
|
|
|
2022-01-13 02:05:10 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProvider) ValidateTokenAndMergeWithUserInfoCallCount() int {
|
|
|
|
return u.validateTokenAndMergeWithUserInfoCallCount
|
2021-10-13 19:31:20 +00:00
|
|
|
}
|
|
|
|
|
2022-01-13 02:05:10 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProvider) ValidateTokenAndMergeWithUserInfoArgs(call int) *ValidateTokenAndMergeWithUserInfoArgs {
|
|
|
|
if u.validateTokenAndMergeWithUserInfoArgs == nil {
|
|
|
|
u.validateTokenAndMergeWithUserInfoArgs = make([]*ValidateTokenAndMergeWithUserInfoArgs, 0)
|
2021-10-13 19:31:20 +00:00
|
|
|
}
|
2022-01-13 02:05:10 +00:00
|
|
|
return u.validateTokenAndMergeWithUserInfoArgs[call]
|
2021-04-09 00:28:01 +00:00
|
|
|
}
|
|
|
|
|
2023-06-13 19:26:59 +00:00
|
|
|
type TestFederationDomainIdentityProvidersListerFinder struct {
|
|
|
|
upstreamOIDCIdentityProviders []*TestUpstreamOIDCIdentityProvider
|
|
|
|
upstreamLDAPIdentityProviders []*TestUpstreamLDAPIdentityProvider
|
|
|
|
upstreamActiveDirectoryIdentityProviders []*TestUpstreamLDAPIdentityProvider
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TestFederationDomainIdentityProvidersListerFinder) GetOIDCIdentityProviders() []*provider.FederationDomainResolvedOIDCIdentityProvider {
|
|
|
|
fdIDPs := make([]*provider.FederationDomainResolvedOIDCIdentityProvider, len(t.upstreamOIDCIdentityProviders))
|
|
|
|
for i, testIDP := range t.upstreamOIDCIdentityProviders {
|
|
|
|
fdIDP := &provider.FederationDomainResolvedOIDCIdentityProvider{
|
|
|
|
DisplayName: testIDP.DisplayNameForFederationDomain,
|
|
|
|
Provider: testIDP,
|
|
|
|
SessionProviderType: psession.ProviderTypeOIDC,
|
|
|
|
Transforms: testIDP.TransformsForFederationDomain,
|
|
|
|
}
|
|
|
|
fdIDPs[i] = fdIDP
|
|
|
|
}
|
|
|
|
return fdIDPs
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TestFederationDomainIdentityProvidersListerFinder) GetLDAPIdentityProviders() []*provider.FederationDomainResolvedLDAPIdentityProvider {
|
|
|
|
fdIDPs := make([]*provider.FederationDomainResolvedLDAPIdentityProvider, len(t.upstreamLDAPIdentityProviders))
|
|
|
|
for i, testIDP := range t.upstreamLDAPIdentityProviders {
|
|
|
|
fdIDP := &provider.FederationDomainResolvedLDAPIdentityProvider{
|
|
|
|
DisplayName: testIDP.DisplayNameForFederationDomain,
|
|
|
|
Provider: testIDP,
|
|
|
|
SessionProviderType: psession.ProviderTypeLDAP,
|
|
|
|
Transforms: testIDP.TransformsForFederationDomain,
|
|
|
|
}
|
|
|
|
fdIDPs[i] = fdIDP
|
|
|
|
}
|
|
|
|
return fdIDPs
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TestFederationDomainIdentityProvidersListerFinder) GetActiveDirectoryIdentityProviders() []*provider.FederationDomainResolvedLDAPIdentityProvider {
|
|
|
|
fdIDPs := make([]*provider.FederationDomainResolvedLDAPIdentityProvider, len(t.upstreamActiveDirectoryIdentityProviders))
|
|
|
|
for i, testIDP := range t.upstreamActiveDirectoryIdentityProviders {
|
|
|
|
fdIDP := &provider.FederationDomainResolvedLDAPIdentityProvider{
|
|
|
|
DisplayName: testIDP.DisplayNameForFederationDomain,
|
|
|
|
Provider: testIDP,
|
|
|
|
SessionProviderType: psession.ProviderTypeActiveDirectory,
|
|
|
|
Transforms: testIDP.TransformsForFederationDomain,
|
|
|
|
}
|
|
|
|
fdIDPs[i] = fdIDP
|
|
|
|
}
|
|
|
|
return fdIDPs
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TestFederationDomainIdentityProvidersListerFinder) FindDefaultIDP() (*provider.FederationDomainResolvedOIDCIdentityProvider, *provider.FederationDomainResolvedLDAPIdentityProvider, error) {
|
|
|
|
return nil, nil, fmt.Errorf("TODO: implement me") // TODO
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TestFederationDomainIdentityProvidersListerFinder) FindUpstreamIDPByDisplayName(upstreamIDPDisplayName string) (*provider.FederationDomainResolvedOIDCIdentityProvider, *provider.FederationDomainResolvedLDAPIdentityProvider, error) {
|
|
|
|
for _, testIDP := range t.upstreamOIDCIdentityProviders {
|
|
|
|
if upstreamIDPDisplayName == testIDP.DisplayNameForFederationDomain {
|
|
|
|
return &provider.FederationDomainResolvedOIDCIdentityProvider{
|
|
|
|
DisplayName: testIDP.DisplayNameForFederationDomain,
|
|
|
|
Provider: testIDP,
|
|
|
|
SessionProviderType: psession.ProviderTypeOIDC,
|
|
|
|
Transforms: testIDP.TransformsForFederationDomain,
|
|
|
|
}, nil, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, testIDP := range t.upstreamLDAPIdentityProviders {
|
|
|
|
if upstreamIDPDisplayName == testIDP.DisplayNameForFederationDomain {
|
|
|
|
return nil, &provider.FederationDomainResolvedLDAPIdentityProvider{
|
|
|
|
DisplayName: testIDP.DisplayNameForFederationDomain,
|
|
|
|
Provider: testIDP,
|
|
|
|
SessionProviderType: psession.ProviderTypeLDAP,
|
|
|
|
Transforms: testIDP.TransformsForFederationDomain,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, testIDP := range t.upstreamActiveDirectoryIdentityProviders {
|
|
|
|
if upstreamIDPDisplayName == testIDP.DisplayNameForFederationDomain {
|
|
|
|
return nil, &provider.FederationDomainResolvedLDAPIdentityProvider{
|
|
|
|
DisplayName: testIDP.DisplayNameForFederationDomain,
|
|
|
|
Provider: testIDP,
|
|
|
|
SessionProviderType: psession.ProviderTypeActiveDirectory,
|
|
|
|
Transforms: testIDP.TransformsForFederationDomain,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, nil, fmt.Errorf("did not find IDP with name %q", upstreamIDPDisplayName)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TestFederationDomainIdentityProvidersListerFinder) SetOIDCIdentityProviders(providers []*TestUpstreamOIDCIdentityProvider) {
|
|
|
|
t.upstreamOIDCIdentityProviders = providers
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TestFederationDomainIdentityProvidersListerFinder) SetLDAPIdentityProviders(providers []*TestUpstreamLDAPIdentityProvider) {
|
|
|
|
t.upstreamLDAPIdentityProviders = providers
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TestFederationDomainIdentityProvidersListerFinder) SetActiveDirectoryIdentityProviders(providers []*TestUpstreamLDAPIdentityProvider) {
|
|
|
|
t.upstreamActiveDirectoryIdentityProviders = providers
|
|
|
|
}
|
|
|
|
|
2021-04-09 00:28:01 +00:00
|
|
|
type UpstreamIDPListerBuilder struct {
|
2021-07-02 22:30:27 +00:00
|
|
|
upstreamOIDCIdentityProviders []*TestUpstreamOIDCIdentityProvider
|
|
|
|
upstreamLDAPIdentityProviders []*TestUpstreamLDAPIdentityProvider
|
|
|
|
upstreamActiveDirectoryIdentityProviders []*TestUpstreamLDAPIdentityProvider
|
2021-04-09 00:28:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (b *UpstreamIDPListerBuilder) WithOIDC(upstreamOIDCIdentityProviders ...*TestUpstreamOIDCIdentityProvider) *UpstreamIDPListerBuilder {
|
|
|
|
b.upstreamOIDCIdentityProviders = append(b.upstreamOIDCIdentityProviders, upstreamOIDCIdentityProviders...)
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *UpstreamIDPListerBuilder) WithLDAP(upstreamLDAPIdentityProviders ...*TestUpstreamLDAPIdentityProvider) *UpstreamIDPListerBuilder {
|
|
|
|
b.upstreamLDAPIdentityProviders = append(b.upstreamLDAPIdentityProviders, upstreamLDAPIdentityProviders...)
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
2021-07-02 22:30:27 +00:00
|
|
|
func (b *UpstreamIDPListerBuilder) WithActiveDirectory(upstreamActiveDirectoryIdentityProviders ...*TestUpstreamLDAPIdentityProvider) *UpstreamIDPListerBuilder {
|
|
|
|
b.upstreamActiveDirectoryIdentityProviders = append(b.upstreamActiveDirectoryIdentityProviders, upstreamActiveDirectoryIdentityProviders...)
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
2023-06-13 19:26:59 +00:00
|
|
|
func (b *UpstreamIDPListerBuilder) BuildFederationDomainIdentityProvidersListerFinder() *TestFederationDomainIdentityProvidersListerFinder {
|
|
|
|
return &TestFederationDomainIdentityProvidersListerFinder{
|
|
|
|
upstreamOIDCIdentityProviders: b.upstreamOIDCIdentityProviders,
|
|
|
|
upstreamLDAPIdentityProviders: b.upstreamLDAPIdentityProviders,
|
|
|
|
upstreamActiveDirectoryIdentityProviders: b.upstreamActiveDirectoryIdentityProviders,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *UpstreamIDPListerBuilder) BuildDynamicUpstreamIDPProvider() provider.DynamicUpstreamIDPProvider {
|
2021-04-09 00:28:01 +00:00
|
|
|
idpProvider := provider.NewDynamicUpstreamIDPProvider()
|
|
|
|
|
2023-05-08 21:07:38 +00:00
|
|
|
oidcUpstreams := make([]upstreamprovider.UpstreamOIDCIdentityProviderI, len(b.upstreamOIDCIdentityProviders))
|
2021-04-09 00:28:01 +00:00
|
|
|
for i := range b.upstreamOIDCIdentityProviders {
|
2023-05-08 21:07:38 +00:00
|
|
|
oidcUpstreams[i] = upstreamprovider.UpstreamOIDCIdentityProviderI(b.upstreamOIDCIdentityProviders[i])
|
2021-04-09 00:28:01 +00:00
|
|
|
}
|
|
|
|
idpProvider.SetOIDCIdentityProviders(oidcUpstreams)
|
|
|
|
|
2023-05-08 21:07:38 +00:00
|
|
|
ldapUpstreams := make([]upstreamprovider.UpstreamLDAPIdentityProviderI, len(b.upstreamLDAPIdentityProviders))
|
2021-04-09 00:28:01 +00:00
|
|
|
for i := range b.upstreamLDAPIdentityProviders {
|
2023-05-08 21:07:38 +00:00
|
|
|
ldapUpstreams[i] = upstreamprovider.UpstreamLDAPIdentityProviderI(b.upstreamLDAPIdentityProviders[i])
|
2021-04-09 00:28:01 +00:00
|
|
|
}
|
|
|
|
idpProvider.SetLDAPIdentityProviders(ldapUpstreams)
|
|
|
|
|
2023-05-08 21:07:38 +00:00
|
|
|
adUpstreams := make([]upstreamprovider.UpstreamLDAPIdentityProviderI, len(b.upstreamActiveDirectoryIdentityProviders))
|
2021-07-02 22:30:27 +00:00
|
|
|
for i := range b.upstreamActiveDirectoryIdentityProviders {
|
2023-05-08 21:07:38 +00:00
|
|
|
adUpstreams[i] = upstreamprovider.UpstreamLDAPIdentityProviderI(b.upstreamActiveDirectoryIdentityProviders[i])
|
2021-07-02 22:30:27 +00:00
|
|
|
}
|
|
|
|
idpProvider.SetActiveDirectoryIdentityProviders(adUpstreams)
|
|
|
|
|
2021-04-09 00:28:01 +00:00
|
|
|
return idpProvider
|
|
|
|
}
|
|
|
|
|
2021-08-13 00:53:14 +00:00
|
|
|
func (b *UpstreamIDPListerBuilder) RequireExactlyOneCallToPasswordCredentialsGrantAndValidateTokens(
|
|
|
|
t *testing.T,
|
|
|
|
expectedPerformedByUpstreamName string,
|
|
|
|
expectedArgs *PasswordCredentialsGrantAndValidateTokensArgs,
|
|
|
|
) {
|
|
|
|
t.Helper()
|
|
|
|
var actualArgs *PasswordCredentialsGrantAndValidateTokensArgs
|
|
|
|
var actualNameOfUpstreamWhichMadeCall string
|
|
|
|
actualCallCountAcrossAllOIDCUpstreams := 0
|
|
|
|
for _, upstreamOIDC := range b.upstreamOIDCIdentityProviders {
|
|
|
|
callCountOnThisUpstream := upstreamOIDC.passwordCredentialsGrantAndValidateTokensCallCount
|
|
|
|
actualCallCountAcrossAllOIDCUpstreams += callCountOnThisUpstream
|
|
|
|
if callCountOnThisUpstream == 1 {
|
|
|
|
actualNameOfUpstreamWhichMadeCall = upstreamOIDC.Name
|
|
|
|
actualArgs = upstreamOIDC.passwordCredentialsGrantAndValidateTokensArgs[0]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
require.Equal(t, 1, actualCallCountAcrossAllOIDCUpstreams,
|
|
|
|
"should have been exactly one call to PasswordCredentialsGrantAndValidateTokens() by all OIDC upstreams",
|
|
|
|
)
|
|
|
|
require.Equal(t, expectedPerformedByUpstreamName, actualNameOfUpstreamWhichMadeCall,
|
|
|
|
"PasswordCredentialsGrantAndValidateTokens() was called on the wrong OIDC upstream",
|
|
|
|
)
|
|
|
|
require.Equal(t, expectedArgs, actualArgs)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *UpstreamIDPListerBuilder) RequireExactlyZeroCallsToPasswordCredentialsGrantAndValidateTokens(t *testing.T) {
|
|
|
|
t.Helper()
|
|
|
|
actualCallCountAcrossAllOIDCUpstreams := 0
|
|
|
|
for _, upstreamOIDC := range b.upstreamOIDCIdentityProviders {
|
|
|
|
actualCallCountAcrossAllOIDCUpstreams += upstreamOIDC.passwordCredentialsGrantAndValidateTokensCallCount
|
|
|
|
}
|
|
|
|
require.Equal(t, 0, actualCallCountAcrossAllOIDCUpstreams,
|
|
|
|
"expected exactly zero calls to PasswordCredentialsGrantAndValidateTokens()",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *UpstreamIDPListerBuilder) RequireExactlyOneCallToExchangeAuthcodeAndValidateTokens(
|
|
|
|
t *testing.T,
|
|
|
|
expectedPerformedByUpstreamName string,
|
|
|
|
expectedArgs *ExchangeAuthcodeAndValidateTokenArgs,
|
|
|
|
) {
|
|
|
|
t.Helper()
|
|
|
|
var actualArgs *ExchangeAuthcodeAndValidateTokenArgs
|
|
|
|
var actualNameOfUpstreamWhichMadeCall string
|
|
|
|
actualCallCountAcrossAllOIDCUpstreams := 0
|
|
|
|
for _, upstreamOIDC := range b.upstreamOIDCIdentityProviders {
|
|
|
|
callCountOnThisUpstream := upstreamOIDC.exchangeAuthcodeAndValidateTokensCallCount
|
|
|
|
actualCallCountAcrossAllOIDCUpstreams += callCountOnThisUpstream
|
|
|
|
if callCountOnThisUpstream == 1 {
|
|
|
|
actualNameOfUpstreamWhichMadeCall = upstreamOIDC.Name
|
|
|
|
actualArgs = upstreamOIDC.exchangeAuthcodeAndValidateTokensArgs[0]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
require.Equal(t, 1, actualCallCountAcrossAllOIDCUpstreams,
|
|
|
|
"should have been exactly one call to ExchangeAuthcodeAndValidateTokens() by all OIDC upstreams",
|
|
|
|
)
|
|
|
|
require.Equal(t, expectedPerformedByUpstreamName, actualNameOfUpstreamWhichMadeCall,
|
|
|
|
"ExchangeAuthcodeAndValidateTokens() was called on the wrong OIDC upstream",
|
|
|
|
)
|
|
|
|
require.Equal(t, expectedArgs, actualArgs)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *UpstreamIDPListerBuilder) RequireExactlyZeroCallsToExchangeAuthcodeAndValidateTokens(t *testing.T) {
|
|
|
|
t.Helper()
|
|
|
|
actualCallCountAcrossAllOIDCUpstreams := 0
|
|
|
|
for _, upstreamOIDC := range b.upstreamOIDCIdentityProviders {
|
|
|
|
actualCallCountAcrossAllOIDCUpstreams += upstreamOIDC.exchangeAuthcodeAndValidateTokensCallCount
|
|
|
|
}
|
|
|
|
require.Equal(t, 0, actualCallCountAcrossAllOIDCUpstreams,
|
|
|
|
"expected exactly zero calls to ExchangeAuthcodeAndValidateTokens()",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-10-13 19:31:20 +00:00
|
|
|
func (b *UpstreamIDPListerBuilder) RequireExactlyOneCallToPerformRefresh(
|
|
|
|
t *testing.T,
|
|
|
|
expectedPerformedByUpstreamName string,
|
|
|
|
expectedArgs *PerformRefreshArgs,
|
|
|
|
) {
|
|
|
|
t.Helper()
|
|
|
|
var actualArgs *PerformRefreshArgs
|
|
|
|
var actualNameOfUpstreamWhichMadeCall string
|
2021-10-22 20:57:30 +00:00
|
|
|
actualCallCountAcrossAllUpstreams := 0
|
2021-10-13 19:31:20 +00:00
|
|
|
for _, upstreamOIDC := range b.upstreamOIDCIdentityProviders {
|
|
|
|
callCountOnThisUpstream := upstreamOIDC.performRefreshCallCount
|
2021-10-22 20:57:30 +00:00
|
|
|
actualCallCountAcrossAllUpstreams += callCountOnThisUpstream
|
2021-10-13 19:31:20 +00:00
|
|
|
if callCountOnThisUpstream == 1 {
|
|
|
|
actualNameOfUpstreamWhichMadeCall = upstreamOIDC.Name
|
|
|
|
actualArgs = upstreamOIDC.performRefreshArgs[0]
|
|
|
|
}
|
|
|
|
}
|
2021-10-22 20:57:30 +00:00
|
|
|
for _, upstreamLDAP := range b.upstreamLDAPIdentityProviders {
|
|
|
|
callCountOnThisUpstream := upstreamLDAP.performRefreshCallCount
|
|
|
|
actualCallCountAcrossAllUpstreams += callCountOnThisUpstream
|
|
|
|
if callCountOnThisUpstream == 1 {
|
|
|
|
actualNameOfUpstreamWhichMadeCall = upstreamLDAP.Name
|
|
|
|
actualArgs = upstreamLDAP.performRefreshArgs[0]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, upstreamAD := range b.upstreamActiveDirectoryIdentityProviders {
|
|
|
|
callCountOnThisUpstream := upstreamAD.performRefreshCallCount
|
|
|
|
actualCallCountAcrossAllUpstreams += callCountOnThisUpstream
|
|
|
|
if callCountOnThisUpstream == 1 {
|
|
|
|
actualNameOfUpstreamWhichMadeCall = upstreamAD.Name
|
|
|
|
actualArgs = upstreamAD.performRefreshArgs[0]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
require.Equal(t, 1, actualCallCountAcrossAllUpstreams,
|
|
|
|
"should have been exactly one call to PerformRefresh() by all upstreams",
|
2021-10-13 19:31:20 +00:00
|
|
|
)
|
|
|
|
require.Equal(t, expectedPerformedByUpstreamName, actualNameOfUpstreamWhichMadeCall,
|
2021-10-22 20:57:30 +00:00
|
|
|
"PerformRefresh() was called on the wrong upstream",
|
2021-10-13 19:31:20 +00:00
|
|
|
)
|
|
|
|
require.Equal(t, expectedArgs, actualArgs)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *UpstreamIDPListerBuilder) RequireExactlyZeroCallsToPerformRefresh(t *testing.T) {
|
|
|
|
t.Helper()
|
2021-10-22 20:57:30 +00:00
|
|
|
actualCallCountAcrossAllUpstreams := 0
|
2021-10-13 19:31:20 +00:00
|
|
|
for _, upstreamOIDC := range b.upstreamOIDCIdentityProviders {
|
2021-10-22 20:57:30 +00:00
|
|
|
actualCallCountAcrossAllUpstreams += upstreamOIDC.performRefreshCallCount
|
2021-10-13 19:31:20 +00:00
|
|
|
}
|
2021-10-22 20:57:30 +00:00
|
|
|
for _, upstreamLDAP := range b.upstreamLDAPIdentityProviders {
|
|
|
|
actualCallCountAcrossAllUpstreams += upstreamLDAP.performRefreshCallCount
|
|
|
|
}
|
|
|
|
for _, upstreamActiveDirectory := range b.upstreamActiveDirectoryIdentityProviders {
|
|
|
|
actualCallCountAcrossAllUpstreams += upstreamActiveDirectory.performRefreshCallCount
|
|
|
|
}
|
|
|
|
|
|
|
|
require.Equal(t, 0, actualCallCountAcrossAllUpstreams,
|
2021-10-13 19:31:20 +00:00
|
|
|
"expected exactly zero calls to PerformRefresh()",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *UpstreamIDPListerBuilder) RequireExactlyOneCallToValidateToken(
|
|
|
|
t *testing.T,
|
|
|
|
expectedPerformedByUpstreamName string,
|
2022-01-13 02:05:10 +00:00
|
|
|
expectedArgs *ValidateTokenAndMergeWithUserInfoArgs,
|
2021-10-13 19:31:20 +00:00
|
|
|
) {
|
|
|
|
t.Helper()
|
2022-01-13 02:05:10 +00:00
|
|
|
var actualArgs *ValidateTokenAndMergeWithUserInfoArgs
|
2021-10-13 19:31:20 +00:00
|
|
|
var actualNameOfUpstreamWhichMadeCall string
|
|
|
|
actualCallCountAcrossAllOIDCUpstreams := 0
|
|
|
|
for _, upstreamOIDC := range b.upstreamOIDCIdentityProviders {
|
2022-01-13 02:05:10 +00:00
|
|
|
callCountOnThisUpstream := upstreamOIDC.validateTokenAndMergeWithUserInfoCallCount
|
2021-10-13 19:31:20 +00:00
|
|
|
actualCallCountAcrossAllOIDCUpstreams += callCountOnThisUpstream
|
|
|
|
if callCountOnThisUpstream == 1 {
|
|
|
|
actualNameOfUpstreamWhichMadeCall = upstreamOIDC.Name
|
2022-01-13 02:05:10 +00:00
|
|
|
actualArgs = upstreamOIDC.validateTokenAndMergeWithUserInfoArgs[0]
|
2021-10-13 19:31:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
require.Equal(t, 1, actualCallCountAcrossAllOIDCUpstreams,
|
2021-12-16 20:53:49 +00:00
|
|
|
"should have been exactly one call to ValidateTokenAndMergeWithUserInfo() by all OIDC upstreams",
|
2021-10-13 19:31:20 +00:00
|
|
|
)
|
|
|
|
require.Equal(t, expectedPerformedByUpstreamName, actualNameOfUpstreamWhichMadeCall,
|
2021-12-16 20:53:49 +00:00
|
|
|
"ValidateTokenAndMergeWithUserInfo() was called on the wrong OIDC upstream",
|
2021-10-13 19:31:20 +00:00
|
|
|
)
|
|
|
|
require.Equal(t, expectedArgs, actualArgs)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *UpstreamIDPListerBuilder) RequireExactlyZeroCallsToValidateToken(t *testing.T) {
|
|
|
|
t.Helper()
|
|
|
|
actualCallCountAcrossAllOIDCUpstreams := 0
|
|
|
|
for _, upstreamOIDC := range b.upstreamOIDCIdentityProviders {
|
2022-01-13 02:05:10 +00:00
|
|
|
actualCallCountAcrossAllOIDCUpstreams += upstreamOIDC.validateTokenAndMergeWithUserInfoCallCount
|
2021-10-13 19:31:20 +00:00
|
|
|
}
|
|
|
|
require.Equal(t, 0, actualCallCountAcrossAllOIDCUpstreams,
|
2021-12-16 20:53:49 +00:00
|
|
|
"expected exactly zero calls to ValidateTokenAndMergeWithUserInfo()",
|
2021-10-13 19:31:20 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-12-03 21:44:24 +00:00
|
|
|
func (b *UpstreamIDPListerBuilder) RequireExactlyOneCallToRevokeToken(
|
2021-11-10 23:34:19 +00:00
|
|
|
t *testing.T,
|
|
|
|
expectedPerformedByUpstreamName string,
|
2021-12-03 21:44:24 +00:00
|
|
|
expectedArgs *RevokeTokenArgs,
|
2021-11-10 23:34:19 +00:00
|
|
|
) {
|
|
|
|
t.Helper()
|
2021-12-03 21:44:24 +00:00
|
|
|
var actualArgs *RevokeTokenArgs
|
2021-11-10 23:34:19 +00:00
|
|
|
var actualNameOfUpstreamWhichMadeCall string
|
|
|
|
actualCallCountAcrossAllOIDCUpstreams := 0
|
|
|
|
for _, upstreamOIDC := range b.upstreamOIDCIdentityProviders {
|
2021-12-03 21:44:24 +00:00
|
|
|
callCountOnThisUpstream := upstreamOIDC.revokeTokenCallCount
|
2021-11-10 23:34:19 +00:00
|
|
|
actualCallCountAcrossAllOIDCUpstreams += callCountOnThisUpstream
|
|
|
|
if callCountOnThisUpstream == 1 {
|
|
|
|
actualNameOfUpstreamWhichMadeCall = upstreamOIDC.Name
|
2021-12-03 21:44:24 +00:00
|
|
|
actualArgs = upstreamOIDC.revokeTokenArgs[0]
|
2021-11-10 23:34:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
require.Equal(t, 1, actualCallCountAcrossAllOIDCUpstreams,
|
2021-12-03 21:44:24 +00:00
|
|
|
"should have been exactly one call to RevokeToken() by all OIDC upstreams",
|
2021-11-10 23:34:19 +00:00
|
|
|
)
|
|
|
|
require.Equal(t, expectedPerformedByUpstreamName, actualNameOfUpstreamWhichMadeCall,
|
2021-12-03 21:44:24 +00:00
|
|
|
"RevokeToken() was called on the wrong OIDC upstream",
|
2021-11-10 23:34:19 +00:00
|
|
|
)
|
|
|
|
require.Equal(t, expectedArgs, actualArgs)
|
|
|
|
}
|
|
|
|
|
2021-12-03 21:44:24 +00:00
|
|
|
func (b *UpstreamIDPListerBuilder) RequireExactlyZeroCallsToRevokeToken(t *testing.T) {
|
2021-11-10 23:34:19 +00:00
|
|
|
t.Helper()
|
|
|
|
actualCallCountAcrossAllOIDCUpstreams := 0
|
|
|
|
for _, upstreamOIDC := range b.upstreamOIDCIdentityProviders {
|
2021-12-03 21:44:24 +00:00
|
|
|
actualCallCountAcrossAllOIDCUpstreams += upstreamOIDC.revokeTokenCallCount
|
2021-11-10 23:34:19 +00:00
|
|
|
}
|
|
|
|
require.Equal(t, 0, actualCallCountAcrossAllOIDCUpstreams,
|
2021-12-03 21:44:24 +00:00
|
|
|
"expected exactly zero calls to RevokeToken()",
|
2021-11-10 23:34:19 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-04-09 00:28:01 +00:00
|
|
|
func NewUpstreamIDPListerBuilder() *UpstreamIDPListerBuilder {
|
|
|
|
return &UpstreamIDPListerBuilder{}
|
|
|
|
}
|
|
|
|
|
2021-08-13 00:53:14 +00:00
|
|
|
type TestUpstreamOIDCIdentityProviderBuilder struct {
|
2022-01-13 02:05:10 +00:00
|
|
|
name string
|
|
|
|
resourceUID types.UID
|
|
|
|
clientID string
|
|
|
|
scopes []string
|
|
|
|
idToken map[string]interface{}
|
|
|
|
refreshToken *oidctypes.RefreshToken
|
|
|
|
accessToken *oidctypes.AccessToken
|
|
|
|
usernameClaim string
|
|
|
|
groupsClaim string
|
|
|
|
refreshedTokens *oauth2.Token
|
|
|
|
validatedAndMergedWithUserInfoTokens *oidctypes.Token
|
|
|
|
authorizationURL url.URL
|
|
|
|
hasUserInfoURL bool
|
|
|
|
additionalAuthcodeParams map[string]string
|
2022-09-20 21:54:10 +00:00
|
|
|
additionalClaimMappings map[string]string
|
2022-01-13 02:05:10 +00:00
|
|
|
allowPasswordGrant bool
|
|
|
|
authcodeExchangeErr error
|
|
|
|
passwordGrantErr error
|
|
|
|
performRefreshErr error
|
2022-01-14 18:49:22 +00:00
|
|
|
revokeTokenErr error
|
2022-01-13 02:05:10 +00:00
|
|
|
validateTokenAndMergeWithUserInfoErr error
|
2023-06-13 19:26:59 +00:00
|
|
|
displayNameForFederationDomain string
|
|
|
|
transformsForFederationDomain *idtransform.TransformationPipeline
|
2021-08-13 00:53:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithName(value string) *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.name = value
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
2021-10-08 22:48:21 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithResourceUID(value types.UID) *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.resourceUID = value
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
2021-08-13 00:53:14 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithClientID(value string) *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.clientID = value
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithAuthorizationURL(value url.URL) *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.authorizationURL = value
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
2022-01-11 23:40:38 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithUserInfoURL() *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.hasUserInfoURL = true
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithoutUserInfoURL() *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.hasUserInfoURL = false
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
2021-08-13 00:53:14 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithAllowPasswordGrant(value bool) *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.allowPasswordGrant = value
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithScopes(values []string) *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.scopes = values
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithUsernameClaim(value string) *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.usernameClaim = value
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithoutUsernameClaim() *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.usernameClaim = ""
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithGroupsClaim(value string) *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.groupsClaim = value
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithoutGroupsClaim() *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.groupsClaim = ""
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithIDTokenClaim(name string, value interface{}) *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
if u.idToken == nil {
|
|
|
|
u.idToken = map[string]interface{}{}
|
|
|
|
}
|
|
|
|
u.idToken[name] = value
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithoutIDTokenClaim(claim string) *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
delete(u.idToken, claim)
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
2021-10-08 22:48:21 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithAdditionalAuthcodeParams(params map[string]string) *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.additionalAuthcodeParams = params
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
2022-09-20 21:54:10 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithAdditionalClaimMappings(m map[string]string) *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.additionalClaimMappings = m
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
2021-10-08 22:48:21 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithRefreshToken(token string) *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.refreshToken = &oidctypes.RefreshToken{Token: token}
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithEmptyRefreshToken() *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.refreshToken = &oidctypes.RefreshToken{Token: ""}
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithoutRefreshToken() *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.refreshToken = nil
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
2022-01-18 23:34:19 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithAccessToken(token string, expiry metav1.Time) *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.accessToken = &oidctypes.AccessToken{Token: token, Expiry: expiry}
|
2022-01-05 18:31:38 +00:00
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithEmptyAccessToken() *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.accessToken = &oidctypes.AccessToken{Token: ""}
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithoutAccessToken() *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.accessToken = nil
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
2021-08-13 00:53:14 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithUpstreamAuthcodeExchangeError(err error) *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.authcodeExchangeErr = err
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithPasswordGrantError(err error) *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.passwordGrantErr = err
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
2021-10-13 19:31:20 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithRefreshedTokens(tokens *oauth2.Token) *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.refreshedTokens = tokens
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithPerformRefreshError(err error) *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.performRefreshErr = err
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
2022-01-13 02:05:10 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithValidatedAndMergedWithUserInfoTokens(tokens *oidctypes.Token) *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.validatedAndMergedWithUserInfoTokens = tokens
|
2021-10-13 19:31:20 +00:00
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
2022-01-13 02:05:10 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithValidateTokenAndMergeWithUserInfoError(err error) *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.validateTokenAndMergeWithUserInfoErr = err
|
2021-10-13 19:31:20 +00:00
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
2021-12-03 21:44:24 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithRevokeTokenError(err error) *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.revokeTokenErr = err
|
2021-11-10 23:34:19 +00:00
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
2023-06-13 19:26:59 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithDisplayNameForFederationDomain(displayName string) *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.displayNameForFederationDomain = displayName
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithTransformsForFederationDomain(transforms *idtransform.TransformationPipeline) *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
u.transformsForFederationDomain = transforms
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
2021-08-13 00:53:14 +00:00
|
|
|
func (u *TestUpstreamOIDCIdentityProviderBuilder) Build() *TestUpstreamOIDCIdentityProvider {
|
2023-06-13 19:26:59 +00:00
|
|
|
if u.displayNameForFederationDomain == "" {
|
|
|
|
// default it to the CR name
|
|
|
|
u.displayNameForFederationDomain = u.name
|
|
|
|
}
|
|
|
|
if u.transformsForFederationDomain == nil {
|
|
|
|
// default to an empty pipeline
|
|
|
|
u.transformsForFederationDomain = idtransform.NewTransformationPipeline()
|
|
|
|
}
|
|
|
|
|
2021-08-13 00:53:14 +00:00
|
|
|
return &TestUpstreamOIDCIdentityProvider{
|
2023-06-13 19:26:59 +00:00
|
|
|
Name: u.name,
|
|
|
|
ClientID: u.clientID,
|
|
|
|
ResourceUID: u.resourceUID,
|
|
|
|
UsernameClaim: u.usernameClaim,
|
|
|
|
GroupsClaim: u.groupsClaim,
|
|
|
|
Scopes: u.scopes,
|
|
|
|
AllowPasswordGrant: u.allowPasswordGrant,
|
|
|
|
AuthorizationURL: u.authorizationURL,
|
|
|
|
UserInfoURL: u.hasUserInfoURL,
|
|
|
|
AdditionalAuthcodeParams: u.additionalAuthcodeParams,
|
|
|
|
AdditionalClaimMappings: u.additionalClaimMappings,
|
|
|
|
DisplayNameForFederationDomain: u.displayNameForFederationDomain,
|
|
|
|
TransformsForFederationDomain: u.transformsForFederationDomain,
|
2021-08-13 00:53:14 +00:00
|
|
|
ExchangeAuthcodeAndValidateTokensFunc: func(ctx context.Context, authcode string, pkceCodeVerifier pkce.Code, expectedIDTokenNonce nonce.Nonce) (*oidctypes.Token, error) {
|
|
|
|
if u.authcodeExchangeErr != nil {
|
|
|
|
return nil, u.authcodeExchangeErr
|
|
|
|
}
|
2022-01-05 18:31:38 +00:00
|
|
|
return &oidctypes.Token{IDToken: &oidctypes.IDToken{Claims: u.idToken}, RefreshToken: u.refreshToken, AccessToken: u.accessToken}, nil
|
2021-08-13 00:53:14 +00:00
|
|
|
},
|
|
|
|
PasswordCredentialsGrantAndValidateTokensFunc: func(ctx context.Context, username, password string) (*oidctypes.Token, error) {
|
|
|
|
if u.passwordGrantErr != nil {
|
|
|
|
return nil, u.passwordGrantErr
|
|
|
|
}
|
2022-01-05 18:31:38 +00:00
|
|
|
return &oidctypes.Token{IDToken: &oidctypes.IDToken{Claims: u.idToken}, RefreshToken: u.refreshToken, AccessToken: u.accessToken}, nil
|
2021-08-13 00:53:14 +00:00
|
|
|
},
|
2021-10-13 19:31:20 +00:00
|
|
|
PerformRefreshFunc: func(ctx context.Context, refreshToken string) (*oauth2.Token, error) {
|
|
|
|
if u.performRefreshErr != nil {
|
|
|
|
return nil, u.performRefreshErr
|
|
|
|
}
|
|
|
|
return u.refreshedTokens, nil
|
|
|
|
},
|
2023-05-08 21:07:38 +00:00
|
|
|
RevokeTokenFunc: func(ctx context.Context, refreshToken string, tokenType upstreamprovider.RevocableTokenType) error {
|
2021-12-03 21:44:24 +00:00
|
|
|
return u.revokeTokenErr
|
2021-10-22 21:32:26 +00:00
|
|
|
},
|
2022-01-13 02:05:10 +00:00
|
|
|
ValidateTokenAndMergeWithUserInfoFunc: func(ctx context.Context, tok *oauth2.Token, expectedIDTokenNonce nonce.Nonce) (*oidctypes.Token, error) {
|
|
|
|
if u.validateTokenAndMergeWithUserInfoErr != nil {
|
|
|
|
return nil, u.validateTokenAndMergeWithUserInfoErr
|
2021-10-13 19:31:20 +00:00
|
|
|
}
|
2022-01-13 02:05:10 +00:00
|
|
|
return u.validatedAndMergedWithUserInfoTokens, nil
|
2021-10-13 19:31:20 +00:00
|
|
|
},
|
2021-08-13 00:53:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewTestUpstreamOIDCIdentityProviderBuilder() *TestUpstreamOIDCIdentityProviderBuilder {
|
|
|
|
return &TestUpstreamOIDCIdentityProviderBuilder{}
|
|
|
|
}
|
|
|
|
|
2021-04-09 00:28:01 +00:00
|
|
|
// Declare a separate type from the production code to ensure that the state param's contents was serialized
|
|
|
|
// in the format that we expect, with the json keys that we expect, etc. This also ensure that the order of
|
|
|
|
// the serialized fields is the same, which doesn't really matter expect that we can make simpler equality
|
|
|
|
// assertions about the redirect URL in this test.
|
|
|
|
type ExpectedUpstreamStateParamFormat struct {
|
|
|
|
P string `json:"p"`
|
|
|
|
U string `json:"u"`
|
2022-04-26 19:51:56 +00:00
|
|
|
T string `json:"t"`
|
2021-04-09 00:28:01 +00:00
|
|
|
N string `json:"n"`
|
|
|
|
C string `json:"c"`
|
|
|
|
K string `json:"k"`
|
|
|
|
V string `json:"v"`
|
|
|
|
}
|
|
|
|
|
2022-04-26 22:30:39 +00:00
|
|
|
type UpstreamStateParamBuilder ExpectedUpstreamStateParamFormat
|
|
|
|
|
|
|
|
func (b UpstreamStateParamBuilder) Build(t *testing.T, stateEncoder *securecookie.SecureCookie) string {
|
|
|
|
state, err := stateEncoder.Encode("s", b)
|
|
|
|
require.NoError(t, err)
|
|
|
|
return state
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *UpstreamStateParamBuilder) WithAuthorizeRequestParams(params string) *UpstreamStateParamBuilder {
|
|
|
|
b.P = params
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *UpstreamStateParamBuilder) WithNonce(nonce string) *UpstreamStateParamBuilder {
|
|
|
|
b.N = nonce
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *UpstreamStateParamBuilder) WithCSRF(csrf string) *UpstreamStateParamBuilder {
|
|
|
|
b.C = csrf
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *UpstreamStateParamBuilder) WithPKCE(pkce string) *UpstreamStateParamBuilder {
|
|
|
|
b.K = pkce
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *UpstreamStateParamBuilder) WithUpstreamIDPType(upstreamIDPType string) *UpstreamStateParamBuilder {
|
|
|
|
b.T = upstreamIDPType
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *UpstreamStateParamBuilder) WithStateVersion(version string) *UpstreamStateParamBuilder {
|
|
|
|
b.V = version
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
2021-04-09 00:28:01 +00:00
|
|
|
type staticKeySet struct {
|
|
|
|
publicKey crypto.PublicKey
|
|
|
|
}
|
|
|
|
|
|
|
|
func newStaticKeySet(publicKey crypto.PublicKey) coreosoidc.KeySet {
|
|
|
|
return &staticKeySet{publicKey}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *staticKeySet) VerifySignature(_ context.Context, jwt string) ([]byte, error) {
|
|
|
|
jws, err := jose.ParseSigned(jwt)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("oidc: malformed jwt: %w", err)
|
|
|
|
}
|
|
|
|
return jws.Verify(s.publicKey)
|
|
|
|
}
|
|
|
|
|
|
|
|
// VerifyECDSAIDToken verifies that the provided idToken was issued via the provided jwtSigningKey.
|
|
|
|
// It also performs some light validation on the claims, i.e., it makes sure the provided idToken
|
|
|
|
// has the provided issuer and clientID.
|
|
|
|
//
|
|
|
|
// Further validation can be done via callers via the returned coreosoidc.IDToken.
|
|
|
|
func VerifyECDSAIDToken(
|
|
|
|
t *testing.T,
|
|
|
|
issuer, clientID string,
|
|
|
|
jwtSigningKey *ecdsa.PrivateKey,
|
|
|
|
idToken string,
|
|
|
|
) *coreosoidc.IDToken {
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
keySet := newStaticKeySet(jwtSigningKey.Public())
|
|
|
|
verifyConfig := coreosoidc.Config{ClientID: clientID, SupportedSigningAlgs: []string{coreosoidc.ES256}}
|
|
|
|
verifier := coreosoidc.NewVerifier(issuer, keySet, &verifyConfig)
|
|
|
|
token, err := verifier.Verify(context.Background(), idToken)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
return token
|
|
|
|
}
|
|
|
|
|
2021-06-16 18:11:07 +00:00
|
|
|
func RequireAuthCodeRegexpMatch(
|
2021-04-09 00:28:01 +00:00
|
|
|
t *testing.T,
|
2021-06-16 18:11:07 +00:00
|
|
|
actualContent string,
|
|
|
|
wantRegexp string,
|
2021-04-09 00:28:01 +00:00
|
|
|
kubeClient *fake.Clientset,
|
|
|
|
secretsClient v1.SecretInterface,
|
|
|
|
oauthStore fositestoragei.AllFositeStorage,
|
|
|
|
wantDownstreamGrantedScopes []string,
|
|
|
|
wantDownstreamIDTokenSubject string,
|
|
|
|
wantDownstreamIDTokenUsername string,
|
|
|
|
wantDownstreamIDTokenGroups []string,
|
|
|
|
wantDownstreamRequestedScopes []string,
|
|
|
|
wantDownstreamPKCEChallenge string,
|
|
|
|
wantDownstreamPKCEChallengeMethod string,
|
|
|
|
wantDownstreamNonce string,
|
|
|
|
wantDownstreamClientID string,
|
|
|
|
wantDownstreamRedirectURI string,
|
2021-10-08 22:48:21 +00:00
|
|
|
wantCustomSessionData *psession.CustomSessionData,
|
2023-01-13 22:56:40 +00:00
|
|
|
wantDownstreamAdditionalClaims map[string]interface{},
|
2021-04-09 00:28:01 +00:00
|
|
|
) {
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
// Assert that Location header matches regular expression.
|
2021-06-16 18:11:07 +00:00
|
|
|
regex := regexp.MustCompile(wantRegexp)
|
|
|
|
submatches := regex.FindStringSubmatch(actualContent)
|
|
|
|
require.Lenf(t, submatches, 2, "no regexp match in actualContent: %", actualContent)
|
2021-04-09 00:28:01 +00:00
|
|
|
capturedAuthCode := submatches[1]
|
|
|
|
|
2022-04-13 17:13:27 +00:00
|
|
|
// Authcodes should start with the custom prefix "pin_ac_" to make them identifiable as authcodes when seen by a user out of context.
|
|
|
|
require.True(t, strings.HasPrefix(capturedAuthCode, "pin_ac_"), "token %q did not have expected prefix 'pin_ac_'", capturedAuthCode)
|
|
|
|
|
2021-04-09 00:28:01 +00:00
|
|
|
// fosite authcodes are in the format `data.signature`, so grab the signature part, which is the lookup key in the storage interface
|
|
|
|
authcodeDataAndSignature := strings.Split(capturedAuthCode, ".")
|
|
|
|
require.Len(t, authcodeDataAndSignature, 2)
|
|
|
|
|
|
|
|
// Several Secrets should have been created
|
|
|
|
expectedNumberOfCreatedSecrets := 2
|
|
|
|
if includesOpenIDScope(wantDownstreamGrantedScopes) {
|
|
|
|
expectedNumberOfCreatedSecrets++
|
|
|
|
}
|
2022-07-21 16:26:00 +00:00
|
|
|
require.Len(t, FilterClientSecretCreateActions(kubeClient.Actions()), expectedNumberOfCreatedSecrets)
|
2021-04-09 00:28:01 +00:00
|
|
|
|
|
|
|
// One authcode should have been stored.
|
|
|
|
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secretsClient, labels.Set{crud.SecretLabelKey: authorizationcode.TypeLabelValue}, 1)
|
|
|
|
|
|
|
|
storedRequestFromAuthcode, storedSessionFromAuthcode := validateAuthcodeStorage(
|
|
|
|
t,
|
|
|
|
oauthStore,
|
|
|
|
authcodeDataAndSignature[1], // Authcode store key is authcode signature
|
|
|
|
wantDownstreamGrantedScopes,
|
|
|
|
wantDownstreamIDTokenSubject,
|
|
|
|
wantDownstreamIDTokenUsername,
|
|
|
|
wantDownstreamIDTokenGroups,
|
|
|
|
wantDownstreamRequestedScopes,
|
|
|
|
wantDownstreamClientID,
|
|
|
|
wantDownstreamRedirectURI,
|
2021-10-08 22:48:21 +00:00
|
|
|
wantCustomSessionData,
|
2023-01-13 22:56:40 +00:00
|
|
|
wantDownstreamAdditionalClaims,
|
2021-04-09 00:28:01 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// One PKCE should have been stored.
|
|
|
|
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secretsClient, labels.Set{crud.SecretLabelKey: pkce2.TypeLabelValue}, 1)
|
|
|
|
|
|
|
|
validatePKCEStorage(
|
|
|
|
t,
|
|
|
|
oauthStore,
|
|
|
|
authcodeDataAndSignature[1], // PKCE store key is authcode signature
|
|
|
|
storedRequestFromAuthcode,
|
|
|
|
storedSessionFromAuthcode,
|
|
|
|
wantDownstreamPKCEChallenge,
|
|
|
|
wantDownstreamPKCEChallengeMethod,
|
|
|
|
)
|
|
|
|
|
|
|
|
// One IDSession should have been stored, if the downstream actually requested the "openid" scope
|
|
|
|
if includesOpenIDScope(wantDownstreamGrantedScopes) {
|
|
|
|
testutil.RequireNumberOfSecretsMatchingLabelSelector(t, secretsClient, labels.Set{crud.SecretLabelKey: openidconnect.TypeLabelValue}, 1)
|
|
|
|
|
|
|
|
validateIDSessionStorage(
|
|
|
|
t,
|
|
|
|
oauthStore,
|
|
|
|
capturedAuthCode, // IDSession store key is full authcode
|
|
|
|
storedRequestFromAuthcode,
|
|
|
|
storedSessionFromAuthcode,
|
|
|
|
wantDownstreamNonce,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func includesOpenIDScope(scopes []string) bool {
|
|
|
|
for _, scope := range scopes {
|
|
|
|
if scope == "openid" {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2022-09-20 21:54:10 +00:00
|
|
|
//nolint:funlen
|
2021-04-09 00:28:01 +00:00
|
|
|
func validateAuthcodeStorage(
|
|
|
|
t *testing.T,
|
|
|
|
oauthStore fositestoragei.AllFositeStorage,
|
|
|
|
storeKey string,
|
|
|
|
wantDownstreamGrantedScopes []string,
|
|
|
|
wantDownstreamIDTokenSubject string,
|
|
|
|
wantDownstreamIDTokenUsername string,
|
|
|
|
wantDownstreamIDTokenGroups []string,
|
|
|
|
wantDownstreamRequestedScopes []string,
|
|
|
|
wantDownstreamClientID string,
|
|
|
|
wantDownstreamRedirectURI string,
|
2021-10-08 22:48:21 +00:00
|
|
|
wantCustomSessionData *psession.CustomSessionData,
|
2023-01-13 22:56:40 +00:00
|
|
|
wantDownstreamAdditionalClaims map[string]interface{},
|
2021-10-06 22:28:13 +00:00
|
|
|
) (*fosite.Request, *psession.PinnipedSession) {
|
2021-04-09 00:28:01 +00:00
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
const (
|
|
|
|
authCodeExpirationSeconds = 10 * 60 // Currently, we set our auth code expiration to 10 minutes
|
|
|
|
timeComparisonFudgeFactor = time.Second * 15
|
|
|
|
)
|
|
|
|
|
|
|
|
// Get the authcode session back from storage so we can require that it was stored correctly.
|
|
|
|
storedAuthorizeRequestFromAuthcode, err := oauthStore.GetAuthorizeCodeSession(context.Background(), storeKey, nil)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Check that storage returned the expected concrete data types.
|
|
|
|
storedRequestFromAuthcode, storedSessionFromAuthcode := castStoredAuthorizeRequest(t, storedAuthorizeRequestFromAuthcode)
|
|
|
|
|
|
|
|
// Check which scopes were granted.
|
|
|
|
require.ElementsMatch(t, wantDownstreamGrantedScopes, storedRequestFromAuthcode.GetGrantedScopes())
|
|
|
|
|
|
|
|
// Check all the other fields of the stored request.
|
|
|
|
require.NotEmpty(t, storedRequestFromAuthcode.ID)
|
|
|
|
require.Equal(t, wantDownstreamClientID, storedRequestFromAuthcode.Client.GetID())
|
|
|
|
require.ElementsMatch(t, wantDownstreamRequestedScopes, storedRequestFromAuthcode.RequestedScope)
|
|
|
|
require.Nil(t, storedRequestFromAuthcode.RequestedAudience)
|
|
|
|
require.Empty(t, storedRequestFromAuthcode.GrantedAudience)
|
|
|
|
require.Equal(t, url.Values{"redirect_uri": []string{wantDownstreamRedirectURI}}, storedRequestFromAuthcode.Form)
|
|
|
|
testutil.RequireTimeInDelta(t, time.Now(), storedRequestFromAuthcode.RequestedAt, timeComparisonFudgeFactor)
|
|
|
|
|
|
|
|
// We're not using these fields yet, so confirm that we did not set them (for now).
|
2021-10-06 22:28:13 +00:00
|
|
|
require.Empty(t, storedSessionFromAuthcode.Fosite.Subject)
|
|
|
|
require.Empty(t, storedSessionFromAuthcode.Fosite.Username)
|
|
|
|
require.Empty(t, storedSessionFromAuthcode.Fosite.Headers)
|
2021-04-09 00:28:01 +00:00
|
|
|
|
|
|
|
// The authcode that we are issuing should be good for the length of time that we declare in the fosite config.
|
2021-10-06 22:28:13 +00:00
|
|
|
testutil.RequireTimeInDelta(t, time.Now().Add(authCodeExpirationSeconds*time.Second), storedSessionFromAuthcode.Fosite.ExpiresAt[fosite.AuthorizeCode], timeComparisonFudgeFactor)
|
|
|
|
require.Len(t, storedSessionFromAuthcode.Fosite.ExpiresAt, 1)
|
2021-04-09 00:28:01 +00:00
|
|
|
|
|
|
|
// Now confirm the ID token claims.
|
2021-10-06 22:28:13 +00:00
|
|
|
actualClaims := storedSessionFromAuthcode.Fosite.Claims
|
2021-04-09 00:28:01 +00:00
|
|
|
|
2022-08-09 23:07:23 +00:00
|
|
|
// Should always have an azp claim.
|
|
|
|
require.Equal(t, wantDownstreamClientID, actualClaims.Extra["azp"])
|
|
|
|
wantDownstreamIDTokenExtraClaimsCount := 1 // should always have azp claim
|
|
|
|
|
2023-01-13 22:56:40 +00:00
|
|
|
if len(wantDownstreamAdditionalClaims) > 0 {
|
2022-09-20 21:54:10 +00:00
|
|
|
wantDownstreamIDTokenExtraClaimsCount++
|
|
|
|
}
|
|
|
|
|
2021-04-09 00:28:01 +00:00
|
|
|
// Check the user's identity, which are put into the downstream ID token's subject, username and groups claims.
|
|
|
|
require.Equal(t, wantDownstreamIDTokenSubject, actualClaims.Subject)
|
Create username scope, required for clients to get username in ID token
- For backwards compatibility with older Pinniped CLIs, the pinniped-cli
client does not need to request the username or groups scopes for them
to be granted. For dynamic clients, the usual OAuth2 rules apply:
the client must be allowed to request the scopes according to its
configuration, and the client must actually request the scopes in the
authorization request.
- If the username scope was not granted, then there will be no username
in the ID token, and the cluster-scoped token exchange will fail since
there would be no username in the resulting cluster-scoped ID token.
- The OIDC well-known discovery endpoint lists the username and groups
scopes in the scopes_supported list, and lists the username and groups
claims in the claims_supported list.
- Add username and groups scopes to the default list of scopes
put into kubeconfig files by "pinniped get kubeconfig" CLI command,
and the default list of scopes used by "pinniped login oidc" when
no list of scopes is specified in the kubeconfig file
- The warning header about group memberships changing during upstream
refresh will only be sent to the pinniped-cli client, since it is
only intended for kubectl and it could leak the username to the
client (which may not have the username scope granted) through the
warning message text.
- Add the user's username to the session storage as a new field, so that
during upstream refresh we can compare the original username from the
initial authorization to the refreshed username, even in the case when
the username scope was not granted (and therefore the username is not
stored in the ID token claims of the session storage)
- Bump the Supervisor session storage format version from 2 to 3
due to the username field being added to the session struct
- Extract commonly used string constants related to OIDC flows to api
package.
- Change some import names to make them consistent:
- Always import github.com/coreos/go-oidc/v3/oidc as "coreosoidc"
- Always import go.pinniped.dev/generated/latest/apis/supervisor/oidc
as "oidcapi"
- Always import go.pinniped.dev/internal/oidc as "oidc"
2022-08-08 23:29:22 +00:00
|
|
|
if wantDownstreamIDTokenUsername == "" {
|
|
|
|
require.NotContains(t, actualClaims.Extra, "username")
|
|
|
|
} else {
|
2022-08-09 23:07:23 +00:00
|
|
|
wantDownstreamIDTokenExtraClaimsCount++ // should also have username claim
|
Create username scope, required for clients to get username in ID token
- For backwards compatibility with older Pinniped CLIs, the pinniped-cli
client does not need to request the username or groups scopes for them
to be granted. For dynamic clients, the usual OAuth2 rules apply:
the client must be allowed to request the scopes according to its
configuration, and the client must actually request the scopes in the
authorization request.
- If the username scope was not granted, then there will be no username
in the ID token, and the cluster-scoped token exchange will fail since
there would be no username in the resulting cluster-scoped ID token.
- The OIDC well-known discovery endpoint lists the username and groups
scopes in the scopes_supported list, and lists the username and groups
claims in the claims_supported list.
- Add username and groups scopes to the default list of scopes
put into kubeconfig files by "pinniped get kubeconfig" CLI command,
and the default list of scopes used by "pinniped login oidc" when
no list of scopes is specified in the kubeconfig file
- The warning header about group memberships changing during upstream
refresh will only be sent to the pinniped-cli client, since it is
only intended for kubectl and it could leak the username to the
client (which may not have the username scope granted) through the
warning message text.
- Add the user's username to the session storage as a new field, so that
during upstream refresh we can compare the original username from the
initial authorization to the refreshed username, even in the case when
the username scope was not granted (and therefore the username is not
stored in the ID token claims of the session storage)
- Bump the Supervisor session storage format version from 2 to 3
due to the username field being added to the session struct
- Extract commonly used string constants related to OIDC flows to api
package.
- Change some import names to make them consistent:
- Always import github.com/coreos/go-oidc/v3/oidc as "coreosoidc"
- Always import go.pinniped.dev/generated/latest/apis/supervisor/oidc
as "oidcapi"
- Always import go.pinniped.dev/internal/oidc as "oidc"
2022-08-08 23:29:22 +00:00
|
|
|
require.Equal(t, wantDownstreamIDTokenUsername, actualClaims.Extra["username"])
|
|
|
|
}
|
2022-06-15 15:00:17 +00:00
|
|
|
if slices.Contains(wantDownstreamGrantedScopes, "groups") {
|
2022-08-09 23:07:23 +00:00
|
|
|
wantDownstreamIDTokenExtraClaimsCount++ // should also have groups claim
|
2022-06-15 15:00:17 +00:00
|
|
|
actualDownstreamIDTokenGroups := actualClaims.Extra["groups"]
|
|
|
|
require.NotNil(t, actualDownstreamIDTokenGroups)
|
|
|
|
require.ElementsMatch(t, wantDownstreamIDTokenGroups, actualDownstreamIDTokenGroups)
|
|
|
|
} else {
|
Create username scope, required for clients to get username in ID token
- For backwards compatibility with older Pinniped CLIs, the pinniped-cli
client does not need to request the username or groups scopes for them
to be granted. For dynamic clients, the usual OAuth2 rules apply:
the client must be allowed to request the scopes according to its
configuration, and the client must actually request the scopes in the
authorization request.
- If the username scope was not granted, then there will be no username
in the ID token, and the cluster-scoped token exchange will fail since
there would be no username in the resulting cluster-scoped ID token.
- The OIDC well-known discovery endpoint lists the username and groups
scopes in the scopes_supported list, and lists the username and groups
claims in the claims_supported list.
- Add username and groups scopes to the default list of scopes
put into kubeconfig files by "pinniped get kubeconfig" CLI command,
and the default list of scopes used by "pinniped login oidc" when
no list of scopes is specified in the kubeconfig file
- The warning header about group memberships changing during upstream
refresh will only be sent to the pinniped-cli client, since it is
only intended for kubectl and it could leak the username to the
client (which may not have the username scope granted) through the
warning message text.
- Add the user's username to the session storage as a new field, so that
during upstream refresh we can compare the original username from the
initial authorization to the refreshed username, even in the case when
the username scope was not granted (and therefore the username is not
stored in the ID token claims of the session storage)
- Bump the Supervisor session storage format version from 2 to 3
due to the username field being added to the session struct
- Extract commonly used string constants related to OIDC flows to api
package.
- Change some import names to make them consistent:
- Always import github.com/coreos/go-oidc/v3/oidc as "coreosoidc"
- Always import go.pinniped.dev/generated/latest/apis/supervisor/oidc
as "oidcapi"
- Always import go.pinniped.dev/internal/oidc as "oidc"
2022-08-08 23:29:22 +00:00
|
|
|
require.Emptyf(t, wantDownstreamIDTokenGroups, "test case did not want the groups scope to be granted, "+
|
|
|
|
"but wanted something in the groups claim, which doesn't make sense. please review the test case's expectations.")
|
2022-06-15 15:00:17 +00:00
|
|
|
actualDownstreamIDTokenGroups := actualClaims.Extra["groups"]
|
|
|
|
require.Nil(t, actualDownstreamIDTokenGroups)
|
|
|
|
}
|
2023-01-13 22:56:40 +00:00
|
|
|
if len(wantDownstreamAdditionalClaims) > 0 {
|
|
|
|
actualAdditionalClaims, ok := actualClaims.Get("additionalClaims").(map[string]interface{})
|
|
|
|
require.True(t, ok, "expected additionalClaims to be a map[string]interface{}")
|
|
|
|
require.Equal(t, wantDownstreamAdditionalClaims, actualAdditionalClaims)
|
2022-09-20 21:54:10 +00:00
|
|
|
} else {
|
2023-01-13 22:56:40 +00:00
|
|
|
require.NotContains(t, actualClaims.Extra, "additionalClaims", "additionalClaims must not be present when there are no wanted additional claims")
|
2022-09-20 21:54:10 +00:00
|
|
|
}
|
|
|
|
|
2022-08-09 23:07:23 +00:00
|
|
|
// Make sure that we asserted on every extra claim.
|
|
|
|
require.Len(t, actualClaims.Extra, wantDownstreamIDTokenExtraClaimsCount)
|
2021-04-09 00:28:01 +00:00
|
|
|
|
|
|
|
// Check the rest of the downstream ID token's claims. Fosite wants us to set these (in UTC time).
|
|
|
|
testutil.RequireTimeInDelta(t, time.Now().UTC(), actualClaims.RequestedAt, timeComparisonFudgeFactor)
|
|
|
|
testutil.RequireTimeInDelta(t, time.Now().UTC(), actualClaims.AuthTime, timeComparisonFudgeFactor)
|
|
|
|
requestedAtZone, _ := actualClaims.RequestedAt.Zone()
|
|
|
|
require.Equal(t, "UTC", requestedAtZone)
|
|
|
|
authTimeZone, _ := actualClaims.AuthTime.Zone()
|
|
|
|
require.Equal(t, "UTC", authTimeZone)
|
|
|
|
|
|
|
|
// Fosite will set these fields for us in the token endpoint based on the store session
|
|
|
|
// information. Therefore, we assert that they are empty because we want the library to do the
|
|
|
|
// lifting for us.
|
|
|
|
require.Empty(t, actualClaims.Issuer)
|
|
|
|
require.Nil(t, actualClaims.Audience)
|
|
|
|
require.Empty(t, actualClaims.Nonce)
|
|
|
|
require.Zero(t, actualClaims.ExpiresAt)
|
|
|
|
require.Zero(t, actualClaims.IssuedAt)
|
|
|
|
|
|
|
|
// These are not needed yet.
|
|
|
|
require.Empty(t, actualClaims.JTI)
|
|
|
|
require.Empty(t, actualClaims.CodeHash)
|
|
|
|
require.Empty(t, actualClaims.AccessTokenHash)
|
|
|
|
require.Empty(t, actualClaims.AuthenticationContextClassReference)
|
2021-12-10 22:22:36 +00:00
|
|
|
require.Empty(t, actualClaims.AuthenticationMethodsReferences)
|
2021-04-09 00:28:01 +00:00
|
|
|
|
2021-10-08 22:48:21 +00:00
|
|
|
// Check that the custom Pinniped session data matches.
|
|
|
|
require.Equal(t, wantCustomSessionData, storedSessionFromAuthcode.Custom)
|
|
|
|
|
2021-04-09 00:28:01 +00:00
|
|
|
return storedRequestFromAuthcode, storedSessionFromAuthcode
|
|
|
|
}
|
|
|
|
|
|
|
|
func validatePKCEStorage(
|
|
|
|
t *testing.T,
|
|
|
|
oauthStore fositestoragei.AllFositeStorage,
|
|
|
|
storeKey string,
|
|
|
|
storedRequestFromAuthcode *fosite.Request,
|
2021-10-06 22:28:13 +00:00
|
|
|
storedSessionFromAuthcode *psession.PinnipedSession,
|
2021-04-09 00:28:01 +00:00
|
|
|
wantDownstreamPKCEChallenge, wantDownstreamPKCEChallengeMethod string,
|
|
|
|
) {
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
storedAuthorizeRequestFromPKCE, err := oauthStore.GetPKCERequestSession(context.Background(), storeKey, nil)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Check that storage returned the expected concrete data types.
|
|
|
|
storedRequestFromPKCE, storedSessionFromPKCE := castStoredAuthorizeRequest(t, storedAuthorizeRequestFromPKCE)
|
|
|
|
|
|
|
|
// The stored PKCE request should be the same as the stored authcode request.
|
|
|
|
require.Equal(t, storedRequestFromAuthcode.ID, storedRequestFromPKCE.ID)
|
|
|
|
require.Equal(t, storedSessionFromAuthcode, storedSessionFromPKCE)
|
|
|
|
|
|
|
|
// The stored PKCE request should also contain the PKCE challenge that the downstream sent us.
|
|
|
|
require.Equal(t, wantDownstreamPKCEChallenge, storedRequestFromPKCE.Form.Get("code_challenge"))
|
|
|
|
require.Equal(t, wantDownstreamPKCEChallengeMethod, storedRequestFromPKCE.Form.Get("code_challenge_method"))
|
|
|
|
}
|
|
|
|
|
|
|
|
func validateIDSessionStorage(
|
|
|
|
t *testing.T,
|
|
|
|
oauthStore fositestoragei.AllFositeStorage,
|
|
|
|
storeKey string,
|
|
|
|
storedRequestFromAuthcode *fosite.Request,
|
2021-10-06 22:28:13 +00:00
|
|
|
storedSessionFromAuthcode *psession.PinnipedSession,
|
2021-04-09 00:28:01 +00:00
|
|
|
wantDownstreamNonce string,
|
|
|
|
) {
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
storedAuthorizeRequestFromIDSession, err := oauthStore.GetOpenIDConnectSession(context.Background(), storeKey, nil)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Check that storage returned the expected concrete data types.
|
|
|
|
storedRequestFromIDSession, storedSessionFromIDSession := castStoredAuthorizeRequest(t, storedAuthorizeRequestFromIDSession)
|
|
|
|
|
|
|
|
// The stored IDSession request should be the same as the stored authcode request.
|
|
|
|
require.Equal(t, storedRequestFromAuthcode.ID, storedRequestFromIDSession.ID)
|
|
|
|
require.Equal(t, storedSessionFromAuthcode, storedSessionFromIDSession)
|
|
|
|
|
|
|
|
// The stored IDSession request should also contain the nonce that the downstream sent us.
|
|
|
|
require.Equal(t, wantDownstreamNonce, storedRequestFromIDSession.Form.Get("nonce"))
|
|
|
|
}
|
|
|
|
|
2021-10-06 22:28:13 +00:00
|
|
|
func castStoredAuthorizeRequest(t *testing.T, storedAuthorizeRequest fosite.Requester) (*fosite.Request, *psession.PinnipedSession) {
|
2021-04-09 00:28:01 +00:00
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
storedRequest, ok := storedAuthorizeRequest.(*fosite.Request)
|
|
|
|
require.Truef(t, ok, "could not cast %T to %T", storedAuthorizeRequest, &fosite.Request{})
|
2021-10-06 22:28:13 +00:00
|
|
|
storedSession, ok := storedAuthorizeRequest.GetSession().(*psession.PinnipedSession)
|
|
|
|
require.Truef(t, ok, "could not cast %T to %T", storedAuthorizeRequest.GetSession(), &psession.PinnipedSession{})
|
2021-04-09 00:28:01 +00:00
|
|
|
|
|
|
|
return storedRequest, storedSession
|
|
|
|
}
|
2022-07-21 16:26:00 +00:00
|
|
|
|
|
|
|
// FilterClientSecretCreateActions ignores any reads made to get a storage secret corresponding to an OIDCClient, since these
|
|
|
|
// are normal actions when the request is using a dynamic client's client_id, and we don't need to make assertions
|
|
|
|
// about these Secrets since they are not related to session storage.
|
|
|
|
func FilterClientSecretCreateActions(actions []kubetesting.Action) []kubetesting.Action {
|
|
|
|
filtered := make([]kubetesting.Action, 0, len(actions))
|
|
|
|
for _, action := range actions {
|
|
|
|
if action.Matches("get", "secrets") {
|
|
|
|
getAction := action.(kubetesting.GetAction)
|
|
|
|
if strings.HasPrefix(getAction.GetName(), "pinniped-storage-oidc-client-secret-") {
|
|
|
|
continue // filter out OIDCClient's storage secret reads
|
|
|
|
}
|
|
|
|
}
|
|
|
|
filtered = append(filtered, action) // otherwise include the action
|
|
|
|
}
|
|
|
|
return filtered
|
|
|
|
}
|