diff --git a/hack/prepare-supervisor-on-kind.sh b/hack/prepare-supervisor-on-kind.sh index 0696e17ba..1fdf4a814 100755 --- a/hack/prepare-supervisor-on-kind.sh +++ b/hack/prepare-supervisor-on-kind.sh @@ -246,7 +246,7 @@ if [[ "$use_oidc_upstream" == "yes" ]]; then fd_idps="${fd_idps}$( cat <(.+)`, - wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped, + wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?idpName=" + happyUpstreamIDPName + "&sub=" + oidcUpstreamSubjectQueryEscaped, wantDownstreamIDTokenUsername: oidcUpstreamUsername, wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership, wantDownstreamRequestedScopes: happyDownstreamScopesRequested, @@ -262,7 +262,7 @@ func TestCallbackEndpoint(t *testing.T) { wantStatus: http.StatusOK, wantContentType: "text/html;charset=UTF-8", wantBodyFormResponseRegexp: `(.+)`, - wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped, + wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?idpName=" + happyUpstreamIDPName + "&sub=" + oidcUpstreamSubjectQueryEscaped, wantDownstreamIDTokenUsername: oidcUpstreamUsername, wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership, wantDownstreamRequestedScopes: happyDownstreamScopesRequested, @@ -290,7 +290,7 @@ func TestCallbackEndpoint(t *testing.T) { wantStatus: http.StatusSeeOther, wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp, wantBody: "", - wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped, + wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?idpName=" + happyUpstreamIDPName + "&sub=" + oidcUpstreamSubjectQueryEscaped, wantDownstreamIDTokenUsername: oidcUpstreamUsername, wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership, wantDownstreamRequestedScopes: happyDownstreamScopesRequested, @@ -315,7 +315,7 @@ func TestCallbackEndpoint(t *testing.T) { wantStatus: http.StatusSeeOther, wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp, wantBody: "", - wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped, + wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?idpName=" + happyUpstreamIDPName + "&sub=" + oidcUpstreamSubjectQueryEscaped, wantDownstreamIDTokenUsername: oidcUpstreamUsername, wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership, wantDownstreamRequestedScopes: happyDownstreamScopesRequested, @@ -339,7 +339,7 @@ func TestCallbackEndpoint(t *testing.T) { wantStatus: http.StatusSeeOther, wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp, wantBody: "", - wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped, + wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?idpName=" + happyUpstreamIDPName + "&sub=" + oidcUpstreamSubjectQueryEscaped, wantDownstreamIDTokenUsername: oidcUpstreamUsername, wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership, wantDownstreamRequestedScopes: happyDownstreamScopesRequested, @@ -373,7 +373,7 @@ func TestCallbackEndpoint(t *testing.T) { wantStatus: http.StatusOK, wantContentType: "text/html;charset=UTF-8", wantBodyFormResponseRegexp: `(.+)`, - wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped, + wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?idpName=" + happyUpstreamIDPName + "&sub=" + oidcUpstreamSubjectQueryEscaped, wantDownstreamIDTokenUsername: oidcUpstreamUsername, wantDownstreamRequestedScopes: []string{"openid"}, wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership, @@ -398,7 +398,7 @@ func TestCallbackEndpoint(t *testing.T) { wantStatus: http.StatusSeeOther, wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp, wantBody: "", - wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped, + wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?idpName=" + happyUpstreamIDPName + "&sub=" + oidcUpstreamSubjectQueryEscaped, wantDownstreamIDTokenUsername: oidcUpstreamUsername, wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership, wantDownstreamRequestedScopes: happyDownstreamScopesRequested, @@ -437,7 +437,7 @@ func TestCallbackEndpoint(t *testing.T) { wantStatus: http.StatusSeeOther, wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp, wantBody: "", - wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped, + wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?idpName=" + happyUpstreamIDPName + "&sub=" + oidcUpstreamSubjectQueryEscaped, wantDownstreamIDTokenUsername: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped, wantDownstreamIDTokenGroups: []string{}, wantDownstreamRequestedScopes: happyDownstreamScopesRequested, @@ -467,7 +467,7 @@ func TestCallbackEndpoint(t *testing.T) { wantStatus: http.StatusSeeOther, wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp, wantBody: "", - wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped, + wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?idpName=" + happyUpstreamIDPName + "&sub=" + oidcUpstreamSubjectQueryEscaped, wantDownstreamIDTokenUsername: "joe@whitehouse.gov", wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership, wantDownstreamRequestedScopes: happyDownstreamScopesRequested, @@ -499,7 +499,7 @@ func TestCallbackEndpoint(t *testing.T) { wantStatus: http.StatusSeeOther, wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp, wantBody: "", - wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped, + wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?idpName=" + happyUpstreamIDPName + "&sub=" + oidcUpstreamSubjectQueryEscaped, wantDownstreamIDTokenUsername: "joe@whitehouse.gov", wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership, wantDownstreamRequestedScopes: happyDownstreamScopesRequested, @@ -532,7 +532,7 @@ func TestCallbackEndpoint(t *testing.T) { wantStatus: http.StatusSeeOther, // succeed despite `email_verified=false` because we're not using the email claim for anything wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp, wantBody: "", - wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped, + wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?idpName=" + happyUpstreamIDPName + "&sub=" + oidcUpstreamSubjectQueryEscaped, wantDownstreamIDTokenUsername: "joe", wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership, wantDownstreamRequestedScopes: happyDownstreamScopesRequested, @@ -667,7 +667,7 @@ func TestCallbackEndpoint(t *testing.T) { wantStatus: http.StatusSeeOther, wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp, wantBody: "", - wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped, + wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?idpName=" + happyUpstreamIDPName + "&sub=" + oidcUpstreamSubjectQueryEscaped, wantDownstreamIDTokenUsername: oidcUpstreamSubject, wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership, wantDownstreamRequestedScopes: happyDownstreamScopesRequested, @@ -697,7 +697,7 @@ func TestCallbackEndpoint(t *testing.T) { wantStatus: http.StatusSeeOther, wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp, wantBody: "", - wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped, + wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?idpName=" + happyUpstreamIDPName + "&sub=" + oidcUpstreamSubjectQueryEscaped, wantDownstreamIDTokenUsername: oidcUpstreamUsername, wantDownstreamIDTokenGroups: []string{"notAnArrayGroup1 notAnArrayGroup2"}, wantDownstreamRequestedScopes: happyDownstreamScopesRequested, @@ -727,7 +727,7 @@ func TestCallbackEndpoint(t *testing.T) { wantStatus: http.StatusSeeOther, wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp, wantBody: "", - wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped, + wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?idpName=" + happyUpstreamIDPName + "&sub=" + oidcUpstreamSubjectQueryEscaped, wantDownstreamIDTokenUsername: oidcUpstreamUsername, wantDownstreamIDTokenGroups: []string{"group1", "group2"}, wantDownstreamRequestedScopes: happyDownstreamScopesRequested, @@ -761,7 +761,7 @@ func TestCallbackEndpoint(t *testing.T) { wantStatus: http.StatusSeeOther, wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=openid\+offline_access\+groups&state=` + happyDownstreamState, wantBody: "", - wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped, + wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?idpName=" + happyUpstreamIDPName + "&sub=" + oidcUpstreamSubjectQueryEscaped, wantDownstreamIDTokenUsername: "", // username scope was not requested wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership, wantDownstreamRequestedScopes: []string{"openid", "groups", "offline_access"}, @@ -791,7 +791,7 @@ func TestCallbackEndpoint(t *testing.T) { wantStatus: http.StatusSeeOther, wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=openid\+offline_access\+username&state=` + happyDownstreamState, wantBody: "", - wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped, + wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?idpName=" + happyUpstreamIDPName + "&sub=" + oidcUpstreamSubjectQueryEscaped, wantDownstreamIDTokenUsername: oidcUpstreamUsername, wantDownstreamIDTokenGroups: nil, // groups scope was not requested wantDownstreamRequestedScopes: []string{"openid", "username", "offline_access"}, @@ -834,7 +834,7 @@ func TestCallbackEndpoint(t *testing.T) { wantStatus: http.StatusSeeOther, wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=openid\+offline_access\+groups&state=` + happyDownstreamState, wantBody: "", - wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped, + wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?idpName=" + happyUpstreamIDPName + "&sub=" + oidcUpstreamSubjectQueryEscaped, wantDownstreamIDTokenUsername: "", // username scope was not requested wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership, wantDownstreamRequestedScopes: []string{"openid", "groups", "offline_access"}, @@ -877,7 +877,7 @@ func TestCallbackEndpoint(t *testing.T) { wantStatus: http.StatusSeeOther, wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=openid\+offline_access\+username&state=` + happyDownstreamState, wantBody: "", - wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped, + wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?idpName=" + happyUpstreamIDPName + "&sub=" + oidcUpstreamSubjectQueryEscaped, wantDownstreamIDTokenUsername: oidcUpstreamUsername, wantDownstreamIDTokenGroups: nil, // groups scope was not requested wantDownstreamRequestedScopes: []string{"openid", "username", "offline_access"}, @@ -902,7 +902,7 @@ func TestCallbackEndpoint(t *testing.T) { wantStatus: http.StatusSeeOther, wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp, wantBody: "", - wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped, + wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?idpName=" + happyUpstreamIDPName + "&sub=" + oidcUpstreamSubjectQueryEscaped, wantDownstreamIDTokenUsername: transformationUsernamePrefix + oidcUpstreamUsername, wantDownstreamIDTokenGroups: testutil.AddPrefixToEach(transformationGroupsPrefix, oidcUpstreamGroupMembership), wantDownstreamRequestedScopes: happyDownstreamScopesRequested, @@ -1158,7 +1158,7 @@ func TestCallbackEndpoint(t *testing.T) { wantStatus: http.StatusSeeOther, wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=username\+groups&state=` + happyDownstreamState, wantDownstreamIDTokenUsername: oidcUpstreamUsername, - wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped, + wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?idpName=" + happyUpstreamIDPName + "&sub=" + oidcUpstreamSubjectQueryEscaped, wantDownstreamRequestedScopes: []string{"profile", "email", "username", "groups"}, wantDownstreamGrantedScopes: []string{"username", "groups"}, wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership, @@ -1188,7 +1188,7 @@ func TestCallbackEndpoint(t *testing.T) { wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=username\+groups&state=` + happyDownstreamState, wantDownstreamIDTokenUsername: oidcUpstreamUsername, wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership, - wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped, + wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?idpName=" + happyUpstreamIDPName + "&sub=" + oidcUpstreamSubjectQueryEscaped, wantDownstreamRequestedScopes: []string{"profile", "email"}, // username and groups scopes were not requested but are granted anyway for the pinniped-cli client for backwards compatibility wantDownstreamGrantedScopes: []string{"username", "groups"}, @@ -1217,7 +1217,7 @@ func TestCallbackEndpoint(t *testing.T) { wantStatus: http.StatusSeeOther, wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=openid\+offline_access\+username\+groups&state=` + happyDownstreamState, wantDownstreamIDTokenUsername: oidcUpstreamUsername, - wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped, + wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?idpName=" + happyUpstreamIDPName + "&sub=" + oidcUpstreamSubjectQueryEscaped, wantDownstreamRequestedScopes: []string{"openid", "offline_access", "username", "groups"}, wantDownstreamGrantedScopes: []string{"openid", "offline_access", "username", "groups"}, wantDownstreamIDTokenGroups: oidcUpstreamGroupMembership, @@ -1315,7 +1315,7 @@ func TestCallbackEndpoint(t *testing.T) { wantStatus: http.StatusSeeOther, wantRedirectLocationRegexp: happyDownstreamRedirectLocationRegexp, wantBody: "", - wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?sub=" + oidcUpstreamSubjectQueryEscaped, + wantDownstreamIDTokenSubject: oidcUpstreamIssuer + "?idpName=" + happyUpstreamIDPName + "&sub=" + oidcUpstreamSubjectQueryEscaped, wantDownstreamIDTokenUsername: oidcUpstreamUsername, wantDownstreamRequestedScopes: happyDownstreamScopesRequested, wantDownstreamGrantedScopes: happyDownstreamScopesGranted, diff --git a/internal/federationdomain/endpoints/login/post_login_handler.go b/internal/federationdomain/endpoints/login/post_login_handler.go index 018ac4ffa..bdd1cb11c 100644 --- a/internal/federationdomain/endpoints/login/post_login_handler.go +++ b/internal/federationdomain/endpoints/login/post_login_handler.go @@ -63,7 +63,9 @@ func NewPostHandler(issuerURL string, upstreamIDPs federationdomainproviders.Fed } // Attempt to authenticate the user with the upstream IDP. - authenticateResponse, authenticated, err := ldapUpstream.Provider.AuthenticateUser(r.Context(), submittedUsername, submittedPassword, authorizeRequester.GetGrantedScopes()) + authenticateResponse, authenticated, err := ldapUpstream.Provider.AuthenticateUser( + r.Context(), submittedUsername, submittedPassword, authorizeRequester.GetGrantedScopes(), + ) if err != nil { plog.WarningErr("unexpected error during upstream LDAP authentication", err, "upstreamName", ldapUpstream.Provider.GetName()) // There was some problem during authentication with the upstream, aside from bad username/password. @@ -80,11 +82,15 @@ func NewPostHandler(issuerURL string, upstreamIDPs federationdomainproviders.Fed // Now the upstream IDP has authenticated the user, so now we're back into the regular OIDC authcode flow steps. // Both success and error responses from this point onwards should look like the usual fosite redirect // responses, and a happy redirect response will include a downstream authcode. - subject := downstreamsession.DownstreamSubjectFromUpstreamLDAP(ldapUpstream.Provider, authenticateResponse) + subject := downstreamsession.DownstreamSubjectFromUpstreamLDAP( + ldapUpstream.Provider, authenticateResponse, ldapUpstream.DisplayName, + ) upstreamUsername := authenticateResponse.User.GetName() upstreamGroups := authenticateResponse.User.GetGroups() - username, groups, err := downstreamsession.ApplyIdentityTransformations(r.Context(), ldapUpstream.Transforms, upstreamUsername, upstreamGroups) + username, groups, err := downstreamsession.ApplyIdentityTransformations( + r.Context(), ldapUpstream.Transforms, upstreamUsername, upstreamGroups, + ) if err != nil { oidc.WriteAuthorizeError(r, w, oauthHelper, authorizeRequester, fosite.ErrAccessDenied.WithHintf("Reason: %s.", err.Error()), false, @@ -92,7 +98,8 @@ func NewPostHandler(issuerURL string, upstreamIDPs federationdomainproviders.Fed return nil } - customSessionData := downstreamsession.MakeDownstreamLDAPOrADCustomSessionData(ldapUpstream.Provider, ldapUpstream.SessionProviderType, authenticateResponse, username, upstreamUsername, upstreamGroups) + customSessionData := downstreamsession.MakeDownstreamLDAPOrADCustomSessionData( + ldapUpstream.Provider, ldapUpstream.SessionProviderType, authenticateResponse, username, upstreamUsername, upstreamGroups) openIDSession := downstreamsession.MakeDownstreamSession(subject, username, groups, authorizeRequester.GetGrantedScopes(), authorizeRequester.GetClient().GetID(), customSessionData, map[string]interface{}{}) oidc.PerformAuthcodeRedirect(r, w, oauthHelper, authorizeRequester, openIDSession, false) diff --git a/internal/federationdomain/endpoints/login/post_login_handler_test.go b/internal/federationdomain/endpoints/login/post_login_handler_test.go index 0a1753336..153cb9ded 100644 --- a/internal/federationdomain/endpoints/login/post_login_handler_test.go +++ b/internal/federationdomain/endpoints/login/post_login_handler_test.go @@ -338,7 +338,7 @@ func TestPostLoginEndpoint(t *testing.T) { wantContentType: htmlContentType, wantBodyString: "", wantRedirectLocationRegexp: happyAuthcodeDownstreamRedirectLocationRegexp, - wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID, + wantDownstreamIDTokenSubject: upstreamLDAPURL + "&idpName=" + ldapUpstreamName + "&sub=" + happyLDAPUID, wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator, wantDownstreamIDTokenGroups: happyLDAPGroups, wantDownstreamRequestedScopes: happyDownstreamScopesRequested, @@ -361,7 +361,7 @@ func TestPostLoginEndpoint(t *testing.T) { wantContentType: htmlContentType, wantBodyString: "", wantRedirectLocationRegexp: happyAuthcodeDownstreamRedirectLocationRegexp, - wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID, + wantDownstreamIDTokenSubject: upstreamLDAPURL + "&idpName=" + ldapUpstreamName + "&sub=" + happyLDAPUID, wantDownstreamIDTokenUsername: transformationUsernamePrefix + happyLDAPUsernameFromAuthenticator, wantDownstreamIDTokenGroups: testutil.AddPrefixToEach(transformationGroupsPrefix, happyLDAPGroups), wantDownstreamRequestedScopes: happyDownstreamScopesRequested, @@ -390,7 +390,7 @@ func TestPostLoginEndpoint(t *testing.T) { wantContentType: htmlContentType, wantBodyString: "", wantRedirectLocationRegexp: happyAuthcodeDownstreamRedirectLocationRegexp, - wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID, + wantDownstreamIDTokenSubject: upstreamLDAPURL + "&idpName=" + ldapUpstreamName + "&sub=" + happyLDAPUID, wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator, wantDownstreamIDTokenGroups: happyLDAPGroups, wantDownstreamRequestedScopes: happyDownstreamScopesRequested, @@ -413,7 +413,7 @@ func TestPostLoginEndpoint(t *testing.T) { wantContentType: htmlContentType, wantBodyString: "", wantRedirectLocationRegexp: happyAuthcodeDownstreamRedirectLocationRegexp, - wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID, + wantDownstreamIDTokenSubject: upstreamLDAPURL + "&idpName=" + activeDirectoryUpstreamName + "&sub=" + happyLDAPUID, wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator, wantDownstreamIDTokenGroups: happyLDAPGroups, wantDownstreamRequestedScopes: happyDownstreamScopesRequested, @@ -436,7 +436,7 @@ func TestPostLoginEndpoint(t *testing.T) { wantContentType: htmlContentType, wantBodyString: "", wantRedirectLocationRegexp: happyAuthcodeDownstreamRedirectLocationRegexp, - wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID, + wantDownstreamIDTokenSubject: upstreamLDAPURL + "&idpName=" + activeDirectoryUpstreamName + "&sub=" + happyLDAPUID, wantDownstreamIDTokenUsername: transformationUsernamePrefix + happyLDAPUsernameFromAuthenticator, wantDownstreamIDTokenGroups: testutil.AddPrefixToEach(transformationGroupsPrefix, happyLDAPGroups), wantDownstreamRequestedScopes: happyDownstreamScopesRequested, @@ -465,7 +465,7 @@ func TestPostLoginEndpoint(t *testing.T) { wantContentType: htmlContentType, wantBodyString: "", wantRedirectLocationRegexp: happyAuthcodeDownstreamRedirectLocationRegexp, - wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID, + wantDownstreamIDTokenSubject: upstreamLDAPURL + "&idpName=" + activeDirectoryUpstreamName + "&sub=" + happyLDAPUID, wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator, wantDownstreamIDTokenGroups: happyLDAPGroups, wantDownstreamRequestedScopes: happyDownstreamScopesRequested, @@ -490,7 +490,7 @@ func TestPostLoginEndpoint(t *testing.T) { wantContentType: htmlContentType, wantBodyFormResponseRegexp: `(?s).*To finish logging in, paste this authorization code` + `.*
.*(.+).*`, // "(?s)" means match "." across newlines - wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID, + wantDownstreamIDTokenSubject: upstreamLDAPURL + "&idpName=" + ldapUpstreamName + "&sub=" + happyLDAPUID, wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator, wantDownstreamIDTokenGroups: happyLDAPGroups, wantDownstreamRequestedScopes: happyDownstreamScopesRequested, @@ -515,7 +515,7 @@ func TestPostLoginEndpoint(t *testing.T) { wantContentType: htmlContentType, wantBodyString: "", wantRedirectLocationRegexp: "http://127.0.0.1:4242/callback" + `\?code=([^&]+)&scope=openid\+username\+groups&state=` + happyDownstreamState, - wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID, + wantDownstreamIDTokenSubject: upstreamLDAPURL + "&idpName=" + ldapUpstreamName + "&sub=" + happyLDAPUID, wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator, wantDownstreamIDTokenGroups: happyLDAPGroups, wantDownstreamRequestedScopes: happyDownstreamScopesRequested, @@ -541,7 +541,7 @@ func TestPostLoginEndpoint(t *testing.T) { wantContentType: htmlContentType, wantBodyString: "", wantRedirectLocationRegexp: "http://127.0.0.1:4242/callback" + `\?code=([^&]+)&scope=openid\+username\+groups&state=` + happyDownstreamState, - wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID, + wantDownstreamIDTokenSubject: upstreamLDAPURL + "&idpName=" + ldapUpstreamName + "&sub=" + happyLDAPUID, wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator, wantDownstreamIDTokenGroups: happyLDAPGroups, wantDownstreamRequestedScopes: happyDownstreamScopesRequested, @@ -567,7 +567,7 @@ func TestPostLoginEndpoint(t *testing.T) { wantBodyString: "", // username and groups scopes were not requested but are granted anyway for the pinniped-cli client for backwards compatibility wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=openid\+offline_access\+pinniped%3Arequest-audience\+username\+groups&state=` + happyDownstreamState, - wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID, + wantDownstreamIDTokenSubject: upstreamLDAPURL + "&idpName=" + ldapUpstreamName + "&sub=" + happyLDAPUID, wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator, wantDownstreamIDTokenGroups: happyLDAPGroups, wantDownstreamRequestedScopes: []string{"openid", "offline_access", "pinniped:request-audience"}, @@ -593,7 +593,7 @@ func TestPostLoginEndpoint(t *testing.T) { wantContentType: htmlContentType, wantBodyString: "", wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=openid\+offline_access\+pinniped%3Arequest-audience&state=` + happyDownstreamState, - wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID, + wantDownstreamIDTokenSubject: upstreamLDAPURL + "&idpName=" + ldapUpstreamName + "&sub=" + happyLDAPUID, wantDownstreamIDTokenUsername: "", // username scope was not requested, so there should be no username in the ID token wantDownstreamIDTokenGroups: []string{}, // groups scope was not requested, so there should be no groups in the ID token wantDownstreamRequestedScopes: []string{"openid", "offline_access", "pinniped:request-audience"}, @@ -627,7 +627,7 @@ func TestPostLoginEndpoint(t *testing.T) { wantContentType: htmlContentType, wantBodyString: "", wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=openid\+offline_access\+groups&state=` + happyDownstreamState, - wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID, + wantDownstreamIDTokenSubject: upstreamLDAPURL + "&idpName=" + ldapUpstreamName + "&sub=" + happyLDAPUID, wantDownstreamIDTokenUsername: "", // username scope was not requested, so there should be no username in the ID token wantDownstreamIDTokenGroups: happyLDAPGroups, wantDownstreamRequestedScopes: []string{"openid", "offline_access", "groups"}, @@ -661,7 +661,7 @@ func TestPostLoginEndpoint(t *testing.T) { wantContentType: htmlContentType, wantBodyString: "", wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=openid\+offline_access\+username&state=` + happyDownstreamState, - wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID, + wantDownstreamIDTokenSubject: upstreamLDAPURL + "&idpName=" + ldapUpstreamName + "&sub=" + happyLDAPUID, wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator, wantDownstreamIDTokenGroups: []string{}, // groups scope was not requested, so there should be no groups in the ID token wantDownstreamRequestedScopes: []string{"openid", "offline_access", "username"}, @@ -691,7 +691,7 @@ func TestPostLoginEndpoint(t *testing.T) { wantBodyString: "", // username and groups scopes were not requested but are granted anyway for the pinniped-cli client for backwards compatibility wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=username\+groups&state=` + happyDownstreamState, - wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID, + wantDownstreamIDTokenSubject: upstreamLDAPURL + "&idpName=" + ldapUpstreamName + "&sub=" + happyLDAPUID, wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator, wantDownstreamIDTokenGroups: happyLDAPGroups, wantDownstreamRequestedScopes: []string{"email"}, // only email was requested @@ -719,7 +719,7 @@ func TestPostLoginEndpoint(t *testing.T) { wantBodyString: "", // username and groups scopes were not requested but are granted anyway for the pinniped-cli client for backwards compatibility wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=openid\+username\+groups&state=` + happyDownstreamState, - wantDownstreamIDTokenSubject: upstreamLDAPURL + "&sub=" + happyLDAPUID, + wantDownstreamIDTokenSubject: upstreamLDAPURL + "&idpName=" + ldapUpstreamName + "&sub=" + happyLDAPUID, wantDownstreamIDTokenUsername: happyLDAPUsernameFromAuthenticator, wantDownstreamIDTokenGroups: happyLDAPGroups, wantDownstreamRequestedScopes: []string{"openid"}, diff --git a/internal/federationdomain/endpoints/token/token_handler.go b/internal/federationdomain/endpoints/token/token_handler.go index 19057685f..53187d71a 100644 --- a/internal/federationdomain/endpoints/token/token_handler.go +++ b/internal/federationdomain/endpoints/token/token_handler.go @@ -394,7 +394,7 @@ func upstreamLDAPRefresh( Groups: oldUntransformedGroups, AdditionalAttributes: additionalAttributes, GrantedScopes: grantedScopes, - }) + }, p.DisplayName) if err != nil { return errUpstreamRefreshError().WithHint( "Upstream refresh failed.").WithTrace(err). diff --git a/internal/federationdomain/upstreamprovider/upsteam_provider.go b/internal/federationdomain/upstreamprovider/upsteam_provider.go index 68695b254..7caeafc1f 100644 --- a/internal/federationdomain/upstreamprovider/upsteam_provider.go +++ b/internal/federationdomain/upstreamprovider/upsteam_provider.go @@ -122,5 +122,5 @@ type UpstreamLDAPIdentityProviderI interface { authenticators.UserAuthenticator // PerformRefresh performs a refresh against the upstream LDAP identity provider - PerformRefresh(ctx context.Context, storedRefreshAttributes RefreshAttributes) (groups []string, err error) + PerformRefresh(ctx context.Context, storedRefreshAttributes RefreshAttributes, idpDisplayName string) (groups []string, err error) } diff --git a/internal/testutil/oidctestutil/oidctestutil.go b/internal/testutil/oidctestutil/oidctestutil.go index bffe5ae73..a3c55591a 100644 --- a/internal/testutil/oidctestutil/oidctestutil.go +++ b/internal/testutil/oidctestutil/oidctestutil.go @@ -220,7 +220,7 @@ func (u *TestUpstreamLDAPIdentityProvider) GetURL() *url.URL { return u.URL } -func (u *TestUpstreamLDAPIdentityProvider) PerformRefresh(ctx context.Context, storedRefreshAttributes upstreamprovider.RefreshAttributes) ([]string, error) { +func (u *TestUpstreamLDAPIdentityProvider) PerformRefresh(ctx context.Context, storedRefreshAttributes upstreamprovider.RefreshAttributes, idpDisplayName string) ([]string, error) { if u.performRefreshArgs == nil { u.performRefreshArgs = make([]*PerformRefreshArgs, 0) } diff --git a/internal/upstreamldap/upstreamldap.go b/internal/upstreamldap/upstreamldap.go index b178adf2e..ef1c432a6 100644 --- a/internal/upstreamldap/upstreamldap.go +++ b/internal/upstreamldap/upstreamldap.go @@ -188,7 +188,7 @@ func closeAndLogError(conn Conn, doingWhat string) { } } -func (p *Provider) PerformRefresh(ctx context.Context, storedRefreshAttributes upstreamprovider.RefreshAttributes) ([]string, error) { +func (p *Provider) PerformRefresh(ctx context.Context, storedRefreshAttributes upstreamprovider.RefreshAttributes, idpDisplayName string) ([]string, error) { t := trace.FromContext(ctx).Nest("slow ldap refresh attempt", trace.Field{Key: "providerName", Value: p.GetName()}) defer t.LogIfLong(500 * time.Millisecond) // to help users debug slow LDAP searches userDN := storedRefreshAttributes.DN @@ -237,7 +237,7 @@ func (p *Provider) PerformRefresh(ctx context.Context, storedRefreshAttributes u if err != nil { return nil, err } - newSubject := downstreamsession.DownstreamLDAPSubject(newUID, *p.GetURL()) + newSubject := downstreamsession.DownstreamLDAPSubject(newUID, *p.GetURL(), idpDisplayName) if newSubject != storedRefreshAttributes.Subject { return nil, fmt.Errorf(`searching for user %q produced a different subject than the previous value. expected: %q, actual: %q`, userDN, storedRefreshAttributes.Subject, newSubject) } diff --git a/internal/upstreamldap/upstreamldap_test.go b/internal/upstreamldap/upstreamldap_test.go index d07c2c9e1..e511d66e6 100644 --- a/internal/upstreamldap/upstreamldap_test.go +++ b/internal/upstreamldap/upstreamldap_test.go @@ -36,6 +36,7 @@ const ( testHost = "ldap.example.com:8443" testBindUsername = "cn=some-bind-username,dc=pinniped,dc=dev" testBindPassword = "some-bind-password" + testUpstreamName = "some-upstream-idp-name" testUpstreamUsername = "some-upstream-username" testUpstreamPassword = "some-upstream-password" testUserSearchBase = "some-upstream-user-base-dn" @@ -2046,7 +2047,7 @@ func TestUpstreamRefresh(t *testing.T) { }, nil).Times(1) conn.EXPECT().Close().Times(1) }, - wantErr: "searching for user \"some-upstream-user-dn\" produced a different subject than the previous value. expected: \"ldaps://ldap.example.com:8443?base=some-upstream-user-base-dn&sub=c29tZS11cHN0cmVhbS11aWQtdmFsdWU\", actual: \"ldaps://ldap.example.com:8443?base=some-upstream-user-base-dn&sub=d3JvbmctdWlk\"", + wantErr: "searching for user \"some-upstream-user-dn\" produced a different subject than the previous value. expected: \"ldaps://ldap.example.com:8443?base=some-upstream-user-base-dn&idpName=some-upstream-idp-name&sub=c29tZS11cHN0cmVhbS11aWQtdmFsdWU\", actual: \"ldaps://ldap.example.com:8443?base=some-upstream-user-base-dn&idpName=some-upstream-idp-name&sub=d3JvbmctdWlk\"", }, { name: "search result has wrong username", @@ -2279,14 +2280,17 @@ func TestUpstreamRefresh(t *testing.T) { } initialPwdLastSetEncoded := base64.RawURLEncoding.EncodeToString([]byte("132801740800000000")) ldapProvider := New(*tt.providerConfig) - subject := "ldaps://ldap.example.com:8443?base=some-upstream-user-base-dn&sub=c29tZS11cHN0cmVhbS11aWQtdmFsdWU" + subject := fmt.Sprintf( + "ldaps://ldap.example.com:8443?base=some-upstream-user-base-dn&idpName=%s&sub=c29tZS11cHN0cmVhbS11aWQtdmFsdWU", + testUpstreamName, + ) groups, err := ldapProvider.PerformRefresh(context.Background(), upstreamprovider.RefreshAttributes{ Username: testUserSearchResultUsernameAttributeValue, Subject: subject, DN: tt.refreshUserDN, AdditionalAttributes: map[string]string{pwdLastSetAttribute: initialPwdLastSetEncoded}, GrantedScopes: tt.grantedScopes, - }) + }, testUpstreamName) if tt.wantErr != "" { require.Error(t, err) require.Equal(t, tt.wantErr, err.Error()) diff --git a/test/integration/supervisor_login_test.go b/test/integration/supervisor_login_test.go index e7d25ecf4..0d4f2912c 100644 --- a/test/integration/supervisor_login_test.go +++ b/test/integration/supervisor_login_test.go @@ -162,6 +162,49 @@ func TestSupervisorLogin_Browser(t *testing.T) { return ldapIDP, secret } + // The downstream ID token Subject should include the upstream user ID after the upstream issuer name + // and IDP display name. + expectedIDTokenSubjectRegexForUpstreamOIDC := "^" + + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?idpName=test-upstream-oidc-idp-") + `[\w]+` + + regexp.QuoteMeta("&sub=") + ".+" + + "$" + + // The downstream ID token Subject should be the Host URL, plus the user search base, plus the IDP display name, + // plus value pulled from the requested UserSearch.Attributes.UID attribute. + expectedIDTokenSubjectRegexForUpstreamLDAP := "^" + + regexp.QuoteMeta("ldaps://"+env.SupervisorUpstreamLDAP.Host+"?base="+url.QueryEscape(env.SupervisorUpstreamLDAP.UserSearchBase)) + + regexp.QuoteMeta("&idpName=test-upstream-ldap-idp-") + `[\w]+` + + regexp.QuoteMeta("&sub="+base64.RawURLEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue))) + + "$" + + // Some of the LDAP tests below use the StartTLS server and port. + expectedIDTokenSubjectRegexForUpstreamLDAPStartTLSHost := "^" + + regexp.QuoteMeta("ldaps://"+env.SupervisorUpstreamLDAP.StartTLSOnlyHost+"?base="+url.QueryEscape(env.SupervisorUpstreamLDAP.UserSearchBase)) + + regexp.QuoteMeta("&idpName=test-upstream-ldap-idp-") + `[\w]+` + + regexp.QuoteMeta("&sub="+base64.RawURLEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue))) + + "$" + + // The downstream ID token Subject should be in the the same format as LDAP above, but with AD-specific values. + expectedIDTokenSubjectRegexForUpstreamAD := "^" + + regexp.QuoteMeta("ldaps://"+env.SupervisorUpstreamActiveDirectory.Host+"?base="+url.QueryEscape(env.SupervisorUpstreamActiveDirectory.DefaultNamingContextSearchBase)) + + regexp.QuoteMeta("&idpName=test-upstream-ad-idp-") + `[\w]+` + + regexp.QuoteMeta("&sub="+env.SupervisorUpstreamActiveDirectory.TestUserUniqueIDAttributeValue) + + "$" + + // Sometimes the AD tests below use a different search base. + expectedIDTokenSubjectRegexForUpstreamADWithCustomUserSearchBase := "^" + + regexp.QuoteMeta("ldaps://"+env.SupervisorUpstreamActiveDirectory.Host+"?base="+url.QueryEscape(env.SupervisorUpstreamActiveDirectory.UserSearchBase)) + + regexp.QuoteMeta("&idpName=test-upstream-ad-idp-") + `[\w]+` + + regexp.QuoteMeta("&sub="+env.SupervisorUpstreamActiveDirectory.TestUserUniqueIDAttributeValue) + + "$" + + // Sometimes the AD tests below create a user dynamically so we can't know the UID up front. + expectedIDTokenSubjectRegexForUpstreamADWithUnkownUID := "^" + + regexp.QuoteMeta("ldaps://"+env.SupervisorUpstreamActiveDirectory.Host+"?base="+url.QueryEscape(env.SupervisorUpstreamActiveDirectory.DefaultNamingContextSearchBase)) + + regexp.QuoteMeta("&idpName=test-upstream-ad-idp-") + `[\w]+` + + regexp.QuoteMeta("&sub=") + ".+" + + "$" + // These tests attempt to exercise the entire login and refresh flow of the Supervisor for various cases. // They do not use the Pinniped CLI as the client, which allows them to exercise the Supervisor as an // OIDC provider in ways that the CLI might not use. Similar tests exist using the CLI in e2e_test.go. @@ -275,8 +318,7 @@ func TestSupervisorLogin_Browser(t *testing.T) { pinnipedSessionData := pinnipedSession.Custom pinnipedSessionData.OIDC.UpstreamIssuer = "wrong-issuer" }, - // the ID token Subject should include the upstream user ID after the upstream issuer name - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?sub=") + ".+", + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamOIDC, // the ID token Username should include the upstream user ID after the upstream issuer name wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?sub=") + ".+" }, }, @@ -299,7 +341,7 @@ func TestSupervisorLogin_Browser(t *testing.T) { customSessionData := pinnipedSession.Custom customSessionData.Username = "some-incorrect-username" }, - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?sub=") + ".+", + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamOIDC, wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Username) + "$" }, wantDownstreamIDTokenGroups: env.SupervisorUpstreamOIDC.ExpectedGroups, editRefreshSessionDataWithoutBreaking: func(t *testing.T, sessionData *psession.PinnipedSession, _, _ string) []string { @@ -346,7 +388,7 @@ func TestSupervisorLogin_Browser(t *testing.T) { customSessionData := pinnipedSession.Custom customSessionData.Username = "some-incorrect-username" }, - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?sub=") + ".+", + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamOIDC, wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Username) + "$" }, wantDownstreamIDTokenGroups: env.SupervisorUpstreamOIDC.ExpectedGroups, }, @@ -375,8 +417,7 @@ func TestSupervisorLogin_Browser(t *testing.T) { require.NotEmpty(t, customSessionData.OIDC.UpstreamRefreshToken) customSessionData.OIDC.UpstreamRefreshToken = "invalid-updated-refresh-token" }, - // the ID token Subject should include the upstream user ID after the upstream issuer name - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?sub=") + ".+", + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamOIDC, // the ID token Username should include the upstream user ID after the upstream issuer name wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?sub=") + ".+" }, }, @@ -406,8 +447,7 @@ func TestSupervisorLogin_Browser(t *testing.T) { false, ) }, - // the ID token Subject should include the upstream user ID after the upstream issuer name - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?sub=") + ".+", + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamOIDC, // the ID token Username should include the upstream user ID after the upstream issuer name wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?sub=") + ".+" }, wantDownstreamIDTokenAdditionalClaims: wantGroupsInAdditionalClaimsIfGroupsExist(map[string]interface{}{ @@ -431,9 +471,8 @@ func TestSupervisorLogin_Browser(t *testing.T) { } return testlib.CreateTestOIDCIdentityProvider(t, spec, idpv1alpha1.PhaseReady).Name }, - requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowOIDC, - // the ID token Subject should include the upstream user ID after the upstream issuer name - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?sub=") + ".+", + requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowOIDC, + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamOIDC, // the ID token Username should include the upstream user ID after the upstream issuer name wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?sub=") + ".+" }, wantDownstreamIDTokenAdditionalClaims: wantGroupsInAdditionalClaimsIfGroupsExist(map[string]interface{}{ @@ -472,12 +511,7 @@ func TestSupervisorLogin_Browser(t *testing.T) { fositeSessionData := pinnipedSession.Fosite fositeSessionData.Claims.Subject = "not-right" }, - // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta( - "ldaps://"+env.SupervisorUpstreamLDAP.Host+ - "?base="+url.QueryEscape(env.SupervisorUpstreamLDAP.UserSearchBase)+ - "&sub="+base64.RawURLEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue)), - ) + "$", + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamLDAP, // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamLDAP.TestUserMailAttributeValue) + "$" @@ -504,12 +538,7 @@ func TestSupervisorLogin_Browser(t *testing.T) { false, ) }, - // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta( - "ldaps://"+env.SupervisorUpstreamLDAP.Host+ - "?base="+url.QueryEscape(env.SupervisorUpstreamLDAP.UserSearchBase)+ - "&sub="+base64.RawURLEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue)), - ) + "$", + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamLDAP, // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamLDAP.TestUserMailAttributeValue) + "$" @@ -534,12 +563,7 @@ func TestSupervisorLogin_Browser(t *testing.T) { false, ) }, - // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta( - "ldaps://"+env.SupervisorUpstreamLDAP.Host+ - "?base="+url.QueryEscape(env.SupervisorUpstreamLDAP.UserSearchBase)+ - "&sub="+base64.RawURLEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue)), - ) + "$", + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamLDAP, // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamLDAP.TestUserMailAttributeValue) + "$" @@ -563,7 +587,7 @@ func TestSupervisorLogin_Browser(t *testing.T) { downstreamScopes: []string{"openid", "pinniped:request-audience", "offline_access"}, wantDownstreamScopes: []string{"openid", "pinniped:request-audience", "offline_access", "username", "groups"}, requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowOIDC, - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?sub=") + ".+", + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamOIDC, wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Username) + "$" }, wantDownstreamIDTokenGroups: env.SupervisorUpstreamOIDC.ExpectedGroups, }, @@ -579,13 +603,8 @@ func TestSupervisorLogin_Browser(t *testing.T) { return env.SupervisorUpstreamLDAP.TestUserMailAttributeValue, // username to present to server during login env.SupervisorUpstreamLDAP.TestUserPassword // password to present to server during login }, - requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowLDAP, - // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta( - "ldaps://"+env.SupervisorUpstreamLDAP.Host+ - "?base="+url.QueryEscape(env.SupervisorUpstreamLDAP.UserSearchBase)+ - "&sub="+base64.RawURLEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue)), - ) + "$", + requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowLDAP, + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamLDAP, // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamLDAP.TestUserMailAttributeValue) + "$" @@ -634,13 +653,8 @@ func TestSupervisorLogin_Browser(t *testing.T) { return env.SupervisorUpstreamLDAP.TestUserMailAttributeValue, // username to present to server during login env.SupervisorUpstreamLDAP.TestUserPassword // password to present to server during login }, - requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowLDAPWithBadCredentialsAndThenGoodCredentials, - // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta( - "ldaps://"+env.SupervisorUpstreamLDAP.Host+ - "?base="+url.QueryEscape(env.SupervisorUpstreamLDAP.UserSearchBase)+ - "&sub="+base64.RawURLEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue)), - ) + "$", + requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowLDAPWithBadCredentialsAndThenGoodCredentials, + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamLDAP, // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamLDAP.TestUserMailAttributeValue) + "$" @@ -684,12 +698,7 @@ func TestSupervisorLogin_Browser(t *testing.T) { fositeSessionData := pinnipedSession.Fosite fositeSessionData.Claims.Subject = "not-right" }, - // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta( - "ldaps://"+env.SupervisorUpstreamLDAP.Host+ - "?base="+url.QueryEscape(env.SupervisorUpstreamLDAP.UserSearchBase)+ - "&sub="+base64.RawURLEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue)), - ) + "$", + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamLDAP, // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamLDAP.TestUserMailAttributeValue) + "$" @@ -739,12 +748,7 @@ func TestSupervisorLogin_Browser(t *testing.T) { fositeSessionData := pinnipedSession.Fosite fositeSessionData.Claims.Subject = "not-right" }, - // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta( - "ldaps://"+env.SupervisorUpstreamLDAP.Host+ - "?base="+url.QueryEscape(env.SupervisorUpstreamLDAP.UserSearchBase)+ - "&sub="+base64.RawURLEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue)), - ) + "$", + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamLDAP, // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamLDAP.TestUserMailAttributeValue) + "$" @@ -778,12 +782,7 @@ func TestSupervisorLogin_Browser(t *testing.T) { require.NotEmpty(t, customSessionData.LDAP.UserDN) customSessionData.Username = "not-the-same" }, - // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta( - "ldaps://"+env.SupervisorUpstreamLDAP.StartTLSOnlyHost+ - "?base="+url.QueryEscape(env.SupervisorUpstreamLDAP.UserSearchBase)+ - "&sub="+base64.RawURLEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue)), - ) + "$", + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamLDAPStartTLSHost, // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamLDAP.TestUserDN) + "$" }, wantDownstreamIDTokenGroups: env.SupervisorUpstreamLDAP.TestUserDirectGroupsCNs, @@ -852,12 +851,7 @@ func TestSupervisorLogin_Browser(t *testing.T) { require.NotEmpty(t, customSessionData.LDAP.UserDN) customSessionData.LDAP.UserDN = "cn=not-a-user,dc=pinniped,dc=dev" }, - // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta( - "ldaps://"+env.SupervisorUpstreamLDAP.Host+ - "?base="+url.QueryEscape(env.SupervisorUpstreamLDAP.UserSearchBase)+ - "&sub="+base64.RawURLEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue)), - ) + "$", + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamLDAP, // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamLDAP.TestUserMailAttributeValue) + "$" @@ -923,12 +917,7 @@ func TestSupervisorLogin_Browser(t *testing.T) { require.NotEmpty(t, customSessionData.LDAP.UserDN) customSessionData.LDAP.UserDN = "cn=not-a-user,dc=pinniped,dc=dev" }, - // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta( - "ldaps://"+env.SupervisorUpstreamLDAP.Host+ - "?base="+url.QueryEscape(env.SupervisorUpstreamLDAP.UserSearchBase)+ - "&sub="+base64.RawURLEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue)), - ) + "$", + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamLDAP, // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamLDAP.TestUserMailAttributeValue) + "$" @@ -957,12 +946,7 @@ func TestSupervisorLogin_Browser(t *testing.T) { require.NotEmpty(t, customSessionData.ActiveDirectory.UserDN) customSessionData.Username = "not-the-same" }, - // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta( - "ldaps://"+env.SupervisorUpstreamActiveDirectory.Host+ - "?base="+url.QueryEscape(env.SupervisorUpstreamActiveDirectory.DefaultNamingContextSearchBase)+ - "&sub="+env.SupervisorUpstreamActiveDirectory.TestUserUniqueIDAttributeValue, - ) + "$", + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamAD, // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamActiveDirectory.TestUserPrincipalNameValue) + "$" @@ -1007,12 +991,7 @@ func TestSupervisorLogin_Browser(t *testing.T) { fositeSessionData := pinnipedSession.Fosite fositeSessionData.Claims.Subject = "not-right" }, - // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta( - "ldaps://"+env.SupervisorUpstreamActiveDirectory.Host+ - "?base="+url.QueryEscape(env.SupervisorUpstreamActiveDirectory.UserSearchBase)+ - "&sub="+env.SupervisorUpstreamActiveDirectory.TestUserUniqueIDAttributeValue, - ) + "$", + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamADWithCustomUserSearchBase, // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamActiveDirectory.TestUserMailAttributeValue) + "$" @@ -1055,12 +1034,7 @@ func TestSupervisorLogin_Browser(t *testing.T) { false, ) }, - // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta( - "ldaps://"+env.SupervisorUpstreamActiveDirectory.Host+ - "?base="+url.QueryEscape(env.SupervisorUpstreamActiveDirectory.UserSearchBase)+ - "&sub="+env.SupervisorUpstreamActiveDirectory.TestUserUniqueIDAttributeValue, - ) + "$", + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamADWithCustomUserSearchBase, // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamActiveDirectory.TestUserMailAttributeValue) + "$" @@ -1112,12 +1086,7 @@ func TestSupervisorLogin_Browser(t *testing.T) { require.NotEmpty(t, customSessionData.ActiveDirectory.UserDN) customSessionData.ActiveDirectory.UserDN = "cn=not-a-user,dc=pinniped,dc=dev" }, - // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta( - "ldaps://"+env.SupervisorUpstreamActiveDirectory.Host+ - "?base="+url.QueryEscape(env.SupervisorUpstreamActiveDirectory.DefaultNamingContextSearchBase)+ - "&sub="+env.SupervisorUpstreamActiveDirectory.TestUserUniqueIDAttributeValue, - ) + "$", + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamAD, // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamActiveDirectory.TestUserPrincipalNameValue) + "$" @@ -1184,12 +1153,7 @@ func TestSupervisorLogin_Browser(t *testing.T) { require.NotEmpty(t, customSessionData.ActiveDirectory.UserDN) customSessionData.ActiveDirectory.UserDN = "cn=not-a-user,dc=pinniped,dc=dev" }, - // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta( - "ldaps://"+env.SupervisorUpstreamActiveDirectory.Host+ - "?base="+url.QueryEscape(env.SupervisorUpstreamActiveDirectory.DefaultNamingContextSearchBase)+ - "&sub="+env.SupervisorUpstreamActiveDirectory.TestUserUniqueIDAttributeValue, - ) + "$", + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamAD, // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamActiveDirectory.TestUserPrincipalNameValue) + "$" @@ -1218,8 +1182,7 @@ func TestSupervisorLogin_Browser(t *testing.T) { breakRefreshSessionData: func(t *testing.T, sessionData *psession.PinnipedSession, _, username string) { testlib.ChangeADTestUserPassword(t, env, username) }, - // we can't know the subject ahead of time because we created a new user and don't know their uid, - // so skip wantDownstreamIDTokenSubjectToMatch + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamADWithUnkownUID, // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute wantDownstreamIDTokenUsernameToMatch: func(username string) string { return "^" + regexp.QuoteMeta(username+"@"+env.SupervisorUpstreamActiveDirectory.Domain) + "$" @@ -1248,8 +1211,7 @@ func TestSupervisorLogin_Browser(t *testing.T) { breakRefreshSessionData: func(t *testing.T, sessionData *psession.PinnipedSession, _, username string) { testlib.DeactivateADTestUser(t, env, username) }, - // we can't know the subject ahead of time because we created a new user and don't know their uid, - // so skip wantDownstreamIDTokenSubjectToMatch + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamADWithUnkownUID, // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute wantDownstreamIDTokenUsernameToMatch: func(username string) string { return "^" + regexp.QuoteMeta(username+"@"+env.SupervisorUpstreamActiveDirectory.Domain) + "$" @@ -1278,8 +1240,7 @@ func TestSupervisorLogin_Browser(t *testing.T) { breakRefreshSessionData: func(t *testing.T, sessionData *psession.PinnipedSession, _, username string) { testlib.LockADTestUser(t, env, username) }, - // we can't know the subject ahead of time because we created a new user and don't know their uid, - // so skip wantDownstreamIDTokenSubjectToMatch + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamADWithUnkownUID, // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute wantDownstreamIDTokenUsernameToMatch: func(username string) string { return "^" + regexp.QuoteMeta(username+"@"+env.SupervisorUpstreamActiveDirectory.Domain) + "$" @@ -1337,12 +1298,7 @@ func TestSupervisorLogin_Browser(t *testing.T) { require.NoError(t, err) time.Sleep(10 * time.Second) // wait for controllers to pick up the change }, - // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta( - "ldaps://"+env.SupervisorUpstreamLDAP.Host+ - "?base="+url.QueryEscape(env.SupervisorUpstreamLDAP.UserSearchBase)+ - "&sub="+base64.RawURLEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue)), - ) + "$", + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamLDAP, // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamLDAP.TestUserMailAttributeValue) + "$" @@ -1381,12 +1337,7 @@ func TestSupervisorLogin_Browser(t *testing.T) { time.Sleep(10 * time.Second) // wait for controllers to pick up the change return []string{} }, - // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta( - "ldaps://"+env.SupervisorUpstreamLDAP.Host+ - "?base="+url.QueryEscape(env.SupervisorUpstreamLDAP.UserSearchBase)+ - "&sub="+base64.RawURLEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue)), - ) + "$", + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamLDAP, // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamLDAP.TestUserMailAttributeValue) + "$" @@ -1399,10 +1350,9 @@ func TestSupervisorLogin_Browser(t *testing.T) { createIDP: func(t *testing.T) string { return testlib.CreateTestOIDCIdentityProvider(t, basicOIDCIdentityProviderSpec(), idpv1alpha1.PhaseReady).Name }, - requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowOIDC, - requestTokenExchangeAud: "contains-disallowed-substring.pinniped.dev-something", // .pinniped.dev substring is not allowed - // the ID token Subject should include the upstream user ID after the upstream issuer name - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?sub=") + ".+", + requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowOIDC, + requestTokenExchangeAud: "contains-disallowed-substring.pinniped.dev-something", // .pinniped.dev substring is not allowed + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamOIDC, // the ID token Username should include the upstream user ID after the upstream issuer name wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?sub=") + ".+" }, wantTokenExchangeResponse: func(t *testing.T, status int, body string) { @@ -1420,10 +1370,9 @@ func TestSupervisorLogin_Browser(t *testing.T) { createIDP: func(t *testing.T) string { return testlib.CreateTestOIDCIdentityProvider(t, basicOIDCIdentityProviderSpec(), idpv1alpha1.PhaseReady).Name }, - requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowOIDC, - requestTokenExchangeAud: "client.oauth.pinniped.dev-client-name", // OIDC dynamic client name is not allowed - // the ID token Subject should include the upstream user ID after the upstream issuer name - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?sub=") + ".+", + requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowOIDC, + requestTokenExchangeAud: "client.oauth.pinniped.dev-client-name", // OIDC dynamic client name is not allowed + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamOIDC, // the ID token Username should include the upstream user ID after the upstream issuer name wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?sub=") + ".+" }, wantTokenExchangeResponse: func(t *testing.T, status int, body string) { @@ -1441,10 +1390,9 @@ func TestSupervisorLogin_Browser(t *testing.T) { createIDP: func(t *testing.T) string { return testlib.CreateTestOIDCIdentityProvider(t, basicOIDCIdentityProviderSpec(), idpv1alpha1.PhaseReady).Name }, - requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowOIDC, - requestTokenExchangeAud: "pinniped-cli", // pinniped-cli is not allowed - // the ID token Subject should include the upstream user ID after the upstream issuer name - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?sub=") + ".+", + requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowOIDC, + requestTokenExchangeAud: "pinniped-cli", // pinniped-cli is not allowed + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamOIDC, // the ID token Username should include the upstream user ID after the upstream issuer name wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?sub=") + ".+" }, wantTokenExchangeResponse: func(t *testing.T, status int, body string) { @@ -1477,9 +1425,8 @@ func TestSupervisorLogin_Browser(t *testing.T) { AllowedScopes: []configv1alpha1.Scope{"openid", "offline_access", "pinniped:request-audience", "username", "groups"}, }, configv1alpha1.OIDCClientPhaseReady) }, - requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowOIDC, - // the ID token Subject should include the upstream user ID after the upstream issuer name - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?sub=") + ".+", + requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowOIDC, + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamOIDC, wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Username) + "$" }, wantDownstreamIDTokenGroups: env.SupervisorUpstreamOIDC.ExpectedGroups, }, @@ -1510,9 +1457,8 @@ func TestSupervisorLogin_Browser(t *testing.T) { AllowedScopes: []configv1alpha1.Scope{"openid", "offline_access", "pinniped:request-audience", "username", "groups"}, }, configv1alpha1.OIDCClientPhaseReady) }, - requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowOIDC, - // the ID token Subject should include the upstream user ID after the upstream issuer name - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?sub=") + ".+", + requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowOIDC, + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamOIDC, wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Username) + "$" }, wantDownstreamIDTokenGroups: env.SupervisorUpstreamOIDC.ExpectedGroups, wantDownstreamIDTokenAdditionalClaims: wantGroupsInAdditionalClaimsIfGroupsExist(map[string]interface{}{ @@ -1539,13 +1485,8 @@ func TestSupervisorLogin_Browser(t *testing.T) { return env.SupervisorUpstreamLDAP.TestUserMailAttributeValue, // username to present to server during login env.SupervisorUpstreamLDAP.TestUserPassword // password to present to server during login }, - requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowLDAP, - // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta( - "ldaps://"+env.SupervisorUpstreamLDAP.Host+ - "?base="+url.QueryEscape(env.SupervisorUpstreamLDAP.UserSearchBase)+ - "&sub="+base64.RawURLEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue)), - ) + "$", + requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowLDAP, + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamLDAP, // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamLDAP.TestUserMailAttributeValue) + "$" @@ -1691,14 +1632,9 @@ func TestSupervisorLogin_Browser(t *testing.T) { return env.SupervisorUpstreamLDAP.TestUserMailAttributeValue, // username to present to server during login env.SupervisorUpstreamLDAP.TestUserPassword // password to present to server during login }, - downstreamScopes: []string{"openid", "pinniped:request-audience", "offline_access", "username"}, // do not request (or expect) groups - requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowLDAP, - // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta( - "ldaps://"+env.SupervisorUpstreamLDAP.Host+ - "?base="+url.QueryEscape(env.SupervisorUpstreamLDAP.UserSearchBase)+ - "&sub="+base64.RawURLEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue)), - ) + "$", + downstreamScopes: []string{"openid", "pinniped:request-audience", "offline_access", "username"}, // do not request (or expect) groups + requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowLDAP, + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamLDAP, // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamLDAP.TestUserMailAttributeValue) + "$" @@ -1724,14 +1660,9 @@ func TestSupervisorLogin_Browser(t *testing.T) { return env.SupervisorUpstreamLDAP.TestUserMailAttributeValue, // username to present to server during login env.SupervisorUpstreamLDAP.TestUserPassword // password to present to server during login }, - downstreamScopes: []string{"openid", "pinniped:request-audience", "offline_access", "groups"}, // do not request (or expect) username - requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowLDAP, - // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta( - "ldaps://"+env.SupervisorUpstreamLDAP.Host+ - "?base="+url.QueryEscape(env.SupervisorUpstreamLDAP.UserSearchBase)+ - "&sub="+base64.RawURLEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue)), - ) + "$", + downstreamScopes: []string{"openid", "pinniped:request-audience", "offline_access", "groups"}, // do not request (or expect) username + requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowLDAP, + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamLDAP, wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "" // username should not exist as a claim since we did not request it }, @@ -1763,14 +1694,9 @@ func TestSupervisorLogin_Browser(t *testing.T) { return env.SupervisorUpstreamLDAP.TestUserMailAttributeValue, // username to present to server during login env.SupervisorUpstreamLDAP.TestUserPassword // password to present to server during login }, - downstreamScopes: []string{"openid", "offline_access"}, // do not request (or expect) pinniped:request-audience or username or groups - requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowLDAP, - // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta( - "ldaps://"+env.SupervisorUpstreamLDAP.Host+ - "?base="+url.QueryEscape(env.SupervisorUpstreamLDAP.UserSearchBase)+ - "&sub="+base64.RawURLEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue)), - ) + "$", + downstreamScopes: []string{"openid", "offline_access"}, // do not request (or expect) pinniped:request-audience or username or groups + requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowLDAP, + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamLDAP, wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "" // username should not exist as a claim since we did not request it }, @@ -1802,13 +1728,8 @@ func TestSupervisorLogin_Browser(t *testing.T) { return env.SupervisorUpstreamActiveDirectory.TestUserPrincipalNameValue, // username to present to server during login env.SupervisorUpstreamActiveDirectory.TestUserPassword // password to present to server during login }, - requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowLDAP, - // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta( - "ldaps://"+env.SupervisorUpstreamActiveDirectory.Host+ - "?base="+url.QueryEscape(env.SupervisorUpstreamActiveDirectory.DefaultNamingContextSearchBase)+ - "&sub="+env.SupervisorUpstreamActiveDirectory.TestUserUniqueIDAttributeValue, - ) + "$", + requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowLDAP, + wantDownstreamIDTokenSubjectToMatch: expectedIDTokenSubjectRegexForUpstreamAD, // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta(env.SupervisorUpstreamActiveDirectory.TestUserPrincipalNameValue) + "$" @@ -1878,8 +1799,12 @@ func TestSupervisorLogin_Browser(t *testing.T) { }, }, displayName }, - requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowOIDC, - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer+"?sub=") + ".+", + requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowOIDC, + wantDownstreamIDTokenSubjectToMatch: "^" + + regexp.QuoteMeta(env.SupervisorUpstreamOIDC.Issuer) + + regexp.QuoteMeta("?idpName="+url.QueryEscape("my oidc idp")) + + regexp.QuoteMeta("&sub=") + ".+" + + "$", wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta("username-prefix:"+env.SupervisorUpstreamOIDC.Username) + "$" }, @@ -1958,12 +1883,11 @@ func TestSupervisorLogin_Browser(t *testing.T) { // compared to what they chose during the initial login, by changing what they decided during the initial login. customSessionData.Username = "some-different-downstream-username" }, - // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta( - "ldaps://"+env.SupervisorUpstreamLDAP.Host+ - "?base="+url.QueryEscape(env.SupervisorUpstreamLDAP.UserSearchBase)+ - "&sub="+base64.RawURLEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue)), - ) + "$", + wantDownstreamIDTokenSubjectToMatch: "^" + + regexp.QuoteMeta("ldaps://"+env.SupervisorUpstreamLDAP.Host+"?base="+url.QueryEscape(env.SupervisorUpstreamLDAP.UserSearchBase)) + + regexp.QuoteMeta("&idpName="+url.QueryEscape("my ldap idp")) + + regexp.QuoteMeta("&sub="+base64.RawURLEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue))) + + "$", // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute and then transformed wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta("username-prefix:"+env.SupervisorUpstreamLDAP.TestUserMailAttributeValue) + "$" @@ -2023,12 +1947,11 @@ func TestSupervisorLogin_Browser(t *testing.T) { // compared to what they chose during the initial login, by changing what they decided during the initial login. customSessionData.Username = "some-different-downstream-username" }, - // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta( - "ldaps://"+env.SupervisorUpstreamLDAP.Host+ - "?base="+url.QueryEscape(env.SupervisorUpstreamLDAP.UserSearchBase)+ - "&sub="+base64.RawURLEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue)), - ) + "$", + wantDownstreamIDTokenSubjectToMatch: "^" + + regexp.QuoteMeta("ldaps://"+env.SupervisorUpstreamLDAP.Host+"?base="+url.QueryEscape(env.SupervisorUpstreamLDAP.UserSearchBase)) + + regexp.QuoteMeta("&idpName="+url.QueryEscape("my ldap idp")) + + regexp.QuoteMeta("&sub="+base64.RawURLEncoding.EncodeToString([]byte(env.SupervisorUpstreamLDAP.TestUserUniqueIDAttributeValue))) + + "$", // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute and then transformed wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta("username-prefix:"+env.SupervisorUpstreamLDAP.TestUserMailAttributeValue) + "$" @@ -2093,12 +2016,11 @@ func TestSupervisorLogin_Browser(t *testing.T) { // compared to what they chose during the initial login, by changing what they decided during the initial login. customSessionData.Username = "some-different-downstream-username" }, - // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta( - "ldaps://"+env.SupervisorUpstreamActiveDirectory.Host+ - "?base="+url.QueryEscape(env.SupervisorUpstreamActiveDirectory.DefaultNamingContextSearchBase)+ - "&sub="+env.SupervisorUpstreamActiveDirectory.TestUserUniqueIDAttributeValue, - ) + "$", + wantDownstreamIDTokenSubjectToMatch: "^" + + regexp.QuoteMeta("ldaps://"+env.SupervisorUpstreamActiveDirectory.Host+"?base="+url.QueryEscape(env.SupervisorUpstreamActiveDirectory.DefaultNamingContextSearchBase)) + + regexp.QuoteMeta("&idpName="+url.QueryEscape("my ad idp")) + + regexp.QuoteMeta("&sub="+env.SupervisorUpstreamActiveDirectory.TestUserUniqueIDAttributeValue) + + "$", // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta("username-prefix:"+env.SupervisorUpstreamActiveDirectory.TestUserPrincipalNameValue) + "$" @@ -2160,12 +2082,11 @@ func TestSupervisorLogin_Browser(t *testing.T) { // compared to what they chose during the initial login, by changing what they decided during the initial login. customSessionData.Username = "some-different-downstream-username" }, - // the ID token Subject should be the Host URL plus the value pulled from the requested UserSearch.Attributes.UID attribute - wantDownstreamIDTokenSubjectToMatch: "^" + regexp.QuoteMeta( - "ldaps://"+env.SupervisorUpstreamActiveDirectory.Host+ - "?base="+url.QueryEscape(env.SupervisorUpstreamActiveDirectory.DefaultNamingContextSearchBase)+ - "&sub="+env.SupervisorUpstreamActiveDirectory.TestUserUniqueIDAttributeValue, - ) + "$", + wantDownstreamIDTokenSubjectToMatch: "^" + + regexp.QuoteMeta("ldaps://"+env.SupervisorUpstreamActiveDirectory.Host+"?base="+url.QueryEscape(env.SupervisorUpstreamActiveDirectory.DefaultNamingContextSearchBase)) + + regexp.QuoteMeta("&idpName="+url.QueryEscape("my ad idp")) + + regexp.QuoteMeta("&sub="+env.SupervisorUpstreamActiveDirectory.TestUserUniqueIDAttributeValue) + + "$", // the ID token Username should have been pulled from the requested UserSearch.Attributes.Username attribute wantDownstreamIDTokenUsernameToMatch: func(_ string) string { return "^" + regexp.QuoteMeta("username-prefix:"+env.SupervisorUpstreamActiveDirectory.TestUserPrincipalNameValue) + "$"