diff --git a/internal/oidc/auth/auth_handler_test.go b/internal/oidc/auth/auth_handler_test.go index 9c92301e..e6917954 100644 --- a/internal/oidc/auth/auth_handler_test.go +++ b/internal/oidc/auth/auth_handler_test.go @@ -1156,7 +1156,7 @@ func TestAuthorizationEndpoint(t *testing.T) { require.Len(t, kubeClient.Actions(), test.wantUnnecessaryStoredRecords) case test.wantRedirectLocationRegexp != "": require.Len(t, rsp.Header().Values("Location"), 1) - oidctestutil.RequireAuthcodeRedirectLocation( + oidctestutil.RequireAuthCodeRegexpMatch( t, rsp.Header().Get("Location"), test.wantRedirectLocationRegexp, diff --git a/internal/oidc/callback/callback_handler_test.go b/internal/oidc/callback/callback_handler_test.go index 583ee943..4d749355 100644 --- a/internal/oidc/callback/callback_handler_test.go +++ b/internal/oidc/callback/callback_handler_test.go @@ -122,6 +122,7 @@ func TestCallbackEndpoint(t *testing.T) { wantContentType string wantBody string wantRedirectLocationRegexp string + wantBodyFormResponseRegexp string wantDownstreamGrantedScopes []string wantDownstreamIDTokenSubject string wantDownstreamIDTokenUsername string @@ -133,6 +134,32 @@ func TestCallbackEndpoint(t *testing.T) { wantExchangeAndValidateTokensCall *oidctestutil.ExchangeAuthcodeAndValidateTokenArgs }{ + { + name: "GET with good state and cookie and successful upstream token exchange with response_mode=form_post returns 200 with HTML+JS form", + idp: happyUpstream().Build(), + method: http.MethodGet, + path: newRequestPath().WithState( + happyUpstreamStateParam().WithAuthorizeRequestParams( + shallowCopyAndModifyQuery( + happyDownstreamRequestParamsQuery, + map[string]string{"response_mode": "form_post"}, + ).Encode(), + ).Build(t, happyStateCodec), + ).String(), + csrfCookie: happyCSRFCookie, + wantStatus: http.StatusOK, + wantContentType: "text/html;charset=UTF-8", + wantBodyFormResponseRegexp: ``, + wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + queryEscapedUpstreamSubject, + wantDownstreamIDTokenUsername: upstreamUsername, + wantDownstreamIDTokenGroups: upstreamGroupMembership, + wantDownstreamRequestedScopes: happyDownstreamScopesRequested, + wantDownstreamGrantedScopes: happyDownstreamScopesGranted, + wantDownstreamNonce: downstreamNonce, + wantDownstreamPKCEChallenge: downstreamPKCEChallenge, + wantDownstreamPKCEChallengeMethod: downstreamPKCEChallengeMethod, + wantExchangeAndValidateTokensCall: happyExchangeAndValidateTokensArgs, + }, { name: "GET with good state and cookie and successful upstream token exchange returns 302 to downstream client callback with its state and code", idp: happyUpstream().Build(), @@ -666,15 +693,40 @@ func TestCallbackEndpoint(t *testing.T) { require.Equal(t, test.wantStatus, rsp.Code) testutil.RequireEqualContentType(t, rsp.Header().Get("Content-Type"), test.wantContentType) - if test.wantBody != "" { + switch { + // If we want a specific static response body, assert that. + case test.wantBody != "": require.Equal(t, test.wantBody, rsp.Body.String()) - } else { + + // Else if we want a body that contains a regex-matched auth code, assert that (for "response_mode=form_post"). + case test.wantBodyFormResponseRegexp != "": + oidctestutil.RequireAuthCodeRegexpMatch( + t, + rsp.Body.String(), + test.wantBodyFormResponseRegexp, + client, + secrets, + oauthStore, + test.wantDownstreamGrantedScopes, + test.wantDownstreamIDTokenSubject, + test.wantDownstreamIDTokenUsername, + test.wantDownstreamIDTokenGroups, + test.wantDownstreamRequestedScopes, + test.wantDownstreamPKCEChallenge, + test.wantDownstreamPKCEChallengeMethod, + test.wantDownstreamNonce, + downstreamClientID, + downstreamRedirectURI, + ) + + // Otherwise, expect an empty response body. + default: require.Empty(t, rsp.Body.String()) } if test.wantRedirectLocationRegexp != "" { //nolint:nestif // don't mind have several sequential if statements in this test require.Len(t, rsp.Header().Values("Location"), 1) - oidctestutil.RequireAuthcodeRedirectLocation( + oidctestutil.RequireAuthCodeRegexpMatch( t, rsp.Header().Get("Location"), test.wantRedirectLocationRegexp, diff --git a/internal/testutil/oidctestutil/oidctestutil.go b/internal/testutil/oidctestutil/oidctestutil.go index b8e7b0de..f690af52 100644 --- a/internal/testutil/oidctestutil/oidctestutil.go +++ b/internal/testutil/oidctestutil/oidctestutil.go @@ -235,10 +235,10 @@ func VerifyECDSAIDToken( return token } -func RequireAuthcodeRedirectLocation( +func RequireAuthCodeRegexpMatch( t *testing.T, - actualRedirectLocation string, - wantRedirectLocationRegexp string, + actualContent string, + wantRegexp string, kubeClient *fake.Clientset, secretsClient v1.SecretInterface, oauthStore fositestoragei.AllFositeStorage, @@ -256,9 +256,9 @@ func RequireAuthcodeRedirectLocation( t.Helper() // Assert that Location header matches regular expression. - regex := regexp.MustCompile(wantRedirectLocationRegexp) - submatches := regex.FindStringSubmatch(actualRedirectLocation) - require.Lenf(t, submatches, 2, "no regexp match in actualRedirectLocation: %q", actualRedirectLocation) + regex := regexp.MustCompile(wantRegexp) + submatches := regex.FindStringSubmatch(actualContent) + require.Lenf(t, submatches, 2, "no regexp match in actualContent: %", actualContent) capturedAuthCode := submatches[1] // fosite authcodes are in the format `data.signature`, so grab the signature part, which is the lookup key in the storage interface