Require refresh tokens for upstream OIDC and save more session data
- Requiring refresh tokens to be returned from upstream OIDC idps - Storing refresh tokens (for oidc) and idp information (for all idps) in custom session data during authentication - Don't pass access=offline all the time
This commit is contained in:
parent
43244b6599
commit
1bd346cbeb
@ -301,8 +301,9 @@ func (c *activeDirectoryWatcherController) validateUpstream(ctx context.Context,
|
||||
adUpstreamImpl := &activeDirectoryUpstreamGenericLDAPImpl{activeDirectoryIdentityProvider: *upstream}
|
||||
|
||||
config := &upstreamldap.ProviderConfig{
|
||||
Name: upstream.Name,
|
||||
Host: spec.Host,
|
||||
Name: upstream.Name,
|
||||
ResourceUID: upstream.UID,
|
||||
Host: spec.Host,
|
||||
UserSearch: upstreamldap.UserSearchConfig{
|
||||
Base: spec.UserSearch.Base,
|
||||
Filter: adUpstreamImpl.Spec().UserSearch().Filter(),
|
||||
|
@ -150,6 +150,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
|
||||
const (
|
||||
testNamespace = "test-namespace"
|
||||
testResourceUID = "test-uid"
|
||||
testName = "test-name"
|
||||
testSecretName = "test-bind-secret"
|
||||
testBindUsername = "test-bind-username"
|
||||
@ -172,7 +173,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
testCABundleBase64Encoded := base64.StdEncoding.EncodeToString(testCABundle)
|
||||
|
||||
validUpstream := &v1alpha1.ActiveDirectoryIdentityProvider{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: testName, Namespace: testNamespace, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: testName, Namespace: testNamespace, Generation: 1234, UID: testResourceUID},
|
||||
Spec: v1alpha1.ActiveDirectoryIdentityProviderSpec{
|
||||
Host: testHost,
|
||||
TLS: &v1alpha1.TLSSpec{CertificateAuthorityData: testCABundleBase64Encoded},
|
||||
@ -202,6 +203,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
|
||||
providerConfigForValidUpstreamWithTLS := &upstreamldap.ProviderConfig{
|
||||
Name: testName,
|
||||
ResourceUID: testResourceUID,
|
||||
Host: testHost,
|
||||
ConnectionProtocol: upstreamldap.TLS,
|
||||
CABundle: testCABundle,
|
||||
@ -364,7 +366,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{providerConfigForValidUpstreamWithTLS},
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: allConditionsTrue(1234, "4242"),
|
||||
@ -379,7 +381,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{},
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Error",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -407,7 +409,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{},
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Error",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -434,7 +436,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{},
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Error",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -460,7 +462,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{},
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Error",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -486,7 +488,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{},
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Error",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -517,6 +519,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{
|
||||
{
|
||||
Name: testName,
|
||||
ResourceUID: testResourceUID,
|
||||
Host: testHost,
|
||||
ConnectionProtocol: upstreamldap.TLS,
|
||||
CABundle: nil,
|
||||
@ -537,7 +540,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
},
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -572,6 +575,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{
|
||||
{
|
||||
Name: testName,
|
||||
ResourceUID: testResourceUID,
|
||||
Host: testHost,
|
||||
ConnectionProtocol: upstreamldap.TLS,
|
||||
CABundle: nil,
|
||||
@ -592,7 +596,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
},
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -630,6 +634,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{
|
||||
{
|
||||
Name: testName,
|
||||
ResourceUID: testResourceUID,
|
||||
Host: "ldap.example.com",
|
||||
ConnectionProtocol: upstreamldap.StartTLS, // successfully fell back to using StartTLS
|
||||
CABundle: testCABundle,
|
||||
@ -650,7 +655,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
},
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -688,6 +693,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
// even though the connection test failed, still loads into the cache because it is treated like a warning
|
||||
{
|
||||
Name: testName,
|
||||
ResourceUID: testResourceUID,
|
||||
Host: "ldap.example.com:5678",
|
||||
ConnectionProtocol: upstreamldap.TLS, // need to pick TLS or StartTLS to load into the cache when both fail, so choose TLS
|
||||
CABundle: testCABundle,
|
||||
@ -709,7 +715,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Error",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -745,6 +751,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{
|
||||
{
|
||||
Name: testName,
|
||||
ResourceUID: testResourceUID,
|
||||
Host: testHost,
|
||||
ConnectionProtocol: upstreamldap.TLS,
|
||||
CABundle: nil,
|
||||
@ -765,7 +772,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
},
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: allConditionsTrue(1234, "4242"),
|
||||
@ -779,6 +786,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
upstream.Name = "other-upstream"
|
||||
upstream.Generation = 42
|
||||
upstream.Spec.Bind.SecretName = "non-existent-secret"
|
||||
upstream.UID = "other-uid"
|
||||
})},
|
||||
inputSecrets: []runtime.Object{validBindUserSecret("4242")},
|
||||
setupMocks: func(conn *mockldapconn.MockConn) {
|
||||
@ -790,7 +798,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{providerConfigForValidUpstreamWithTLS},
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: "other-upstream", Generation: 42},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: "other-upstream", Generation: 42, UID: "other-uid"},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Error",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -807,7 +815,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: allConditionsTrue(1234, "4242"),
|
||||
@ -831,7 +839,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{providerConfigForValidUpstreamWithTLS},
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Error",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -872,6 +880,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{
|
||||
{
|
||||
Name: testName,
|
||||
ResourceUID: testResourceUID,
|
||||
Host: testHost,
|
||||
ConnectionProtocol: upstreamldap.TLS,
|
||||
CABundle: testCABundle,
|
||||
@ -892,7 +901,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
},
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Error",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -928,7 +937,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Error",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -966,7 +975,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{providerConfigForValidUpstreamWithTLS},
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: allConditionsTrue(1234, "4242"),
|
||||
@ -995,6 +1004,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{
|
||||
{
|
||||
Name: testName,
|
||||
ResourceUID: testResourceUID,
|
||||
Host: testHost,
|
||||
ConnectionProtocol: upstreamldap.TLS,
|
||||
CABundle: testCABundle,
|
||||
@ -1015,7 +1025,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
},
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -1045,6 +1055,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{
|
||||
{
|
||||
Name: testName,
|
||||
ResourceUID: testResourceUID,
|
||||
Host: testHost,
|
||||
ConnectionProtocol: upstreamldap.TLS,
|
||||
CABundle: testCABundle,
|
||||
@ -1065,7 +1076,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
},
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -1100,7 +1111,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{providerConfigForValidUpstreamWithStartTLS},
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: allConditionsTrue(1234, "4242"),
|
||||
@ -1137,7 +1148,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{providerConfigForValidUpstreamWithTLS},
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: allConditionsTrue(1234, "4242"),
|
||||
@ -1175,7 +1186,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{providerConfigForValidUpstreamWithTLS},
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: allConditionsTrue(1234, "4242"),
|
||||
@ -1212,7 +1223,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{providerConfigForValidUpstreamWithTLS},
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: allConditionsTrue(1234, "4242"),
|
||||
@ -1243,6 +1254,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{
|
||||
{
|
||||
Name: testName,
|
||||
ResourceUID: testResourceUID,
|
||||
Host: testHost,
|
||||
ConnectionProtocol: upstreamldap.TLS,
|
||||
CABundle: testCABundle,
|
||||
@ -1264,7 +1276,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
},
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: allConditionsTrue(1234, "4242"),
|
||||
@ -1295,6 +1307,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{
|
||||
{
|
||||
Name: testName,
|
||||
ResourceUID: testResourceUID,
|
||||
Host: testHost,
|
||||
ConnectionProtocol: upstreamldap.TLS,
|
||||
CABundle: testCABundle,
|
||||
@ -1315,7 +1328,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
},
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -1350,6 +1363,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{
|
||||
{
|
||||
Name: testName,
|
||||
ResourceUID: testResourceUID,
|
||||
Host: testHost,
|
||||
ConnectionProtocol: upstreamldap.TLS,
|
||||
CABundle: testCABundle,
|
||||
@ -1370,7 +1384,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
},
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -1399,6 +1413,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{
|
||||
{
|
||||
Name: testName,
|
||||
ResourceUID: testResourceUID,
|
||||
Host: testHost,
|
||||
ConnectionProtocol: upstreamldap.TLS,
|
||||
CABundle: testCABundle,
|
||||
@ -1419,7 +1434,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
},
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -1447,7 +1462,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Error",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -1483,7 +1498,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Error",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -1525,7 +1540,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Error",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -1554,7 +1569,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Error",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -1594,6 +1609,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{
|
||||
{
|
||||
Name: testName,
|
||||
ResourceUID: testResourceUID,
|
||||
Host: testHost,
|
||||
ConnectionProtocol: upstreamldap.TLS,
|
||||
CABundle: testCABundle,
|
||||
@ -1614,7 +1630,7 @@ func TestActiveDirectoryUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
},
|
||||
wantResultingUpstreams: []v1alpha1.ActiveDirectoryIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testResourceUID, Generation: 1234},
|
||||
Status: v1alpha1.ActiveDirectoryIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
|
@ -226,8 +226,9 @@ func (c *ldapWatcherController) validateUpstream(ctx context.Context, upstream *
|
||||
spec := upstream.Spec
|
||||
|
||||
config := &upstreamldap.ProviderConfig{
|
||||
Name: upstream.Name,
|
||||
Host: spec.Host,
|
||||
Name: upstream.Name,
|
||||
ResourceUID: upstream.UID,
|
||||
Host: spec.Host,
|
||||
UserSearch: upstreamldap.UserSearchConfig{
|
||||
Base: spec.UserSearch.Base,
|
||||
Filter: spec.UserSearch.Filter,
|
||||
|
@ -150,6 +150,7 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
||||
const (
|
||||
testNamespace = "test-namespace"
|
||||
testName = "test-name"
|
||||
testResourceUID = "test-resource-uid"
|
||||
testSecretName = "test-bind-secret"
|
||||
testBindUsername = "test-bind-username"
|
||||
testBindPassword = "test-bind-password"
|
||||
@ -171,7 +172,12 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
||||
testCABundleBase64Encoded := base64.StdEncoding.EncodeToString(testCABundle)
|
||||
|
||||
validUpstream := &v1alpha1.LDAPIdentityProvider{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: testName, Namespace: testNamespace, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: testName,
|
||||
Namespace: testNamespace,
|
||||
Generation: 1234,
|
||||
UID: testResourceUID,
|
||||
},
|
||||
Spec: v1alpha1.LDAPIdentityProviderSpec{
|
||||
Host: testHost,
|
||||
TLS: &v1alpha1.TLSSpec{CertificateAuthorityData: testCABundleBase64Encoded},
|
||||
@ -201,6 +207,7 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
||||
|
||||
providerConfigForValidUpstreamWithTLS := &upstreamldap.ProviderConfig{
|
||||
Name: testName,
|
||||
ResourceUID: testResourceUID,
|
||||
Host: testHost,
|
||||
ConnectionProtocol: upstreamldap.TLS,
|
||||
CABundle: testCABundle,
|
||||
@ -299,7 +306,7 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{providerConfigForValidUpstreamWithTLS},
|
||||
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234, UID: testResourceUID},
|
||||
Status: v1alpha1.LDAPIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: allConditionsTrue(1234, "4242"),
|
||||
@ -320,7 +327,7 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{},
|
||||
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234, UID: testResourceUID},
|
||||
Status: v1alpha1.LDAPIdentityProviderStatus{
|
||||
Phase: "Error",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -348,7 +355,7 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{},
|
||||
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234, UID: testResourceUID},
|
||||
Status: v1alpha1.LDAPIdentityProviderStatus{
|
||||
Phase: "Error",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -375,7 +382,7 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{},
|
||||
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234, UID: testResourceUID},
|
||||
Status: v1alpha1.LDAPIdentityProviderStatus{
|
||||
Phase: "Error",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -401,7 +408,7 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{},
|
||||
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234, UID: testResourceUID},
|
||||
Status: v1alpha1.LDAPIdentityProviderStatus{
|
||||
Phase: "Error",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -427,7 +434,7 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{},
|
||||
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234, UID: testResourceUID},
|
||||
Status: v1alpha1.LDAPIdentityProviderStatus{
|
||||
Phase: "Error",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -458,6 +465,7 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{
|
||||
{
|
||||
Name: testName,
|
||||
ResourceUID: testResourceUID,
|
||||
Host: testHost,
|
||||
ConnectionProtocol: upstreamldap.TLS,
|
||||
CABundle: nil,
|
||||
@ -477,7 +485,7 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
},
|
||||
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234, UID: testResourceUID},
|
||||
Status: v1alpha1.LDAPIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -519,6 +527,7 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{
|
||||
{
|
||||
Name: testName,
|
||||
ResourceUID: testResourceUID,
|
||||
Host: "ldap.example.com",
|
||||
ConnectionProtocol: upstreamldap.StartTLS, // successfully fell back to using StartTLS
|
||||
CABundle: testCABundle,
|
||||
@ -538,7 +547,7 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
},
|
||||
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234, UID: testResourceUID},
|
||||
Status: v1alpha1.LDAPIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -580,6 +589,7 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
||||
// even though the connection test failed, still loads into the cache because it is treated like a warning
|
||||
{
|
||||
Name: testName,
|
||||
ResourceUID: testResourceUID,
|
||||
Host: "ldap.example.com:5678",
|
||||
ConnectionProtocol: upstreamldap.TLS, // need to pick TLS or StartTLS to load into the cache when both fail, so choose TLS
|
||||
CABundle: testCABundle,
|
||||
@ -600,7 +610,7 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
||||
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234, UID: testResourceUID},
|
||||
Status: v1alpha1.LDAPIdentityProviderStatus{
|
||||
Phase: "Error",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -635,6 +645,7 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{
|
||||
{
|
||||
Name: testName,
|
||||
ResourceUID: testResourceUID,
|
||||
Host: testHost,
|
||||
ConnectionProtocol: upstreamldap.TLS,
|
||||
CABundle: nil,
|
||||
@ -654,7 +665,7 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
},
|
||||
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234, UID: testResourceUID},
|
||||
Status: v1alpha1.LDAPIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: allConditionsTrue(1234, "4242"),
|
||||
@ -674,6 +685,7 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
||||
upstream.Name = "other-upstream"
|
||||
upstream.Generation = 42
|
||||
upstream.Spec.Bind.SecretName = "non-existent-secret"
|
||||
upstream.UID = "other-uid"
|
||||
})},
|
||||
inputSecrets: []runtime.Object{validBindUserSecret("4242")},
|
||||
setupMocks: func(conn *mockldapconn.MockConn) {
|
||||
@ -685,7 +697,7 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{providerConfigForValidUpstreamWithTLS},
|
||||
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: "other-upstream", Generation: 42},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: "other-upstream", Generation: 42, UID: "other-uid"},
|
||||
Status: v1alpha1.LDAPIdentityProviderStatus{
|
||||
Phase: "Error",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -702,7 +714,7 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234, UID: testResourceUID},
|
||||
Status: v1alpha1.LDAPIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: allConditionsTrue(1234, "4242"),
|
||||
@ -729,7 +741,7 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wantErr: controllerlib.ErrSyntheticRequeue.Error(),
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{providerConfigForValidUpstreamWithTLS},
|
||||
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234, UID: testResourceUID},
|
||||
Status: v1alpha1.LDAPIdentityProviderStatus{
|
||||
Phase: "Error",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -771,7 +783,7 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{providerConfigForValidUpstreamWithTLS},
|
||||
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234, UID: testResourceUID},
|
||||
Status: v1alpha1.LDAPIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: allConditionsTrue(1234, "4242"),
|
||||
@ -805,7 +817,7 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{providerConfigForValidUpstreamWithStartTLS},
|
||||
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234, UID: testResourceUID},
|
||||
Status: v1alpha1.LDAPIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: allConditionsTrue(1234, "4242"),
|
||||
@ -841,7 +853,7 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{providerConfigForValidUpstreamWithTLS},
|
||||
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234, UID: testResourceUID},
|
||||
Status: v1alpha1.LDAPIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: allConditionsTrue(1234, "4242"),
|
||||
@ -877,7 +889,7 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{providerConfigForValidUpstreamWithTLS},
|
||||
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234, UID: testResourceUID},
|
||||
Status: v1alpha1.LDAPIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: allConditionsTrue(1234, "4242"),
|
||||
@ -917,7 +929,7 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{providerConfigForValidUpstreamWithTLS},
|
||||
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234, UID: testResourceUID},
|
||||
Status: v1alpha1.LDAPIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: allConditionsTrue(1234, "4242"),
|
||||
@ -954,7 +966,7 @@ func TestLDAPUpstreamWatcherControllerSync(t *testing.T) {
|
||||
},
|
||||
wantResultingCache: []*upstreamldap.ProviderConfig{providerConfigForValidUpstreamWithTLS},
|
||||
wantResultingUpstreams: []v1alpha1.LDAPIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234, UID: testResourceUID},
|
||||
Status: v1alpha1.LDAPIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: allConditionsTrue(1234, "4242"),
|
||||
|
@ -172,9 +172,11 @@ func (c *oidcWatcherController) validateUpstream(ctx controllerlib.Context, upst
|
||||
Config: &oauth2.Config{
|
||||
Scopes: computeScopes(upstream.Spec.AuthorizationConfig.AdditionalScopes),
|
||||
},
|
||||
UsernameClaim: upstream.Spec.Claims.Username,
|
||||
GroupsClaim: upstream.Spec.Claims.Groups,
|
||||
AllowPasswordGrant: upstream.Spec.AuthorizationConfig.AllowPasswordGrant,
|
||||
UsernameClaim: upstream.Spec.Claims.Username,
|
||||
GroupsClaim: upstream.Spec.Claims.Groups,
|
||||
AllowPasswordGrant: upstream.Spec.AuthorizationConfig.AllowPasswordGrant,
|
||||
AdditionalAuthcodeParams: map[string]string{"prompt": "consent"},
|
||||
ResourceUID: upstream.UID,
|
||||
}
|
||||
conditions := []*v1alpha1.Condition{
|
||||
c.validateSecret(upstream, &result),
|
||||
@ -374,6 +376,7 @@ func computeScopes(additionalScopes []string) []string {
|
||||
// First compute the unique set of scopes, including "openid" (de-duplicate).
|
||||
set := make(map[string]bool, len(additionalScopes)+1)
|
||||
set["openid"] = true
|
||||
set["offline_access"] = true
|
||||
for _, s := range additionalScopes {
|
||||
set[s] = true
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
|
||||
@ -119,16 +120,18 @@ func TestOIDCUpstreamWatcherControllerSync(t *testing.T) {
|
||||
wrongCABase64 := base64.StdEncoding.EncodeToString(wrongCA.Bundle())
|
||||
|
||||
var (
|
||||
testNamespace = "test-namespace"
|
||||
testName = "test-name"
|
||||
testSecretName = "test-client-secret"
|
||||
testAdditionalScopes = []string{"scope1", "scope2", "scope3"}
|
||||
testExpectedScopes = []string{"openid", "scope1", "scope2", "scope3"}
|
||||
testClientID = "test-oidc-client-id"
|
||||
testClientSecret = "test-oidc-client-secret"
|
||||
testValidSecretData = map[string][]byte{"clientID": []byte(testClientID), "clientSecret": []byte(testClientSecret)}
|
||||
testGroupsClaim = "test-groups-claim"
|
||||
testUsernameClaim = "test-username-claim"
|
||||
testNamespace = "test-namespace"
|
||||
testName = "test-name"
|
||||
testSecretName = "test-client-secret"
|
||||
testAdditionalScopes = []string{"scope1", "scope2", "scope3"}
|
||||
testExpectedScopes = []string{"offline_access", "openid", "scope1", "scope2", "scope3"}
|
||||
testExpectedAdditionalParams = map[string]string{"prompt": "consent"}
|
||||
testClientID = "test-oidc-client-id"
|
||||
testClientSecret = "test-oidc-client-secret"
|
||||
testValidSecretData = map[string][]byte{"clientID": []byte(testClientID), "clientSecret": []byte(testClientSecret)}
|
||||
testGroupsClaim = "test-groups-claim"
|
||||
testUsernameClaim = "test-username-claim"
|
||||
testUID = types.UID("test-uid")
|
||||
)
|
||||
tests := []struct {
|
||||
name string
|
||||
@ -561,13 +564,13 @@ Get "` + testIssuerURL + `/valid-url-that-is-really-really-long-nananananananana
|
||||
{
|
||||
name: "upstream with error becomes valid",
|
||||
inputUpstreams: []runtime.Object{&v1alpha1.OIDCIdentityProvider{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: "test-name"},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: "test-name", UID: testUID},
|
||||
Spec: v1alpha1.OIDCIdentityProviderSpec{
|
||||
Issuer: testIssuerURL,
|
||||
TLS: &v1alpha1.TLSSpec{CertificateAuthorityData: testIssuerCABase64},
|
||||
Client: v1alpha1.OIDCClient{SecretName: testSecretName},
|
||||
AuthorizationConfig: v1alpha1.OIDCAuthorizationConfig{
|
||||
AdditionalScopes: append(testAdditionalScopes, "xyz", "openid"),
|
||||
AdditionalScopes: append(testAdditionalScopes, "xyz", "openid", "offline_access"),
|
||||
AllowPasswordGrant: true,
|
||||
},
|
||||
Claims: v1alpha1.OIDCClaims{Groups: testGroupsClaim, Username: testUsernameClaim},
|
||||
@ -591,17 +594,19 @@ Get "` + testIssuerURL + `/valid-url-that-is-really-really-long-nananananananana
|
||||
},
|
||||
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{
|
||||
&oidctestutil.TestUpstreamOIDCIdentityProvider{
|
||||
Name: testName,
|
||||
ClientID: testClientID,
|
||||
AuthorizationURL: *testIssuerAuthorizeURL,
|
||||
Scopes: append(testExpectedScopes, "xyz"),
|
||||
UsernameClaim: testUsernameClaim,
|
||||
GroupsClaim: testGroupsClaim,
|
||||
AllowPasswordGrant: true,
|
||||
Name: testName,
|
||||
ClientID: testClientID,
|
||||
AuthorizationURL: *testIssuerAuthorizeURL,
|
||||
Scopes: append(testExpectedScopes, "xyz"),
|
||||
UsernameClaim: testUsernameClaim,
|
||||
GroupsClaim: testGroupsClaim,
|
||||
AllowPasswordGrant: true,
|
||||
AdditionalAuthcodeParams: testExpectedAdditionalParams,
|
||||
ResourceUID: testUID,
|
||||
},
|
||||
},
|
||||
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, UID: testUID},
|
||||
Status: v1alpha1.OIDCIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -614,7 +619,7 @@ Get "` + testIssuerURL + `/valid-url-that-is-really-really-long-nananananananana
|
||||
{
|
||||
name: "existing valid upstream",
|
||||
inputUpstreams: []runtime.Object{&v1alpha1.OIDCIdentityProvider{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234, UID: testUID},
|
||||
Spec: v1alpha1.OIDCIdentityProviderSpec{
|
||||
Issuer: testIssuerURL,
|
||||
TLS: &v1alpha1.TLSSpec{CertificateAuthorityData: testIssuerCABase64},
|
||||
@ -644,17 +649,19 @@ Get "` + testIssuerURL + `/valid-url-that-is-really-really-long-nananananananana
|
||||
},
|
||||
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{
|
||||
&oidctestutil.TestUpstreamOIDCIdentityProvider{
|
||||
Name: testName,
|
||||
ClientID: testClientID,
|
||||
AuthorizationURL: *testIssuerAuthorizeURL,
|
||||
Scopes: testExpectedScopes,
|
||||
UsernameClaim: testUsernameClaim,
|
||||
GroupsClaim: testGroupsClaim,
|
||||
AllowPasswordGrant: false,
|
||||
Name: testName,
|
||||
ClientID: testClientID,
|
||||
AuthorizationURL: *testIssuerAuthorizeURL,
|
||||
Scopes: testExpectedScopes,
|
||||
UsernameClaim: testUsernameClaim,
|
||||
GroupsClaim: testGroupsClaim,
|
||||
AllowPasswordGrant: false,
|
||||
AdditionalAuthcodeParams: testExpectedAdditionalParams,
|
||||
ResourceUID: testUID,
|
||||
},
|
||||
},
|
||||
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234, UID: testUID},
|
||||
Status: v1alpha1.OIDCIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -667,7 +674,7 @@ Get "` + testIssuerURL + `/valid-url-that-is-really-really-long-nananananananana
|
||||
{
|
||||
name: "existing valid upstream with trailing slash",
|
||||
inputUpstreams: []runtime.Object{&v1alpha1.OIDCIdentityProvider{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234, UID: testUID},
|
||||
Spec: v1alpha1.OIDCIdentityProviderSpec{
|
||||
Issuer: testIssuerURL + "/ends-with-slash/",
|
||||
TLS: &v1alpha1.TLSSpec{CertificateAuthorityData: testIssuerCABase64},
|
||||
@ -694,17 +701,19 @@ Get "` + testIssuerURL + `/valid-url-that-is-really-really-long-nananananananana
|
||||
},
|
||||
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{
|
||||
&oidctestutil.TestUpstreamOIDCIdentityProvider{
|
||||
Name: testName,
|
||||
ClientID: testClientID,
|
||||
AuthorizationURL: *testIssuerAuthorizeURL,
|
||||
Scopes: testExpectedScopes,
|
||||
UsernameClaim: testUsernameClaim,
|
||||
GroupsClaim: testGroupsClaim,
|
||||
AllowPasswordGrant: false,
|
||||
Name: testName,
|
||||
ClientID: testClientID,
|
||||
AuthorizationURL: *testIssuerAuthorizeURL,
|
||||
Scopes: testExpectedScopes,
|
||||
UsernameClaim: testUsernameClaim,
|
||||
GroupsClaim: testGroupsClaim,
|
||||
AllowPasswordGrant: false,
|
||||
AdditionalAuthcodeParams: testExpectedAdditionalParams,
|
||||
ResourceUID: testUID,
|
||||
},
|
||||
},
|
||||
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234},
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, Name: testName, Generation: 1234, UID: testUID},
|
||||
Status: v1alpha1.OIDCIdentityProviderStatus{
|
||||
Phase: "Ready",
|
||||
Conditions: []v1alpha1.Condition{
|
||||
@ -860,6 +869,8 @@ oidc: issuer did not match the issuer returned by provider, expected "` + testIs
|
||||
require.Equal(t, tt.wantResultingCache[i].GetUsernameClaim(), actualIDP.GetUsernameClaim())
|
||||
require.Equal(t, tt.wantResultingCache[i].GetGroupsClaim(), actualIDP.GetGroupsClaim())
|
||||
require.Equal(t, tt.wantResultingCache[i].AllowsPasswordGrant(), actualIDP.AllowsPasswordGrant())
|
||||
require.Equal(t, tt.wantResultingCache[i].GetAdditionalAuthcodeParams(), actualIDP.GetAdditionalAuthcodeParams())
|
||||
require.Equal(t, tt.wantResultingCache[i].GetResourceUID(), actualIDP.GetResourceUID())
|
||||
require.ElementsMatch(t, tt.wantResultingCache[i].GetScopes(), actualIDP.GetScopes())
|
||||
|
||||
// We always want to use the proxy from env on these clients, so although the following assertions
|
||||
|
@ -26,6 +26,7 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/clock"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
@ -343,6 +344,9 @@ func TestFuzzAndJSONNewValidEmptyAuthorizeCodeSession(t *testing.T) {
|
||||
func(s *fosite.TokenType, c fuzz.Continue) {
|
||||
*s = fosite.TokenType(randString(c))
|
||||
},
|
||||
func(s *types.UID, c fuzz.Continue) {
|
||||
*s = types.UID(randString(c))
|
||||
},
|
||||
// handle string type alias
|
||||
func(s *fosite.Arguments, c fuzz.Continue) {
|
||||
n := c.Intn(3) + 1 // 1 to 3 items
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
oidctypes "go.pinniped.dev/pkg/oidcclient/oidctypes"
|
||||
pkce "go.pinniped.dev/pkg/oidcclient/pkce"
|
||||
oauth2 "golang.org/x/oauth2"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
// MockUpstreamOIDCIdentityProviderI is a mock of UpstreamOIDCIdentityProviderI interface.
|
||||
@ -72,6 +73,20 @@ func (mr *MockUpstreamOIDCIdentityProviderIMockRecorder) ExchangeAuthcodeAndVali
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExchangeAuthcodeAndValidateTokens", reflect.TypeOf((*MockUpstreamOIDCIdentityProviderI)(nil).ExchangeAuthcodeAndValidateTokens), arg0, arg1, arg2, arg3, arg4)
|
||||
}
|
||||
|
||||
// GetAdditionalAuthcodeParams mocks base method.
|
||||
func (m *MockUpstreamOIDCIdentityProviderI) GetAdditionalAuthcodeParams() map[string]string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAdditionalAuthcodeParams")
|
||||
ret0, _ := ret[0].(map[string]string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetAdditionalAuthcodeParams indicates an expected call of GetAdditionalAuthcodeParams.
|
||||
func (mr *MockUpstreamOIDCIdentityProviderIMockRecorder) GetAdditionalAuthcodeParams() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAdditionalAuthcodeParams", reflect.TypeOf((*MockUpstreamOIDCIdentityProviderI)(nil).GetAdditionalAuthcodeParams))
|
||||
}
|
||||
|
||||
// GetAuthorizationURL mocks base method.
|
||||
func (m *MockUpstreamOIDCIdentityProviderI) GetAuthorizationURL() *url.URL {
|
||||
m.ctrl.T.Helper()
|
||||
@ -128,6 +143,20 @@ func (mr *MockUpstreamOIDCIdentityProviderIMockRecorder) GetName() *gomock.Call
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetName", reflect.TypeOf((*MockUpstreamOIDCIdentityProviderI)(nil).GetName))
|
||||
}
|
||||
|
||||
// GetResourceUID mocks base method.
|
||||
func (m *MockUpstreamOIDCIdentityProviderI) GetResourceUID() types.UID {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetResourceUID")
|
||||
ret0, _ := ret[0].(types.UID)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetResourceUID indicates an expected call of GetResourceUID.
|
||||
func (mr *MockUpstreamOIDCIdentityProviderIMockRecorder) GetResourceUID() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetResourceUID", reflect.TypeOf((*MockUpstreamOIDCIdentityProviderI)(nil).GetResourceUID))
|
||||
}
|
||||
|
||||
// GetScopes mocks base method.
|
||||
func (m *MockUpstreamOIDCIdentityProviderI) GetScopes() []string {
|
||||
m.ctrl.T.Helper()
|
||||
|
@ -30,7 +30,10 @@ import (
|
||||
"go.pinniped.dev/pkg/oidcclient/pkce"
|
||||
)
|
||||
|
||||
const promptParamNone = "none"
|
||||
const (
|
||||
promptParamName = "prompt"
|
||||
promptParamNone = "none"
|
||||
)
|
||||
|
||||
func NewHandler(
|
||||
downstreamIssuer string,
|
||||
@ -51,13 +54,13 @@ func NewHandler(
|
||||
return httperr.Newf(http.StatusMethodNotAllowed, "%s (try GET or POST)", r.Method)
|
||||
}
|
||||
|
||||
oidcUpstream, ldapUpstream, err := chooseUpstreamIDP(idpLister)
|
||||
oidcUpstream, ldapUpstream, idpType, err := chooseUpstreamIDP(idpLister)
|
||||
if err != nil {
|
||||
plog.WarningErr("authorize upstream config", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if oidcUpstream != nil {
|
||||
if idpType == psession.ProviderTypeOIDC {
|
||||
if len(r.Header.Values(supervisoroidc.AuthorizeUsernameHeaderName)) > 0 {
|
||||
// The client set a username header, so they are trying to log in with a username/password.
|
||||
return handleAuthRequestForOIDCUpstreamPasswordGrant(r, w, oauthHelperWithStorage, oidcUpstream)
|
||||
@ -74,6 +77,7 @@ func NewHandler(
|
||||
return handleAuthRequestForLDAPUpstream(r, w,
|
||||
oauthHelperWithStorage,
|
||||
ldapUpstream,
|
||||
idpType,
|
||||
)
|
||||
}))
|
||||
}
|
||||
@ -83,6 +87,7 @@ func handleAuthRequestForLDAPUpstream(
|
||||
w http.ResponseWriter,
|
||||
oauthHelper fosite.OAuth2Provider,
|
||||
ldapUpstream provider.UpstreamLDAPIdentityProviderI,
|
||||
idpType psession.ProviderType,
|
||||
) error {
|
||||
authorizeRequester, created := newAuthorizeRequest(r, w, oauthHelper)
|
||||
if !created {
|
||||
@ -108,7 +113,14 @@ func handleAuthRequestForLDAPUpstream(
|
||||
username = authenticateResponse.User.GetName()
|
||||
groups := authenticateResponse.User.GetGroups()
|
||||
|
||||
return makeDownstreamSessionAndReturnAuthcodeRedirect(r, w, oauthHelper, authorizeRequester, subject, username, groups)
|
||||
customSessionData := &psession.CustomSessionData{
|
||||
ProviderUID: ldapUpstream.GetResourceUID(),
|
||||
ProviderName: ldapUpstream.GetName(),
|
||||
ProviderType: idpType,
|
||||
}
|
||||
|
||||
return makeDownstreamSessionAndReturnAuthcodeRedirect(r, w,
|
||||
oauthHelper, authorizeRequester, subject, username, groups, customSessionData)
|
||||
}
|
||||
|
||||
func handleAuthRequestForOIDCUpstreamPasswordGrant(
|
||||
@ -147,6 +159,15 @@ func handleAuthRequestForOIDCUpstreamPasswordGrant(
|
||||
fosite.ErrAccessDenied.WithDebug(err.Error())) // WithDebug hides the error from the client
|
||||
}
|
||||
|
||||
if token.RefreshToken == nil || token.RefreshToken.Token == "" {
|
||||
plog.Warning("refresh token not returned by upstream provider during password grant",
|
||||
"upstreamName", oidcUpstream.GetName(),
|
||||
"scopes", oidcUpstream.GetScopes())
|
||||
return writeAuthorizeError(w, oauthHelper, authorizeRequester,
|
||||
fosite.ErrAccessDenied.WithHint(
|
||||
"Refresh token not returned by upstream provider during password grant."))
|
||||
}
|
||||
|
||||
subject, username, groups, err := downstreamsession.GetDownstreamIdentityFromUpstreamIDToken(oidcUpstream, token.IDToken.Claims)
|
||||
if err != nil {
|
||||
// Return a user-friendly error for this case which is entirely within our control.
|
||||
@ -155,7 +176,15 @@ func handleAuthRequestForOIDCUpstreamPasswordGrant(
|
||||
)
|
||||
}
|
||||
|
||||
return makeDownstreamSessionAndReturnAuthcodeRedirect(r, w, oauthHelper, authorizeRequester, subject, username, groups)
|
||||
customSessionData := &psession.CustomSessionData{
|
||||
ProviderUID: oidcUpstream.GetResourceUID(),
|
||||
ProviderName: oidcUpstream.GetName(),
|
||||
ProviderType: psession.ProviderTypeOIDC,
|
||||
OIDC: &psession.OIDCSessionData{
|
||||
UpstreamRefreshToken: token.RefreshToken.Token,
|
||||
},
|
||||
}
|
||||
return makeDownstreamSessionAndReturnAuthcodeRedirect(r, w, oauthHelper, authorizeRequester, subject, username, groups, customSessionData)
|
||||
}
|
||||
|
||||
func handleAuthRequestForOIDCUpstreamAuthcodeGrant(
|
||||
@ -223,17 +252,20 @@ func handleAuthRequestForOIDCUpstreamAuthcodeGrant(
|
||||
}
|
||||
|
||||
authCodeOptions := []oauth2.AuthCodeOption{
|
||||
oauth2.AccessTypeOffline,
|
||||
nonceValue.Param(),
|
||||
pkceValue.Challenge(),
|
||||
pkceValue.Method(),
|
||||
}
|
||||
|
||||
promptParam := r.Form.Get("prompt")
|
||||
promptParam := r.Form.Get(promptParamName)
|
||||
if promptParam == promptParamNone && oidc.ScopeWasRequested(authorizeRequester, coreosoidc.ScopeOpenID) {
|
||||
return writeAuthorizeError(w, oauthHelper, authorizeRequester, fosite.ErrLoginRequired)
|
||||
}
|
||||
|
||||
for key, val := range oidcUpstream.GetAdditionalAuthcodeParams() {
|
||||
authCodeOptions = append(authCodeOptions, oauth2.SetAuthURLParam(key, val))
|
||||
}
|
||||
|
||||
if csrfFromCookie == "" {
|
||||
// We did not receive an incoming CSRF cookie, so write a new one.
|
||||
err := addCSRFSetCookieHeader(w, csrfValue, cookieCodec)
|
||||
@ -280,8 +312,9 @@ func makeDownstreamSessionAndReturnAuthcodeRedirect(
|
||||
subject string,
|
||||
username string,
|
||||
groups []string,
|
||||
customSessionData *psession.CustomSessionData,
|
||||
) error {
|
||||
openIDSession := downstreamsession.MakeDownstreamSession(subject, username, groups)
|
||||
openIDSession := downstreamsession.MakeDownstreamSession(subject, username, groups, customSessionData)
|
||||
|
||||
authorizeResponder, err := oauthHelper.NewAuthorizeResponse(r.Context(), authorizeRequester, openIDSession)
|
||||
if err != nil {
|
||||
@ -340,13 +373,13 @@ func readCSRFCookie(r *http.Request, codec oidc.Decoder) csrftoken.CSRFToken {
|
||||
}
|
||||
|
||||
// Select either an OIDC, an LDAP or an AD IDP, or return an error.
|
||||
func chooseUpstreamIDP(idpLister oidc.UpstreamIdentityProvidersLister) (provider.UpstreamOIDCIdentityProviderI, provider.UpstreamLDAPIdentityProviderI, error) {
|
||||
func chooseUpstreamIDP(idpLister oidc.UpstreamIdentityProvidersLister) (provider.UpstreamOIDCIdentityProviderI, provider.UpstreamLDAPIdentityProviderI, psession.ProviderType, error) {
|
||||
oidcUpstreams := idpLister.GetOIDCIdentityProviders()
|
||||
ldapUpstreams := idpLister.GetLDAPIdentityProviders()
|
||||
adUpstreams := idpLister.GetActiveDirectoryIdentityProviders()
|
||||
switch {
|
||||
case len(oidcUpstreams)+len(ldapUpstreams)+len(adUpstreams) == 0:
|
||||
return nil, nil, httperr.New(
|
||||
return nil, nil, "", httperr.New(
|
||||
http.StatusUnprocessableEntity,
|
||||
"No upstream providers are configured",
|
||||
)
|
||||
@ -362,16 +395,16 @@ func chooseUpstreamIDP(idpLister oidc.UpstreamIdentityProvidersLister) (provider
|
||||
upstreamIDPNames = append(upstreamIDPNames, idp.GetName())
|
||||
}
|
||||
plog.Warning("Too many upstream providers are configured (found: %s)", upstreamIDPNames)
|
||||
return nil, nil, httperr.New(
|
||||
return nil, nil, "", httperr.New(
|
||||
http.StatusUnprocessableEntity,
|
||||
"Too many upstream providers are configured (support for multiple upstreams is not yet implemented)",
|
||||
)
|
||||
case len(oidcUpstreams) == 1:
|
||||
return oidcUpstreams[0], nil, nil
|
||||
return oidcUpstreams[0], nil, psession.ProviderTypeOIDC, nil
|
||||
case len(adUpstreams) == 1:
|
||||
return nil, adUpstreams[0], nil
|
||||
return nil, adUpstreams[0], psession.ProviderTypeActiveDirectory, nil
|
||||
default:
|
||||
return nil, ldapUpstreams[0], nil
|
||||
return nil, ldapUpstreams[0], psession.ProviderTypeLDAP, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,7 @@ import (
|
||||
"go.pinniped.dev/internal/oidc/csrftoken"
|
||||
"go.pinniped.dev/internal/oidc/jwks"
|
||||
"go.pinniped.dev/internal/oidc/provider"
|
||||
"go.pinniped.dev/internal/psession"
|
||||
"go.pinniped.dev/internal/testutil"
|
||||
"go.pinniped.dev/internal/testutil/oidctestutil"
|
||||
"go.pinniped.dev/pkg/oidcclient/nonce"
|
||||
@ -38,16 +39,23 @@ import (
|
||||
|
||||
func TestAuthorizationEndpoint(t *testing.T) {
|
||||
const (
|
||||
oidcUpstreamName = "some-oidc-idp"
|
||||
oidcPasswordGrantUpstreamName = "some-password-granting-oidc-idp"
|
||||
oidcUpstreamName = "some-oidc-idp"
|
||||
oidcUpstreamResourceUID = "oidc-resource-uid"
|
||||
oidcPasswordGrantUpstreamName = "some-password-granting-oidc-idp"
|
||||
oidcPasswordGrantUpstreamResourceUID = "some-password-granting-resource-uid"
|
||||
ldapUpstreamName = "some-ldap-idp"
|
||||
ldapUpstreamResourceUID = "ldap-resource-uid"
|
||||
activeDirectoryUpstreamName = "some-active-directory-idp"
|
||||
activeDirectoryUpstreamResourceUID = "active-directory-resource-uid"
|
||||
|
||||
oidcUpstreamIssuer = "https://my-upstream-issuer.com"
|
||||
oidcUpstreamSubject = "abc123-some guid" // has a space character which should get escaped in URL
|
||||
oidcUpstreamSubjectQueryEscaped = "abc123-some+guid"
|
||||
oidcUpstreamUsername = "test-oidc-pinniped-username"
|
||||
oidcUpstreamPassword = "test-oidc-pinniped-password" //nolint: gosec
|
||||
oidcUpstreamUsernameClaim = "the-user-claim"
|
||||
oidcUpstreamGroupsClaim = "the-groups-claim"
|
||||
oidcUpstreamIssuer = "https://my-upstream-issuer.com"
|
||||
oidcUpstreamSubject = "abc123-some guid" // has a space character which should get escaped in URL
|
||||
oidcUpstreamSubjectQueryEscaped = "abc123-some+guid"
|
||||
oidcUpstreamUsername = "test-oidc-pinniped-username"
|
||||
oidcUpstreamPassword = "test-oidc-pinniped-password" //nolint: gosec
|
||||
oidcUpstreamUsernameClaim = "the-user-claim"
|
||||
oidcUpstreamGroupsClaim = "the-groups-claim"
|
||||
oidcPasswordGrantUpstreamRefreshToken = "some-opaque-token" //nolint: gosec
|
||||
|
||||
downstreamIssuer = "https://my-downstream-issuer.com/some-path"
|
||||
downstreamRedirectURI = "http://127.0.0.1/callback"
|
||||
@ -146,6 +154,12 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
"state": happyState,
|
||||
}
|
||||
|
||||
fositeAccessDeniedWithMissingRefreshTokenErrorQuery = map[string]string{
|
||||
"error": "access_denied",
|
||||
"error_description": "The resource owner or authorization server denied the request. Refresh token not returned by upstream provider during password grant.",
|
||||
"state": happyState,
|
||||
}
|
||||
|
||||
fositeAccessDeniedWithPasswordGrantDisallowedHintErrorQuery = map[string]string{
|
||||
"error": "access_denied",
|
||||
"error_description": "The resource owner or authorization server denied the request. Resource owner password credentials grant is not allowed for this upstream provider according to its configuration.",
|
||||
@ -208,20 +222,22 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
upstreamAuthURL, err := url.Parse("https://some-upstream-idp:8443/auth")
|
||||
require.NoError(t, err)
|
||||
|
||||
upstreamOIDCIdentityProvider := func() *oidctestutil.TestUpstreamOIDCIdentityProvider {
|
||||
upstreamOIDCIdentityProviderBuilder := func() *oidctestutil.TestUpstreamOIDCIdentityProviderBuilder {
|
||||
return oidctestutil.NewTestUpstreamOIDCIdentityProviderBuilder().
|
||||
WithName(oidcUpstreamName).
|
||||
WithResourceUID(oidcUpstreamResourceUID).
|
||||
WithClientID("some-client-id").
|
||||
WithAuthorizationURL(*upstreamAuthURL).
|
||||
WithScopes([]string{"scope1", "scope2"}). // the scopes to request when starting the upstream authorization flow
|
||||
WithAllowPasswordGrant(false).
|
||||
WithPasswordGrantError(errors.New("should not have used password grant on this instance")).
|
||||
Build()
|
||||
WithAdditionalAuthcodeParams(map[string]string{}).
|
||||
WithPasswordGrantError(errors.New("should not have used password grant on this instance"))
|
||||
}
|
||||
|
||||
passwordGrantUpstreamOIDCIdentityProviderBuilder := func() *oidctestutil.TestUpstreamOIDCIdentityProviderBuilder {
|
||||
return oidctestutil.NewTestUpstreamOIDCIdentityProviderBuilder().
|
||||
WithName(oidcPasswordGrantUpstreamName).
|
||||
WithResourceUID(oidcPasswordGrantUpstreamResourceUID).
|
||||
WithClientID("some-client-id").
|
||||
WithAuthorizationURL(*upstreamAuthURL).
|
||||
WithScopes([]string{"scope1", "scope2"}). // the scopes to request when starting the upstream authorization flow
|
||||
@ -234,6 +250,8 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
WithIDTokenClaim(oidcUpstreamGroupsClaim, oidcUpstreamGroupMembership).
|
||||
WithIDTokenClaim("other-claim", "should be ignored").
|
||||
WithAllowPasswordGrant(true).
|
||||
WithRefreshToken(oidcPasswordGrantUpstreamRefreshToken).
|
||||
WithAdditionalAuthcodeParams(map[string]string{"should-be-ignored": "doesn't apply to password grant"}).
|
||||
WithUpstreamAuthcodeExchangeError(errors.New("should not have tried to exchange upstream authcode on this instance"))
|
||||
}
|
||||
|
||||
@ -254,28 +272,39 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
parsedUpstreamLDAPURL, err := url.Parse(upstreamLDAPURL)
|
||||
require.NoError(t, err)
|
||||
|
||||
ldapAuthenticateFunc := func(ctx context.Context, username, password string) (*authenticator.Response, bool, error) {
|
||||
if username == "" || password == "" {
|
||||
return nil, false, fmt.Errorf("should not have passed empty username or password to the authenticator")
|
||||
}
|
||||
if username == happyLDAPUsername && password == happyLDAPPassword {
|
||||
return &authenticator.Response{
|
||||
User: &user.DefaultInfo{
|
||||
Name: happyLDAPUsernameFromAuthenticator,
|
||||
UID: happyLDAPUID,
|
||||
Groups: happyLDAPGroups,
|
||||
},
|
||||
}, true, nil
|
||||
}
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
upstreamLDAPIdentityProvider := oidctestutil.TestUpstreamLDAPIdentityProvider{
|
||||
Name: "some-ldap-idp",
|
||||
URL: parsedUpstreamLDAPURL,
|
||||
AuthenticateFunc: func(ctx context.Context, username, password string) (*authenticator.Response, bool, error) {
|
||||
if username == "" || password == "" {
|
||||
return nil, false, fmt.Errorf("should not have passed empty username or password to the authenticator")
|
||||
}
|
||||
if username == happyLDAPUsername && password == happyLDAPPassword {
|
||||
return &authenticator.Response{
|
||||
User: &user.DefaultInfo{
|
||||
Name: happyLDAPUsernameFromAuthenticator,
|
||||
UID: happyLDAPUID,
|
||||
Groups: happyLDAPGroups,
|
||||
},
|
||||
}, true, nil
|
||||
}
|
||||
return nil, false, nil
|
||||
},
|
||||
Name: ldapUpstreamName,
|
||||
ResourceUID: ldapUpstreamResourceUID,
|
||||
URL: parsedUpstreamLDAPURL,
|
||||
AuthenticateFunc: ldapAuthenticateFunc,
|
||||
}
|
||||
|
||||
upstreamActiveDirectoryIdentityProvider := oidctestutil.TestUpstreamLDAPIdentityProvider{
|
||||
Name: activeDirectoryUpstreamName,
|
||||
ResourceUID: activeDirectoryUpstreamResourceUID,
|
||||
URL: parsedUpstreamLDAPURL,
|
||||
AuthenticateFunc: ldapAuthenticateFunc,
|
||||
}
|
||||
|
||||
erroringUpstreamLDAPIdentityProvider := oidctestutil.TestUpstreamLDAPIdentityProvider{
|
||||
Name: "some-ldap-idp",
|
||||
Name: ldapUpstreamName,
|
||||
ResourceUID: ldapUpstreamResourceUID,
|
||||
AuthenticateFunc: func(ctx context.Context, username, password string) (*authenticator.Response, bool, error) {
|
||||
return nil, false, fmt.Errorf("some ldap upstream auth error")
|
||||
},
|
||||
@ -387,10 +416,9 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
return encoded
|
||||
}
|
||||
|
||||
expectedRedirectLocationForUpstreamOIDC := func(expectedUpstreamState string, expectedPrompt string) string {
|
||||
expectedRedirectLocationForUpstreamOIDC := func(expectedUpstreamState string, expectedAdditionalParams map[string]string) string {
|
||||
query := map[string]string{
|
||||
"response_type": "code",
|
||||
"access_type": "offline",
|
||||
"scope": "scope1 scope2",
|
||||
"client_id": "some-client-id",
|
||||
"state": expectedUpstreamState,
|
||||
@ -399,12 +427,35 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
"code_challenge_method": downstreamPKCEChallengeMethod,
|
||||
"redirect_uri": downstreamIssuer + "/callback",
|
||||
}
|
||||
if expectedPrompt != "" {
|
||||
query["prompt"] = expectedPrompt
|
||||
for key, val := range expectedAdditionalParams {
|
||||
query[key] = val
|
||||
}
|
||||
return urlWithQuery(upstreamAuthURL.String(), query)
|
||||
}
|
||||
|
||||
expectedHappyActiveDirectoryUpstreamCustomSession := &psession.CustomSessionData{
|
||||
ProviderUID: activeDirectoryUpstreamResourceUID,
|
||||
ProviderName: activeDirectoryUpstreamName,
|
||||
ProviderType: psession.ProviderTypeActiveDirectory,
|
||||
OIDC: nil,
|
||||
}
|
||||
|
||||
expectedHappyLDAPUpstreamCustomSession := &psession.CustomSessionData{
|
||||
ProviderUID: ldapUpstreamResourceUID,
|
||||
ProviderName: ldapUpstreamName,
|
||||
ProviderType: psession.ProviderTypeLDAP,
|
||||
OIDC: nil,
|
||||
}
|
||||
|
||||
expectedHappyOIDCPasswordGrantCustomSession := &psession.CustomSessionData{
|
||||
ProviderUID: oidcPasswordGrantUpstreamResourceUID,
|
||||
ProviderName: oidcPasswordGrantUpstreamName,
|
||||
ProviderType: psession.ProviderTypeOIDC,
|
||||
OIDC: &psession.OIDCSessionData{
|
||||
UpstreamRefreshToken: oidcPasswordGrantUpstreamRefreshToken,
|
||||
},
|
||||
}
|
||||
|
||||
// Note that fosite puts the granted scopes as a param in the redirect URI even though the spec doesn't seem to require it
|
||||
happyAuthcodeDownstreamRedirectLocationRegexp := downstreamRedirectURI + `\?code=([^&]+)&scope=openid&state=` + happyState
|
||||
|
||||
@ -452,11 +503,12 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce string
|
||||
wantUnnecessaryStoredRecords int
|
||||
wantPasswordGrantCall *expectedPasswordGrant
|
||||
wantDownstreamCustomSessionData *psession.CustomSessionData
|
||||
}
|
||||
tests := []testCase{
|
||||
{
|
||||
name: "OIDC upstream browser flow happy path using GET without a CSRF cookie",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
generateCSRF: happyCSRFGenerator,
|
||||
generatePKCE: happyPKCEGenerator,
|
||||
generateNonce: happyNonceGenerator,
|
||||
@ -467,7 +519,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
wantStatus: http.StatusFound,
|
||||
wantContentType: htmlContentType,
|
||||
wantCSRFValueInCookieHeader: happyCSRF,
|
||||
wantLocationHeader: expectedRedirectLocationForUpstreamOIDC(expectedUpstreamStateParam(nil, "", ""), ""),
|
||||
wantLocationHeader: expectedRedirectLocationForUpstreamOIDC(expectedUpstreamStateParam(nil, "", ""), nil),
|
||||
wantUpstreamStateParamInLocationHeader: true,
|
||||
wantBodyStringWithLocationInHref: true,
|
||||
},
|
||||
@ -491,6 +543,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: expectedHappyOIDCPasswordGrantCustomSession,
|
||||
},
|
||||
{
|
||||
name: "LDAP upstream happy path using GET",
|
||||
@ -511,10 +564,11 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: expectedHappyLDAPUpstreamCustomSession,
|
||||
},
|
||||
{
|
||||
name: "ActiveDirectory upstream happy path using GET",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamLDAPIdentityProvider),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamActiveDirectoryIdentityProvider),
|
||||
method: http.MethodGet,
|
||||
path: happyGetRequestPath,
|
||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||
@ -531,10 +585,11 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: expectedHappyActiveDirectoryUpstreamCustomSession,
|
||||
},
|
||||
{
|
||||
name: "OIDC upstream browser flow happy path using GET with a CSRF cookie",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
generateCSRF: happyCSRFGenerator,
|
||||
generatePKCE: happyPKCEGenerator,
|
||||
generateNonce: happyNonceGenerator,
|
||||
@ -545,13 +600,13 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
csrfCookie: "__Host-pinniped-csrf=" + encodedIncomingCookieCSRFValue + " ",
|
||||
wantStatus: http.StatusFound,
|
||||
wantContentType: htmlContentType,
|
||||
wantLocationHeader: expectedRedirectLocationForUpstreamOIDC(expectedUpstreamStateParam(nil, incomingCookieCSRFValue, ""), ""),
|
||||
wantLocationHeader: expectedRedirectLocationForUpstreamOIDC(expectedUpstreamStateParam(nil, incomingCookieCSRFValue, ""), nil),
|
||||
wantUpstreamStateParamInLocationHeader: true,
|
||||
wantBodyStringWithLocationInHref: true,
|
||||
},
|
||||
{
|
||||
name: "OIDC upstream browser flow happy path using POST",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
generateCSRF: happyCSRFGenerator,
|
||||
generatePKCE: happyPKCEGenerator,
|
||||
generateNonce: happyNonceGenerator,
|
||||
@ -565,7 +620,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
wantContentType: "",
|
||||
wantBodyString: "",
|
||||
wantCSRFValueInCookieHeader: happyCSRF,
|
||||
wantLocationHeader: expectedRedirectLocationForUpstreamOIDC(expectedUpstreamStateParam(nil, "", ""), ""),
|
||||
wantLocationHeader: expectedRedirectLocationForUpstreamOIDC(expectedUpstreamStateParam(nil, "", ""), nil),
|
||||
wantUpstreamStateParamInLocationHeader: true,
|
||||
},
|
||||
{
|
||||
@ -590,6 +645,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: expectedHappyOIDCPasswordGrantCustomSession,
|
||||
},
|
||||
{
|
||||
name: "LDAP upstream happy path using POST",
|
||||
@ -612,10 +668,11 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: expectedHappyLDAPUpstreamCustomSession,
|
||||
},
|
||||
{
|
||||
name: "Active Directory upstream happy path using POST",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamLDAPIdentityProvider),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamActiveDirectoryIdentityProvider),
|
||||
method: http.MethodPost,
|
||||
path: "/some/path",
|
||||
contentType: "application/x-www-form-urlencoded",
|
||||
@ -634,10 +691,11 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: expectedHappyActiveDirectoryUpstreamCustomSession,
|
||||
},
|
||||
{
|
||||
name: "OIDC upstream browser flow happy path with prompt param other than none that gets ignored",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
generateCSRF: happyCSRFGenerator,
|
||||
generatePKCE: happyPKCEGenerator,
|
||||
generateNonce: happyNonceGenerator,
|
||||
@ -651,12 +709,31 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
wantContentType: htmlContentType,
|
||||
wantBodyStringWithLocationInHref: true,
|
||||
wantCSRFValueInCookieHeader: happyCSRF,
|
||||
wantLocationHeader: expectedRedirectLocationForUpstreamOIDC(expectedUpstreamStateParam(map[string]string{"prompt": "login"}, "", ""), ""),
|
||||
wantLocationHeader: expectedRedirectLocationForUpstreamOIDC(expectedUpstreamStateParam(map[string]string{"prompt": "login"}, "", ""), nil),
|
||||
wantUpstreamStateParamInLocationHeader: true,
|
||||
},
|
||||
{
|
||||
name: "OIDC upstream browser flow happy path with extra params that get passed through",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().WithAdditionalAuthcodeParams(map[string]string{"prompt": "consent", "abc": "123", "def": "456"}).Build()),
|
||||
generateCSRF: happyCSRFGenerator,
|
||||
generatePKCE: happyPKCEGenerator,
|
||||
generateNonce: happyNonceGenerator,
|
||||
stateEncoder: happyStateEncoder,
|
||||
cookieEncoder: happyCookieEncoder,
|
||||
method: http.MethodGet,
|
||||
path: modifiedHappyGetRequestPath(map[string]string{"prompt": "login"}),
|
||||
contentType: "application/x-www-form-urlencoded",
|
||||
body: encodeQuery(happyGetRequestQueryMap),
|
||||
wantStatus: http.StatusFound,
|
||||
wantContentType: htmlContentType,
|
||||
wantBodyStringWithLocationInHref: true,
|
||||
wantCSRFValueInCookieHeader: happyCSRF,
|
||||
wantLocationHeader: expectedRedirectLocationForUpstreamOIDC(expectedUpstreamStateParam(map[string]string{"prompt": "login"}, "", ""), map[string]string{"prompt": "consent", "abc": "123", "def": "456"}),
|
||||
wantUpstreamStateParamInLocationHeader: true,
|
||||
},
|
||||
{
|
||||
name: "OIDC upstream browser flow with prompt param none throws an error because we want to independently decide the upstream prompt param",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
generateCSRF: happyCSRFGenerator,
|
||||
generatePKCE: happyPKCEGenerator,
|
||||
generateNonce: happyNonceGenerator,
|
||||
@ -673,7 +750,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "OIDC upstream browser flow with error while decoding CSRF cookie just generates a new cookie and succeeds as usual",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
generateCSRF: happyCSRFGenerator,
|
||||
generatePKCE: happyPKCEGenerator,
|
||||
generateNonce: happyNonceGenerator,
|
||||
@ -686,13 +763,13 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
wantContentType: htmlContentType,
|
||||
// Generated a new CSRF cookie and set it in the response.
|
||||
wantCSRFValueInCookieHeader: happyCSRF,
|
||||
wantLocationHeader: expectedRedirectLocationForUpstreamOIDC(expectedUpstreamStateParam(nil, "", ""), ""),
|
||||
wantLocationHeader: expectedRedirectLocationForUpstreamOIDC(expectedUpstreamStateParam(nil, "", ""), nil),
|
||||
wantUpstreamStateParamInLocationHeader: true,
|
||||
wantBodyStringWithLocationInHref: true,
|
||||
},
|
||||
{
|
||||
name: "OIDC upstream browser flow happy path when downstream redirect uri matches what is configured for client except for the port number",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
generateCSRF: happyCSRFGenerator,
|
||||
generatePKCE: happyPKCEGenerator,
|
||||
generateNonce: happyNonceGenerator,
|
||||
@ -707,7 +784,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
wantCSRFValueInCookieHeader: happyCSRF,
|
||||
wantLocationHeader: expectedRedirectLocationForUpstreamOIDC(expectedUpstreamStateParam(map[string]string{
|
||||
"redirect_uri": downstreamRedirectURIWithDifferentPort, // not the same port number that is registered for the client
|
||||
}, "", ""), ""),
|
||||
}, "", ""), nil),
|
||||
wantUpstreamStateParamInLocationHeader: true,
|
||||
wantBodyStringWithLocationInHref: true,
|
||||
},
|
||||
@ -733,6 +810,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: expectedHappyOIDCPasswordGrantCustomSession,
|
||||
},
|
||||
{
|
||||
name: "LDAP upstream happy path when downstream redirect uri matches what is configured for client except for the port number",
|
||||
@ -755,10 +833,11 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: expectedHappyLDAPUpstreamCustomSession,
|
||||
},
|
||||
{
|
||||
name: "OIDC upstream browser flow happy path when downstream requested scopes include offline_access",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
generateCSRF: happyCSRFGenerator,
|
||||
generatePKCE: happyPKCEGenerator,
|
||||
generateNonce: happyNonceGenerator,
|
||||
@ -771,7 +850,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
wantCSRFValueInCookieHeader: happyCSRF,
|
||||
wantLocationHeader: expectedRedirectLocationForUpstreamOIDC(expectedUpstreamStateParam(map[string]string{
|
||||
"scope": "openid offline_access",
|
||||
}, "", ""), ""),
|
||||
}, "", ""), nil),
|
||||
wantUpstreamStateParamInLocationHeader: true,
|
||||
wantBodyStringWithLocationInHref: true,
|
||||
},
|
||||
@ -834,7 +913,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "wrong upstream password for Active Directory authentication",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamLDAPIdentityProvider),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamActiveDirectoryIdentityProvider),
|
||||
method: http.MethodGet,
|
||||
path: happyGetRequestPath,
|
||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||
@ -858,7 +937,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "wrong upstream username for Active Directory authentication",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamLDAPIdentityProvider),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamActiveDirectoryIdentityProvider),
|
||||
method: http.MethodGet,
|
||||
path: happyGetRequestPath,
|
||||
customUsernameHeader: pointer.StringPtr("wrong-username"),
|
||||
@ -882,7 +961,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "missing upstream username on request for Active Directory authentication",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamLDAPIdentityProvider),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamActiveDirectoryIdentityProvider),
|
||||
method: http.MethodGet,
|
||||
path: happyGetRequestPath,
|
||||
customUsernameHeader: nil, // do not send header
|
||||
@ -906,7 +985,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "missing upstream password on request for Active Directory authentication",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamLDAPIdentityProvider),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamActiveDirectoryIdentityProvider),
|
||||
method: http.MethodGet,
|
||||
path: happyGetRequestPath,
|
||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||
@ -916,6 +995,32 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingUsernamePasswordHintErrorQuery),
|
||||
wantBodyString: "",
|
||||
},
|
||||
{
|
||||
name: "return an error when upstream IDP did not return a refresh token",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(passwordGrantUpstreamOIDCIdentityProviderBuilder().WithoutRefreshToken().Build()),
|
||||
method: http.MethodGet,
|
||||
path: happyGetRequestPath,
|
||||
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||
wantStatus: http.StatusFound,
|
||||
wantContentType: "application/json; charset=utf-8",
|
||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingRefreshTokenErrorQuery),
|
||||
wantBodyString: "",
|
||||
},
|
||||
{
|
||||
name: "return an error when upstream IDP did not return a refresh token",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(passwordGrantUpstreamOIDCIdentityProviderBuilder().WithEmptyRefreshToken().Build()),
|
||||
method: http.MethodGet,
|
||||
path: happyGetRequestPath,
|
||||
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
||||
customPasswordHeader: pointer.StringPtr(oidcUpstreamPassword),
|
||||
wantPasswordGrantCall: happyUpstreamPasswordGrantMockExpectation,
|
||||
wantStatus: http.StatusFound,
|
||||
wantContentType: "application/json; charset=utf-8",
|
||||
wantLocationHeader: urlWithQuery(downstreamRedirectURI, fositeAccessDeniedWithMissingRefreshTokenErrorQuery),
|
||||
wantBodyString: "",
|
||||
},
|
||||
{
|
||||
name: "missing upstream password on request for OIDC password grant authentication",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(passwordGrantUpstreamOIDCIdentityProviderBuilder().Build()),
|
||||
@ -930,7 +1035,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "using the custom username header on request for OIDC password grant authentication when OIDCIdentityProvider does not allow password grants",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
method: http.MethodGet,
|
||||
path: happyGetRequestPath,
|
||||
customUsernameHeader: pointer.StringPtr(oidcUpstreamUsername),
|
||||
@ -942,7 +1047,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "downstream redirect uri does not match what is configured for client when using OIDC upstream browser flow",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
generateCSRF: happyCSRFGenerator,
|
||||
generatePKCE: happyPKCEGenerator,
|
||||
generateNonce: happyNonceGenerator,
|
||||
@ -984,7 +1089,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "downstream redirect uri does not match what is configured for client when using active directory upstream",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamLDAPIdentityProvider),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamActiveDirectoryIdentityProvider),
|
||||
method: http.MethodGet,
|
||||
path: modifiedHappyGetRequestPath(map[string]string{
|
||||
"redirect_uri": "http://127.0.0.1/does-not-match-what-is-configured-for-pinniped-cli-client",
|
||||
@ -997,7 +1102,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "downstream client does not exist when using OIDC upstream browser flow",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
generateCSRF: happyCSRFGenerator,
|
||||
generatePKCE: happyPKCEGenerator,
|
||||
generateNonce: happyNonceGenerator,
|
||||
@ -1031,7 +1136,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "downstream client does not exist when using active directory upstream",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamLDAPIdentityProvider),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamActiveDirectoryIdentityProvider),
|
||||
method: http.MethodGet,
|
||||
path: modifiedHappyGetRequestPath(map[string]string{"client_id": "invalid-client"}),
|
||||
wantStatus: http.StatusUnauthorized,
|
||||
@ -1040,7 +1145,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "response type is unsupported when using OIDC upstream browser flow",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
generateCSRF: happyCSRFGenerator,
|
||||
generatePKCE: happyPKCEGenerator,
|
||||
generateNonce: happyNonceGenerator,
|
||||
@ -1077,7 +1182,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "response type is unsupported when using active directory upstream",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamLDAPIdentityProvider),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamActiveDirectoryIdentityProvider),
|
||||
method: http.MethodGet,
|
||||
path: modifiedHappyGetRequestPath(map[string]string{"response_type": "unsupported"}),
|
||||
wantStatus: http.StatusFound,
|
||||
@ -1087,7 +1192,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "downstream scopes do not match what is configured for client using OIDC upstream browser flow",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
generateCSRF: happyCSRFGenerator,
|
||||
generatePKCE: happyPKCEGenerator,
|
||||
generateNonce: happyNonceGenerator,
|
||||
@ -1126,7 +1231,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "downstream scopes do not match what is configured for client using Active Directory upstream",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamLDAPIdentityProvider),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamActiveDirectoryIdentityProvider),
|
||||
method: http.MethodGet,
|
||||
path: modifiedHappyGetRequestPath(map[string]string{"scope": "openid tuna"}),
|
||||
customUsernameHeader: pointer.StringPtr(happyLDAPUsername),
|
||||
@ -1138,7 +1243,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "missing response type in request using OIDC upstream browser flow",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
generateCSRF: happyCSRFGenerator,
|
||||
generatePKCE: happyPKCEGenerator,
|
||||
generateNonce: happyNonceGenerator,
|
||||
@ -1175,7 +1280,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "missing response type in request using Active Directory upstream",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamLDAPIdentityProvider),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithActiveDirectory(&upstreamActiveDirectoryIdentityProvider),
|
||||
method: http.MethodGet,
|
||||
path: modifiedHappyGetRequestPath(map[string]string{"response_type": ""}),
|
||||
wantStatus: http.StatusFound,
|
||||
@ -1185,7 +1290,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "missing client id in request using OIDC upstream browser flow",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
generateCSRF: happyCSRFGenerator,
|
||||
generatePKCE: happyPKCEGenerator,
|
||||
generateNonce: happyNonceGenerator,
|
||||
@ -1219,7 +1324,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "missing PKCE code_challenge in request using OIDC upstream browser flow", // See https://tools.ietf.org/html/rfc7636#section-4.4.1
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
generateCSRF: happyCSRFGenerator,
|
||||
generatePKCE: happyPKCEGenerator,
|
||||
generateNonce: happyNonceGenerator,
|
||||
@ -1261,7 +1366,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "invalid value for PKCE code_challenge_method in request using OIDC upstream browser flow", // https://tools.ietf.org/html/rfc7636#section-4.3
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
generateCSRF: happyCSRFGenerator,
|
||||
generatePKCE: happyPKCEGenerator,
|
||||
generateNonce: happyNonceGenerator,
|
||||
@ -1303,7 +1408,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "when PKCE code_challenge_method in request is `plain` using OIDC upstream browser flow", // https://tools.ietf.org/html/rfc7636#section-4.3
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
generateCSRF: happyCSRFGenerator,
|
||||
generatePKCE: happyPKCEGenerator,
|
||||
generateNonce: happyNonceGenerator,
|
||||
@ -1345,7 +1450,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "missing PKCE code_challenge_method in request using OIDC upstream browser flow", // See https://tools.ietf.org/html/rfc7636#section-4.4.1
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
generateCSRF: happyCSRFGenerator,
|
||||
generatePKCE: happyPKCEGenerator,
|
||||
generateNonce: happyNonceGenerator,
|
||||
@ -1389,7 +1494,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
// This is just one of the many OIDC validations run by fosite. This test is to ensure that we are running
|
||||
// through that part of the fosite library when using an OIDC upstream browser flow.
|
||||
name: "prompt param is not allowed to have none and another legal value at the same time using OIDC upstream browser flow",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
generateCSRF: happyCSRFGenerator,
|
||||
generatePKCE: happyPKCEGenerator,
|
||||
generateNonce: happyNonceGenerator,
|
||||
@ -1435,7 +1540,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "happy path: downstream OIDC validations are skipped when the openid scope was not requested using OIDC upstream browser flow",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
generateCSRF: happyCSRFGenerator,
|
||||
generatePKCE: happyPKCEGenerator,
|
||||
generateNonce: happyNonceGenerator,
|
||||
@ -1449,7 +1554,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
wantCSRFValueInCookieHeader: happyCSRF,
|
||||
wantLocationHeader: expectedRedirectLocationForUpstreamOIDC(expectedUpstreamStateParam(
|
||||
map[string]string{"prompt": "none login", "scope": "email"}, "", "",
|
||||
), ""),
|
||||
), nil),
|
||||
wantUpstreamStateParamInLocationHeader: true,
|
||||
wantBodyStringWithLocationInHref: true,
|
||||
},
|
||||
@ -1474,6 +1579,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: expectedHappyOIDCPasswordGrantCustomSession,
|
||||
},
|
||||
{
|
||||
name: "happy path: downstream OIDC validations are skipped when the openid scope was not requested using LDAP upstream",
|
||||
@ -1495,6 +1601,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: expectedHappyLDAPUpstreamCustomSession,
|
||||
},
|
||||
{
|
||||
name: "OIDC upstream password grant: upstream IDP provides no username or group claim configuration, so we use default username claim and skip groups",
|
||||
@ -1518,6 +1625,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: expectedHappyOIDCPasswordGrantCustomSession,
|
||||
},
|
||||
{
|
||||
name: "OIDC upstream password grant: upstream IDP configures username claim as special claim `email` and `email_verified` upstream claim is missing",
|
||||
@ -1543,6 +1651,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: expectedHappyOIDCPasswordGrantCustomSession,
|
||||
},
|
||||
{
|
||||
name: "OIDC upstream password grant: upstream IDP configures username claim as special claim `email` and `email_verified` upstream claim is present with true value",
|
||||
@ -1569,6 +1678,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: expectedHappyOIDCPasswordGrantCustomSession,
|
||||
},
|
||||
{
|
||||
name: "OIDC upstream password grant: upstream IDP configures username claim as anything other than special claim `email` and `email_verified` upstream claim is present with false value",
|
||||
@ -1596,6 +1706,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: expectedHappyOIDCPasswordGrantCustomSession,
|
||||
},
|
||||
{
|
||||
name: "OIDC upstream password grant: upstream IDP configures username claim as special claim `email` and `email_verified` upstream claim is present with illegal value",
|
||||
@ -1655,6 +1766,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: expectedHappyOIDCPasswordGrantCustomSession,
|
||||
},
|
||||
{
|
||||
name: "OIDC upstream password grant: upstream IDP's configured groups claim in the ID token has a non-array value",
|
||||
@ -1679,6 +1791,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: expectedHappyOIDCPasswordGrantCustomSession,
|
||||
},
|
||||
{
|
||||
name: "OIDC upstream password grant: upstream IDP's configured groups claim in the ID token is a slice of interfaces",
|
||||
@ -1703,6 +1816,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: expectedHappyOIDCPasswordGrantCustomSession,
|
||||
},
|
||||
{
|
||||
name: "OIDC upstream password grant: upstream ID token does not contain requested username claim",
|
||||
@ -1741,6 +1855,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: expectedHappyOIDCPasswordGrantCustomSession,
|
||||
},
|
||||
{
|
||||
name: "OIDC upstream password grant: upstream ID token contains username claim with weird format",
|
||||
@ -1909,7 +2024,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "downstream state does not have enough entropy using OIDC upstream browser flow",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
generateCSRF: happyCSRFGenerator,
|
||||
generatePKCE: happyPKCEGenerator,
|
||||
generateNonce: happyNonceGenerator,
|
||||
@ -1948,7 +2063,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "error while encoding upstream state param using OIDC upstream browser flow",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
generateCSRF: happyCSRFGenerator,
|
||||
generatePKCE: happyPKCEGenerator,
|
||||
generateNonce: happyNonceGenerator,
|
||||
@ -1962,7 +2077,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "error while encoding CSRF cookie value for new cookie using OIDC upstream browser flow",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
generateCSRF: happyCSRFGenerator,
|
||||
generatePKCE: happyPKCEGenerator,
|
||||
generateNonce: happyNonceGenerator,
|
||||
@ -1976,7 +2091,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "error while generating CSRF token using OIDC upstream browser flow",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
generateCSRF: sadCSRFGenerator,
|
||||
generatePKCE: happyPKCEGenerator,
|
||||
generateNonce: happyNonceGenerator,
|
||||
@ -1990,7 +2105,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "error while generating nonce using OIDC upstream browser flow",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
generateCSRF: happyCSRFGenerator,
|
||||
generatePKCE: happyPKCEGenerator,
|
||||
generateNonce: sadNonceGenerator,
|
||||
@ -2004,7 +2119,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "error while generating PKCE using OIDC upstream browser flow",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
generateCSRF: happyCSRFGenerator,
|
||||
generatePKCE: sadPKCEGenerator,
|
||||
generateNonce: happyNonceGenerator,
|
||||
@ -2027,7 +2142,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "too many upstream providers are configured: multiple OIDC",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider(), upstreamOIDCIdentityProvider()), // more than one not allowed
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build(), upstreamOIDCIdentityProviderBuilder().Build()), // more than one not allowed
|
||||
method: http.MethodGet,
|
||||
path: happyGetRequestPath,
|
||||
wantStatus: http.StatusUnprocessableEntity,
|
||||
@ -2054,7 +2169,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "too many upstream providers are configured: both OIDC and LDAP",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()).WithLDAP(&upstreamLDAPIdentityProvider), // more than one not allowed
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()).WithLDAP(&upstreamLDAPIdentityProvider), // more than one not allowed
|
||||
method: http.MethodGet,
|
||||
path: happyGetRequestPath,
|
||||
wantStatus: http.StatusUnprocessableEntity,
|
||||
@ -2063,7 +2178,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "too many upstream providers are configured: OIDC, LDAP and AD",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()).WithLDAP(&upstreamLDAPIdentityProvider).WithActiveDirectory(&upstreamLDAPIdentityProvider), // more than one not allowed
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()).WithLDAP(&upstreamLDAPIdentityProvider).WithActiveDirectory(&upstreamActiveDirectoryIdentityProvider), // more than one not allowed
|
||||
method: http.MethodGet,
|
||||
path: happyGetRequestPath,
|
||||
wantStatus: http.StatusUnprocessableEntity,
|
||||
@ -2072,7 +2187,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "PUT is a bad method",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
method: http.MethodPut,
|
||||
path: "/some/path",
|
||||
wantStatus: http.StatusMethodNotAllowed,
|
||||
@ -2081,7 +2196,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "PATCH is a bad method",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
method: http.MethodPatch,
|
||||
path: "/some/path",
|
||||
wantStatus: http.StatusMethodNotAllowed,
|
||||
@ -2090,7 +2205,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "DELETE is a bad method",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProvider()),
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(upstreamOIDCIdentityProviderBuilder().Build()),
|
||||
method: http.MethodDelete,
|
||||
path: "/some/path",
|
||||
wantStatus: http.StatusMethodNotAllowed,
|
||||
@ -2166,6 +2281,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
test.wantDownstreamNonce,
|
||||
downstreamClientID,
|
||||
test.wantDownstreamRedirectURI,
|
||||
test.wantDownstreamCustomSessionData,
|
||||
)
|
||||
default:
|
||||
require.Empty(t, rsp.Header().Values("Location"))
|
||||
@ -2239,6 +2355,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
WithClientID("some-other-new-client-id").
|
||||
WithAuthorizationURL(*upstreamAuthURL).
|
||||
WithScopes([]string{"some-other-new-scope1", "some-other-new-scope2"}).
|
||||
WithAdditionalAuthcodeParams(map[string]string{"prompt": "consent", "abc": "123"}).
|
||||
Build()
|
||||
idpLister.SetOIDCIdentityProviders([]provider.UpstreamOIDCIdentityProviderI{provider.UpstreamOIDCIdentityProviderI(newProviderSettings)})
|
||||
|
||||
@ -2246,7 +2363,8 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
test.wantLocationHeader = urlWithQuery(upstreamAuthURL.String(),
|
||||
map[string]string{
|
||||
"response_type": "code",
|
||||
"access_type": "offline",
|
||||
"prompt": "consent",
|
||||
"abc": "123",
|
||||
"scope": "some-other-new-scope1 some-other-new-scope2", // updated expectation
|
||||
"client_id": "some-other-new-client-id", // updated expectation
|
||||
"state": expectedUpstreamStateParam(
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"go.pinniped.dev/internal/oidc/provider"
|
||||
"go.pinniped.dev/internal/oidc/provider/formposthtml"
|
||||
"go.pinniped.dev/internal/plog"
|
||||
"go.pinniped.dev/internal/psession"
|
||||
)
|
||||
|
||||
func NewHandler(
|
||||
@ -68,12 +69,27 @@ func NewHandler(
|
||||
return httperr.New(http.StatusBadGateway, "error exchanging and validating upstream tokens")
|
||||
}
|
||||
|
||||
if token.RefreshToken == nil || token.RefreshToken.Token == "" {
|
||||
plog.Warning("refresh token not returned by upstream provider during authcode exchange",
|
||||
"upstreamName", upstreamIDPConfig.GetName(),
|
||||
"scopes", upstreamIDPConfig.GetScopes(),
|
||||
"additionalParams", upstreamIDPConfig.GetAdditionalAuthcodeParams())
|
||||
return httperr.New(http.StatusUnprocessableEntity, "refresh token not returned by upstream provider during authcode exchange")
|
||||
}
|
||||
|
||||
subject, username, groups, err := downstreamsession.GetDownstreamIdentityFromUpstreamIDToken(upstreamIDPConfig, token.IDToken.Claims)
|
||||
if err != nil {
|
||||
return httperr.Wrap(http.StatusUnprocessableEntity, err.Error(), err)
|
||||
}
|
||||
|
||||
openIDSession := downstreamsession.MakeDownstreamSession(subject, username, groups)
|
||||
openIDSession := downstreamsession.MakeDownstreamSession(subject, username, groups, &psession.CustomSessionData{
|
||||
ProviderUID: upstreamIDPConfig.GetResourceUID(),
|
||||
ProviderName: upstreamIDPConfig.GetName(),
|
||||
ProviderType: psession.ProviderTypeOIDC,
|
||||
OIDC: &psession.OIDCSessionData{
|
||||
UpstreamRefreshToken: token.RefreshToken.Token,
|
||||
},
|
||||
})
|
||||
|
||||
authorizeResponder, err := oauthHelper.NewAuthorizeResponse(r.Context(), authorizeRequester, openIDSession)
|
||||
if err != nil {
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
|
||||
"go.pinniped.dev/internal/oidc"
|
||||
"go.pinniped.dev/internal/oidc/jwks"
|
||||
"go.pinniped.dev/internal/psession"
|
||||
"go.pinniped.dev/internal/testutil"
|
||||
"go.pinniped.dev/internal/testutil/oidctestutil"
|
||||
"go.pinniped.dev/pkg/oidcclient/nonce"
|
||||
@ -25,9 +26,11 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
happyUpstreamIDPName = "upstream-idp-name"
|
||||
happyUpstreamIDPName = "upstream-idp-name"
|
||||
happyUpstreamIDPResourceUID = "upstream-uid"
|
||||
|
||||
oidcUpstreamIssuer = "https://my-upstream-issuer.com"
|
||||
oidcUpstreamRefreshToken = "test-refresh-token"
|
||||
oidcUpstreamSubject = "abc123-some guid" // has a space character which should get escaped in URL
|
||||
oidcUpstreamSubjectQueryEscaped = "abc123-some+guid"
|
||||
oidcUpstreamUsername = "test-pinniped-username"
|
||||
@ -69,7 +72,13 @@ var (
|
||||
"code_challenge_method": []string{downstreamPKCEChallengeMethod},
|
||||
"redirect_uri": []string{downstreamRedirectURI},
|
||||
}
|
||||
happyDownstreamRequestParams = happyDownstreamRequestParamsQuery.Encode()
|
||||
happyDownstreamRequestParams = happyDownstreamRequestParamsQuery.Encode()
|
||||
happyDownstreamCustomSessionData = &psession.CustomSessionData{
|
||||
ProviderUID: happyUpstreamIDPResourceUID,
|
||||
ProviderName: happyUpstreamIDPName,
|
||||
ProviderType: psession.ProviderTypeOIDC,
|
||||
OIDC: &psession.OIDCSessionData{UpstreamRefreshToken: oidcUpstreamRefreshToken},
|
||||
}
|
||||
)
|
||||
|
||||
func TestCallbackEndpoint(t *testing.T) {
|
||||
@ -130,6 +139,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce string
|
||||
wantDownstreamPKCEChallenge string
|
||||
wantDownstreamPKCEChallengeMethod string
|
||||
wantDownstreamCustomSessionData *psession.CustomSessionData
|
||||
|
||||
wantAuthcodeExchangeCall *expectedAuthcodeExchange
|
||||
}{
|
||||
@ -157,6 +167,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: happyDownstreamCustomSessionData,
|
||||
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||
performedByUpstreamName: happyUpstreamIDPName,
|
||||
args: happyExchangeAndValidateTokensArgs,
|
||||
@ -179,6 +190,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: happyDownstreamCustomSessionData,
|
||||
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||
performedByUpstreamName: happyUpstreamIDPName,
|
||||
args: happyExchangeAndValidateTokensArgs,
|
||||
@ -203,6 +215,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: happyDownstreamCustomSessionData,
|
||||
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||
performedByUpstreamName: happyUpstreamIDPName,
|
||||
args: happyExchangeAndValidateTokensArgs,
|
||||
@ -227,6 +240,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: happyDownstreamCustomSessionData,
|
||||
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||
performedByUpstreamName: happyUpstreamIDPName,
|
||||
args: happyExchangeAndValidateTokensArgs,
|
||||
@ -253,6 +267,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: happyDownstreamCustomSessionData,
|
||||
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||
performedByUpstreamName: happyUpstreamIDPName,
|
||||
args: happyExchangeAndValidateTokensArgs,
|
||||
@ -280,6 +295,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: happyDownstreamCustomSessionData,
|
||||
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||
performedByUpstreamName: happyUpstreamIDPName,
|
||||
args: happyExchangeAndValidateTokensArgs,
|
||||
@ -302,6 +318,34 @@ func TestCallbackEndpoint(t *testing.T) {
|
||||
args: happyExchangeAndValidateTokensArgs,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "return an error when upstream IDP did not return a refresh token",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().WithoutRefreshToken().Build()),
|
||||
method: http.MethodGet,
|
||||
path: newRequestPath().WithState(happyState).String(),
|
||||
csrfCookie: happyCSRFCookie,
|
||||
wantStatus: http.StatusUnprocessableEntity,
|
||||
wantContentType: htmlContentType,
|
||||
wantBody: "Unprocessable Entity: refresh token not returned by upstream provider during authcode exchange\n",
|
||||
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||
performedByUpstreamName: happyUpstreamIDPName,
|
||||
args: happyExchangeAndValidateTokensArgs,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "return an error when upstream IDP returned an empty refresh token",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(happyUpstream().WithEmptyRefreshToken().Build()),
|
||||
method: http.MethodGet,
|
||||
path: newRequestPath().WithState(happyState).String(),
|
||||
csrfCookie: happyCSRFCookie,
|
||||
wantStatus: http.StatusUnprocessableEntity,
|
||||
wantContentType: htmlContentType,
|
||||
wantBody: "Unprocessable Entity: refresh token not returned by upstream provider during authcode exchange\n",
|
||||
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||
performedByUpstreamName: happyUpstreamIDPName,
|
||||
args: happyExchangeAndValidateTokensArgs,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "upstream IDP configures username claim as special claim `email` and `email_verified` upstream claim is present with false value",
|
||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithOIDC(
|
||||
@ -339,6 +383,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: happyDownstreamCustomSessionData,
|
||||
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||
performedByUpstreamName: happyUpstreamIDPName,
|
||||
args: happyExchangeAndValidateTokensArgs,
|
||||
@ -363,6 +408,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: happyDownstreamCustomSessionData,
|
||||
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||
performedByUpstreamName: happyUpstreamIDPName,
|
||||
args: happyExchangeAndValidateTokensArgs,
|
||||
@ -387,6 +433,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: happyDownstreamCustomSessionData,
|
||||
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||
performedByUpstreamName: happyUpstreamIDPName,
|
||||
args: happyExchangeAndValidateTokensArgs,
|
||||
@ -537,6 +584,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: happyDownstreamCustomSessionData,
|
||||
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||
performedByUpstreamName: happyUpstreamIDPName,
|
||||
args: happyExchangeAndValidateTokensArgs,
|
||||
@ -563,6 +611,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: happyDownstreamCustomSessionData,
|
||||
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||
performedByUpstreamName: happyUpstreamIDPName,
|
||||
args: happyExchangeAndValidateTokensArgs,
|
||||
@ -660,6 +709,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
||||
wantDownstreamNonce: downstreamNonce,
|
||||
wantDownstreamPKCEChallenge: downstreamPKCEChallenge,
|
||||
wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod,
|
||||
wantDownstreamCustomSessionData: happyDownstreamCustomSessionData,
|
||||
wantAuthcodeExchangeCall: &expectedAuthcodeExchange{
|
||||
performedByUpstreamName: happyUpstreamIDPName,
|
||||
args: happyExchangeAndValidateTokensArgs,
|
||||
@ -907,6 +957,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
||||
test.wantDownstreamNonce,
|
||||
downstreamClientID,
|
||||
downstreamRedirectURI,
|
||||
test.wantDownstreamCustomSessionData,
|
||||
)
|
||||
|
||||
// Otherwise, expect an empty response body.
|
||||
@ -933,6 +984,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
||||
test.wantDownstreamNonce,
|
||||
downstreamClientID,
|
||||
downstreamRedirectURI,
|
||||
test.wantDownstreamCustomSessionData,
|
||||
)
|
||||
}
|
||||
})
|
||||
@ -1036,6 +1088,7 @@ func (b *upstreamStateParamBuilder) WithStateVersion(version string) *upstreamSt
|
||||
func happyUpstream() *oidctestutil.TestUpstreamOIDCIdentityProviderBuilder {
|
||||
return oidctestutil.NewTestUpstreamOIDCIdentityProviderBuilder().
|
||||
WithName(happyUpstreamIDPName).
|
||||
WithResourceUID(happyUpstreamIDPResourceUID).
|
||||
WithClientID("some-client-id").
|
||||
WithScopes([]string{"scope1", "scope2"}).
|
||||
WithUsernameClaim(oidcUpstreamUsernameClaim).
|
||||
@ -1046,6 +1099,7 @@ func happyUpstream() *oidctestutil.TestUpstreamOIDCIdentityProviderBuilder {
|
||||
WithIDTokenClaim(oidcUpstreamGroupsClaim, oidcUpstreamGroupMembership).
|
||||
WithIDTokenClaim("other-claim", "should be ignored").
|
||||
WithAllowPasswordGrant(false).
|
||||
WithRefreshToken(oidcUpstreamRefreshToken).
|
||||
WithPasswordGrantError(errors.New("the callback endpoint should not use password grants"))
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ const (
|
||||
)
|
||||
|
||||
// MakeDownstreamSession creates a downstream OIDC session.
|
||||
func MakeDownstreamSession(subject string, username string, groups []string) *psession.PinnipedSession {
|
||||
func MakeDownstreamSession(subject string, username string, groups []string, custom *psession.CustomSessionData) *psession.PinnipedSession {
|
||||
now := time.Now().UTC()
|
||||
openIDSession := &psession.PinnipedSession{
|
||||
Fosite: &openid.DefaultSession{
|
||||
@ -46,6 +46,7 @@ func MakeDownstreamSession(subject string, username string, groups []string) *ps
|
||||
AuthTime: now,
|
||||
},
|
||||
},
|
||||
Custom: custom,
|
||||
}
|
||||
if groups == nil {
|
||||
groups = []string{}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
"go.pinniped.dev/internal/authenticators"
|
||||
"go.pinniped.dev/pkg/oidcclient/nonce"
|
||||
@ -24,6 +25,9 @@ type UpstreamOIDCIdentityProviderI interface {
|
||||
// GetClientID returns the OAuth client ID registered with the upstream provider to be used in the authorization code flow.
|
||||
GetClientID() string
|
||||
|
||||
// GetResourceUID returns the Kubernetes resource ID
|
||||
GetResourceUID() types.UID
|
||||
|
||||
// GetAuthorizationURL returns the Authorization Endpoint fetched from discovery.
|
||||
GetAuthorizationURL() *url.URL
|
||||
|
||||
@ -42,6 +46,9 @@ type UpstreamOIDCIdentityProviderI interface {
|
||||
// flow with this upstream provider. When false, it should not be allowed.
|
||||
AllowsPasswordGrant() bool
|
||||
|
||||
// GetAdditionalAuthcodeParams returns additional params to be sent on authcode requests.
|
||||
GetAdditionalAuthcodeParams() map[string]string
|
||||
|
||||
// PasswordCredentialsGrantAndValidateTokens performs upstream OIDC resource owner password credentials grant and
|
||||
// token validation. Returns the validated raw tokens as well as the parsed claims of the ID token.
|
||||
PasswordCredentialsGrantAndValidateTokens(ctx context.Context, username, password string) (*oidctypes.Token, error)
|
||||
@ -68,6 +75,9 @@ type UpstreamLDAPIdentityProviderI interface {
|
||||
// identifier by being combined with the user's UID, since user UIDs are only unique within one provider.
|
||||
GetURL() *url.URL
|
||||
|
||||
// GetResourceUID returns the Kubernetes resource ID
|
||||
GetResourceUID() types.UID
|
||||
|
||||
// UserAuthenticator adds an interface method for performing user authentication against the upstream LDAP provider.
|
||||
authenticators.UserAuthenticator
|
||||
}
|
||||
|
@ -264,6 +264,7 @@ func TestManager(t *testing.T) {
|
||||
"groups": "test-group1",
|
||||
},
|
||||
},
|
||||
RefreshToken: &oidctypes.RefreshToken{Token: "some-opaque-token"},
|
||||
}, nil
|
||||
},
|
||||
}).Build()
|
||||
|
@ -1374,7 +1374,7 @@ func simulateAuthEndpointHavingAlreadyRun(t *testing.T, authRequest *http.Reques
|
||||
Subject: "", // not used, note that callback_handler.go does not set this
|
||||
Username: "", // not used, note that callback_handler.go does not set this
|
||||
},
|
||||
Custom: &psession.PinnipedSessionData{
|
||||
Custom: &psession.CustomSessionData{
|
||||
OIDC: &psession.OIDCSessionData{
|
||||
UpstreamRefreshToken: "starting-fake-refresh-token",
|
||||
},
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/ory/fosite"
|
||||
"github.com/ory/fosite/handler/openid"
|
||||
"github.com/ory/fosite/token/jwt"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
// PinnipedSession is a session container which includes the fosite standard stuff plus custom Pinniped stuff.
|
||||
@ -18,20 +19,20 @@ type PinnipedSession struct {
|
||||
Fosite *openid.DefaultSession `json:"fosite,omitempty"`
|
||||
|
||||
// Custom Pinniped extensions to the session data.
|
||||
Custom *PinnipedSessionData `json:"custom,omitempty"`
|
||||
Custom *CustomSessionData `json:"custom,omitempty"`
|
||||
}
|
||||
|
||||
var _ openid.Session = &PinnipedSession{}
|
||||
|
||||
// PinnipedSessionData is the custom session data needed by Pinniped. It should be treated as a union type,
|
||||
// CustomSessionData is the custom session data needed by Pinniped. It should be treated as a union type,
|
||||
// where the value of ProviderType decides which other fields to use.
|
||||
type PinnipedSessionData struct {
|
||||
type CustomSessionData struct {
|
||||
// The Kubernetes resource UID of the identity provider CRD for the upstream IDP used to start this session.
|
||||
// This should be validated again upon downstream refresh to make sure that we are not refreshing against
|
||||
// a different identity provider CRD which just happens to have the same name.
|
||||
// This implies that when a user deletes an identity provider CRD, then the sessions that were started
|
||||
// using that identity provider will not be able to perform any more downstream refreshes.
|
||||
ProviderUID string `json:"providerUID"`
|
||||
ProviderUID types.UID `json:"providerUID"`
|
||||
|
||||
// The Kubernetes resource name of the identity provider CRD for the upstream IDP used to start this session.
|
||||
// Used during a downstream refresh to decide which upstream to refresh.
|
||||
@ -40,12 +41,20 @@ type PinnipedSessionData struct {
|
||||
|
||||
// The type of the identity provider for the upstream IDP used to start this session.
|
||||
// Used during a downstream refresh to decide which upstream to refresh.
|
||||
ProviderType string `json:"providerType"`
|
||||
ProviderType ProviderType `json:"providerType"`
|
||||
|
||||
// Only used when ProviderType == "oidc".
|
||||
OIDC *OIDCSessionData `json:"oidc,omitempty"`
|
||||
}
|
||||
|
||||
type ProviderType string
|
||||
|
||||
const (
|
||||
ProviderTypeOIDC ProviderType = "oidc"
|
||||
ProviderTypeLDAP ProviderType = "ldap"
|
||||
ProviderTypeActiveDirectory ProviderType = "activedirectory"
|
||||
)
|
||||
|
||||
// OIDCSessionData is the additional data needed by Pinniped when the upstream IDP is an OIDC provider.
|
||||
type OIDCSessionData struct {
|
||||
UpstreamRefreshToken string `json:"upstreamRefreshToken"`
|
||||
@ -58,7 +67,7 @@ func NewPinnipedSession() *PinnipedSession {
|
||||
Claims: &jwt.IDTokenClaims{},
|
||||
Headers: &jwt.Headers{},
|
||||
},
|
||||
Custom: &PinnipedSessionData{},
|
||||
Custom: &CustomSessionData{},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"golang.org/x/oauth2"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
@ -59,12 +60,17 @@ type PasswordCredentialsGrantAndValidateTokensArgs struct {
|
||||
|
||||
type TestUpstreamLDAPIdentityProvider struct {
|
||||
Name string
|
||||
ResourceUID types.UID
|
||||
URL *url.URL
|
||||
AuthenticateFunc func(ctx context.Context, username, password string) (*authenticator.Response, bool, error)
|
||||
}
|
||||
|
||||
var _ provider.UpstreamLDAPIdentityProviderI = &TestUpstreamLDAPIdentityProvider{}
|
||||
|
||||
func (u *TestUpstreamLDAPIdentityProvider) GetResourceUID() types.UID {
|
||||
return u.ResourceUID
|
||||
}
|
||||
|
||||
func (u *TestUpstreamLDAPIdentityProvider) GetName() string {
|
||||
return u.Name
|
||||
}
|
||||
@ -78,13 +84,15 @@ func (u *TestUpstreamLDAPIdentityProvider) GetURL() *url.URL {
|
||||
}
|
||||
|
||||
type TestUpstreamOIDCIdentityProvider struct {
|
||||
Name string
|
||||
ClientID string
|
||||
AuthorizationURL url.URL
|
||||
UsernameClaim string
|
||||
GroupsClaim string
|
||||
Scopes []string
|
||||
AllowPasswordGrant bool
|
||||
Name string
|
||||
ClientID string
|
||||
ResourceUID types.UID
|
||||
AuthorizationURL url.URL
|
||||
UsernameClaim string
|
||||
GroupsClaim string
|
||||
Scopes []string
|
||||
AdditionalAuthcodeParams map[string]string
|
||||
AllowPasswordGrant bool
|
||||
|
||||
ExchangeAuthcodeAndValidateTokensFunc func(
|
||||
ctx context.Context,
|
||||
@ -105,6 +113,16 @@ type TestUpstreamOIDCIdentityProvider struct {
|
||||
passwordCredentialsGrantAndValidateTokensArgs []*PasswordCredentialsGrantAndValidateTokensArgs
|
||||
}
|
||||
|
||||
var _ provider.UpstreamOIDCIdentityProviderI = &TestUpstreamOIDCIdentityProvider{}
|
||||
|
||||
func (u *TestUpstreamOIDCIdentityProvider) GetResourceUID() types.UID {
|
||||
return u.ResourceUID
|
||||
}
|
||||
|
||||
func (u *TestUpstreamOIDCIdentityProvider) GetAdditionalAuthcodeParams() map[string]string {
|
||||
return u.AdditionalAuthcodeParams
|
||||
}
|
||||
|
||||
func (u *TestUpstreamOIDCIdentityProvider) GetName() string {
|
||||
return u.Name
|
||||
}
|
||||
@ -303,16 +321,19 @@ func NewUpstreamIDPListerBuilder() *UpstreamIDPListerBuilder {
|
||||
}
|
||||
|
||||
type TestUpstreamOIDCIdentityProviderBuilder struct {
|
||||
name string
|
||||
clientID string
|
||||
scopes []string
|
||||
idToken map[string]interface{}
|
||||
usernameClaim string
|
||||
groupsClaim string
|
||||
authorizationURL url.URL
|
||||
allowPasswordGrant bool
|
||||
authcodeExchangeErr error
|
||||
passwordGrantErr error
|
||||
name string
|
||||
resourceUID types.UID
|
||||
clientID string
|
||||
scopes []string
|
||||
idToken map[string]interface{}
|
||||
refreshToken *oidctypes.RefreshToken
|
||||
usernameClaim string
|
||||
groupsClaim string
|
||||
authorizationURL url.URL
|
||||
additionalAuthcodeParams map[string]string
|
||||
allowPasswordGrant bool
|
||||
authcodeExchangeErr error
|
||||
passwordGrantErr error
|
||||
}
|
||||
|
||||
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithName(value string) *TestUpstreamOIDCIdentityProviderBuilder {
|
||||
@ -320,6 +341,11 @@ func (u *TestUpstreamOIDCIdentityProviderBuilder) WithName(value string) *TestUp
|
||||
return u
|
||||
}
|
||||
|
||||
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithResourceUID(value types.UID) *TestUpstreamOIDCIdentityProviderBuilder {
|
||||
u.resourceUID = value
|
||||
return u
|
||||
}
|
||||
|
||||
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithClientID(value string) *TestUpstreamOIDCIdentityProviderBuilder {
|
||||
u.clientID = value
|
||||
return u
|
||||
@ -373,6 +399,26 @@ func (u *TestUpstreamOIDCIdentityProviderBuilder) WithoutIDTokenClaim(claim stri
|
||||
return u
|
||||
}
|
||||
|
||||
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithAdditionalAuthcodeParams(params map[string]string) *TestUpstreamOIDCIdentityProviderBuilder {
|
||||
u.additionalAuthcodeParams = params
|
||||
return u
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (u *TestUpstreamOIDCIdentityProviderBuilder) WithUpstreamAuthcodeExchangeError(err error) *TestUpstreamOIDCIdentityProviderBuilder {
|
||||
u.authcodeExchangeErr = err
|
||||
return u
|
||||
@ -385,24 +431,26 @@ func (u *TestUpstreamOIDCIdentityProviderBuilder) WithPasswordGrantError(err err
|
||||
|
||||
func (u *TestUpstreamOIDCIdentityProviderBuilder) Build() *TestUpstreamOIDCIdentityProvider {
|
||||
return &TestUpstreamOIDCIdentityProvider{
|
||||
Name: u.name,
|
||||
ClientID: u.clientID,
|
||||
UsernameClaim: u.usernameClaim,
|
||||
GroupsClaim: u.groupsClaim,
|
||||
Scopes: u.scopes,
|
||||
AllowPasswordGrant: u.allowPasswordGrant,
|
||||
AuthorizationURL: u.authorizationURL,
|
||||
Name: u.name,
|
||||
ClientID: u.clientID,
|
||||
ResourceUID: u.resourceUID,
|
||||
UsernameClaim: u.usernameClaim,
|
||||
GroupsClaim: u.groupsClaim,
|
||||
Scopes: u.scopes,
|
||||
AllowPasswordGrant: u.allowPasswordGrant,
|
||||
AuthorizationURL: u.authorizationURL,
|
||||
AdditionalAuthcodeParams: u.additionalAuthcodeParams,
|
||||
ExchangeAuthcodeAndValidateTokensFunc: func(ctx context.Context, authcode string, pkceCodeVerifier pkce.Code, expectedIDTokenNonce nonce.Nonce) (*oidctypes.Token, error) {
|
||||
if u.authcodeExchangeErr != nil {
|
||||
return nil, u.authcodeExchangeErr
|
||||
}
|
||||
return &oidctypes.Token{IDToken: &oidctypes.IDToken{Claims: u.idToken}}, nil
|
||||
return &oidctypes.Token{IDToken: &oidctypes.IDToken{Claims: u.idToken}, RefreshToken: u.refreshToken}, nil
|
||||
},
|
||||
PasswordCredentialsGrantAndValidateTokensFunc: func(ctx context.Context, username, password string) (*oidctypes.Token, error) {
|
||||
if u.passwordGrantErr != nil {
|
||||
return nil, u.passwordGrantErr
|
||||
}
|
||||
return &oidctypes.Token{IDToken: &oidctypes.IDToken{Claims: u.idToken}}, nil
|
||||
return &oidctypes.Token{IDToken: &oidctypes.IDToken{Claims: u.idToken}, RefreshToken: u.refreshToken}, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -479,6 +527,7 @@ func RequireAuthCodeRegexpMatch(
|
||||
wantDownstreamNonce string,
|
||||
wantDownstreamClientID string,
|
||||
wantDownstreamRedirectURI string,
|
||||
wantCustomSessionData *psession.CustomSessionData,
|
||||
) {
|
||||
t.Helper()
|
||||
|
||||
@ -513,6 +562,7 @@ func RequireAuthCodeRegexpMatch(
|
||||
wantDownstreamRequestedScopes,
|
||||
wantDownstreamClientID,
|
||||
wantDownstreamRedirectURI,
|
||||
wantCustomSessionData,
|
||||
)
|
||||
|
||||
// One PKCE should have been stored.
|
||||
@ -563,6 +613,7 @@ func validateAuthcodeStorage(
|
||||
wantDownstreamRequestedScopes []string,
|
||||
wantDownstreamClientID string,
|
||||
wantDownstreamRedirectURI string,
|
||||
wantCustomSessionData *psession.CustomSessionData,
|
||||
) (*fosite.Request, *psession.PinnipedSession) {
|
||||
t.Helper()
|
||||
|
||||
@ -634,6 +685,9 @@ func validateAuthcodeStorage(
|
||||
require.Empty(t, actualClaims.AuthenticationContextClassReference)
|
||||
require.Empty(t, actualClaims.AuthenticationMethodsReference)
|
||||
|
||||
// Check that the custom Pinniped session data matches.
|
||||
require.Equal(t, wantCustomSessionData, storedSessionFromAuthcode.Custom)
|
||||
|
||||
return storedRequestFromAuthcode, storedSessionFromAuthcode
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ func NewFakePinnipedSession() *psession.PinnipedSession {
|
||||
Username: "snorlax",
|
||||
Subject: "panda",
|
||||
},
|
||||
Custom: &psession.PinnipedSessionData{
|
||||
Custom: &psession.CustomSessionData{
|
||||
ProviderUID: "fake-provider-uid",
|
||||
ProviderType: "fake-provider-type",
|
||||
ProviderName: "fake-provider-name",
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
"github.com/google/uuid"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/utils/trace"
|
||||
@ -82,6 +83,9 @@ type ProviderConfig struct {
|
||||
// Name is the unique name of this upstream LDAP IDP.
|
||||
Name string
|
||||
|
||||
// ResourceUID is the Kubernetes resource UID of this identity provider.
|
||||
ResourceUID types.UID
|
||||
|
||||
// Host is the hostname or "hostname:port" of the LDAP server. When the port is not specified,
|
||||
// the default LDAP port will be used.
|
||||
Host string
|
||||
@ -265,6 +269,10 @@ func (p *Provider) GetName() string {
|
||||
return p.c.Name
|
||||
}
|
||||
|
||||
func (p *Provider) GetResourceUID() types.UID {
|
||||
return p.c.ResourceUID
|
||||
}
|
||||
|
||||
// Return a URL which uniquely identifies this LDAP provider, e.g. "ldaps://host.example.com:1234?base=user-search-base".
|
||||
// This URL is not used for connecting to the provider, but rather is used for creating a globally unique user
|
||||
// identifier by being combined with the user's UID, since user UIDs are only unique within one provider.
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
coreosoidc "github.com/coreos/go-oidc/v3/oidc"
|
||||
"golang.org/x/oauth2"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
|
||||
"go.pinniped.dev/internal/httputil/httperr"
|
||||
@ -31,19 +32,29 @@ func New(config *oauth2.Config, provider *coreosoidc.Provider, client *http.Clie
|
||||
|
||||
// ProviderConfig holds the active configuration of an upstream OIDC provider.
|
||||
type ProviderConfig struct {
|
||||
Name string
|
||||
UsernameClaim string
|
||||
GroupsClaim string
|
||||
Config *oauth2.Config
|
||||
Client *http.Client
|
||||
AllowPasswordGrant bool
|
||||
Provider interface {
|
||||
Name string
|
||||
ResourceUID types.UID
|
||||
UsernameClaim string
|
||||
GroupsClaim string
|
||||
Config *oauth2.Config
|
||||
Client *http.Client
|
||||
AllowPasswordGrant bool
|
||||
AdditionalAuthcodeParams map[string]string
|
||||
Provider interface {
|
||||
Verifier(*coreosoidc.Config) *coreosoidc.IDTokenVerifier
|
||||
Claims(v interface{}) error
|
||||
UserInfo(ctx context.Context, tokenSource oauth2.TokenSource) (*coreosoidc.UserInfo, error)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ProviderConfig) GetResourceUID() types.UID {
|
||||
return p.ResourceUID
|
||||
}
|
||||
|
||||
func (p *ProviderConfig) GetAdditionalAuthcodeParams() map[string]string {
|
||||
return p.AdditionalAuthcodeParams
|
||||
}
|
||||
|
||||
func (p *ProviderConfig) GetName() string {
|
||||
return p.Name
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user