112 lines
5.2 KiB
Go
112 lines
5.2 KiB
Go
// Copyright 2022-2023 the Pinniped contributors. All Rights Reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package testutil
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/crypto/bcrypt"
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
|
|
configv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/config/v1alpha1"
|
|
)
|
|
|
|
const (
|
|
AllDynamicClientScopesSpaceSep = "openid offline_access pinniped:request-audience username groups"
|
|
|
|
// PlaintextPassword1 is a fake client secret for use in unit tests, along with several flavors of the bcrypt
|
|
// hashed version of the password. Do not use for integration tests.
|
|
PlaintextPassword1 = "password1"
|
|
HashedPassword1AtGoMinCost = "$2a$04$JfX1ba/ctAt3AGk73E9Zz.Fdki5GiQtj.O/CnPbRRSKQWWfv1svoe" //nolint:gosec // this is not a credential
|
|
HashedPassword1JustBelowSupervisorMinCost = "$2a$11$w/incy7Z1/ljLYvv2XRg4.WrPgY9oR7phebcgr6rGA3u/5TG9MKOe" //nolint:gosec // this is not a credential
|
|
HashedPassword1AtSupervisorMinCost = "$2a$12$id4i/yFYxS99txKOFEeboea2kU6DyZY0Nh4ul0eR46sDuoFoNTRV." //nolint:gosec // this is not a credential
|
|
HashedPassword1InvalidFormat = "$2a$12$id4i/yFYxS99txKOFEeboea2kU6DyZY0Nh4ul0eR46sDuo" //nolint:gosec // this is not a credential
|
|
|
|
// PlaintextPassword2 is a second fake client secret for use in unit tests, along with several flavors of the bcrypt
|
|
// hashed version of the password. Do not use for integration tests.
|
|
PlaintextPassword2 = "password2"
|
|
HashedPassword2AtGoMinCost = "$2a$04$VQ5z6kkgU8JPLGSGctg.s.iYyoac3Oisa/SIM3sDK5BxTrVbCkyNm" //nolint:gosec // this is not a credential
|
|
HashedPassword2AtSupervisorMinCost = "$2a$12$SdUqoJOn4/3yEQfJx616V.q.f76KaXD.ISgJT1oydqFdgfjJpBh6u" //nolint:gosec // this is not a credential
|
|
)
|
|
|
|
// allDynamicClientScopes returns a slice of all scopes that are supported by the Supervisor for dynamic clients.
|
|
func allDynamicClientScopes() []configv1alpha1.Scope {
|
|
scopes := []configv1alpha1.Scope{}
|
|
for _, s := range strings.Split(AllDynamicClientScopesSpaceSep, " ") {
|
|
scopes = append(scopes, configv1alpha1.Scope(s))
|
|
}
|
|
return scopes
|
|
}
|
|
|
|
func newOIDCClient(
|
|
namespace string,
|
|
clientID string,
|
|
clientUID string,
|
|
redirectURI string,
|
|
allowedGrantTypes []configv1alpha1.GrantType,
|
|
allowedScopes []configv1alpha1.Scope,
|
|
) *configv1alpha1.OIDCClient {
|
|
return &configv1alpha1.OIDCClient{
|
|
ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: clientID, Generation: 1, UID: types.UID(clientUID)},
|
|
Spec: configv1alpha1.OIDCClientSpec{
|
|
AllowedGrantTypes: allowedGrantTypes,
|
|
AllowedScopes: allowedScopes,
|
|
AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(redirectURI)},
|
|
},
|
|
}
|
|
}
|
|
|
|
// OIDCClientValidatorFunc is an interface-like type that allows these test helpers to avoid having a direct dependency
|
|
// on the production code, to avoid circular module dependencies. Implemented by oidcclientvalidator.Validate.
|
|
type OIDCClientValidatorFunc func(oidcClient *configv1alpha1.OIDCClient, secret *corev1.Secret, minBcryptCost int) (bool, []*metav1.Condition, []string)
|
|
|
|
// FullyCapableOIDCClientAndStorageSecret returns an OIDC client which is allowed to use all grant types and all scopes
|
|
// that are supported by the Supervisor for dynamic clients, along with a corresponding client secret storage Secret.
|
|
func FullyCapableOIDCClientAndStorageSecret(
|
|
t *testing.T,
|
|
namespace string,
|
|
clientID string,
|
|
clientUID string,
|
|
redirectURI string,
|
|
hashes []string,
|
|
validateFunc OIDCClientValidatorFunc,
|
|
) (*configv1alpha1.OIDCClient, *corev1.Secret) {
|
|
allScopes := allDynamicClientScopes()
|
|
|
|
allGrantTypes := []configv1alpha1.GrantType{
|
|
"authorization_code", "urn:ietf:params:oauth:grant-type:token-exchange", "refresh_token",
|
|
}
|
|
|
|
return OIDCClientAndStorageSecret(t, namespace, clientID, clientUID, allGrantTypes, allScopes, redirectURI, hashes, validateFunc)
|
|
}
|
|
|
|
// OIDCClientAndStorageSecret returns an OIDC client which is allowed to use the specified grant types and scopes,
|
|
// along with a corresponding client secret storage Secret. It also validates the client to make sure that the specified
|
|
// combination of grant types and scopes is considered valid before returning the client.
|
|
func OIDCClientAndStorageSecret(
|
|
t *testing.T,
|
|
namespace string,
|
|
clientID string,
|
|
clientUID string,
|
|
allowedGrantTypes []configv1alpha1.GrantType,
|
|
allowedScopes []configv1alpha1.Scope,
|
|
redirectURI string,
|
|
hashes []string,
|
|
validateFunc OIDCClientValidatorFunc,
|
|
) (*configv1alpha1.OIDCClient, *corev1.Secret) {
|
|
oidcClient := newOIDCClient(namespace, clientID, clientUID, redirectURI, allowedGrantTypes, allowedScopes)
|
|
secret := OIDCClientSecretStorageSecretForUID(t, namespace, clientUID, hashes)
|
|
|
|
// If a test made an invalid OIDCClient then inform the author of the test, so they can fix the test case.
|
|
// This is an easy mistake to make when writing tests because there are lots of validations on OIDCClients.
|
|
valid, conditions, _ := validateFunc(oidcClient, secret, bcrypt.MinCost)
|
|
require.True(t, valid, "Test's OIDCClient should have been valid. See conditions for errors: %s", conditions)
|
|
|
|
return oidcClient, secret
|
|
}
|