Add integration test for failed client auth for a dynamic client

This commit is contained in:
Ryan Richard 2022-07-21 10:13:34 -07:00
parent e42f5488fa
commit c12ffad29e

View File

@ -224,10 +224,14 @@ func TestSupervisorLogin_Browser(t *testing.T) {
// Want the authorization endpoint to redirect to the callback with this error type. // Want the authorization endpoint to redirect to the callback with this error type.
// The rest of the flow will be skipped since the initial authorization failed. // The rest of the flow will be skipped since the initial authorization failed.
wantErrorType string wantAuthorizationErrorType string
// Want the authorization endpoint to redirect to the callback with this error description. // Want the authorization endpoint to redirect to the callback with this error description.
// Should be used with wantErrorType. // Should be used with wantAuthorizationErrorType.
wantErrorDescription string wantAuthorizationErrorDescription string
// Optionally want to the authcode exchange at the token endpoint to fail. The rest of the flow will be
// skipped since the authcode exchange failed.
wantAuthcodeExchangeError string
// Optionally make all required assertions about the response of the RFC8693 token exchange for // Optionally make all required assertions about the response of the RFC8693 token exchange for
// the cluster-scoped ID token, given the http response status and response body from the token endpoint. // the cluster-scoped ID token, given the http response status and response body from the token endpoint.
@ -674,8 +678,8 @@ func TestSupervisorLogin_Browser(t *testing.T) {
true, true,
) )
}, },
wantErrorDescription: "The resource owner or authorization server denied the request. Username/password not accepted by LDAP provider.", wantAuthorizationErrorDescription: "The resource owner or authorization server denied the request. Username/password not accepted by LDAP provider.",
wantErrorType: "access_denied", wantAuthorizationErrorType: "access_denied",
}, },
{ {
name: "ldap login still works after updating bind secret", name: "ldap login still works after updating bind secret",
@ -1126,8 +1130,8 @@ func TestSupervisorLogin_Browser(t *testing.T) {
) )
}, },
breakRefreshSessionData: nil, breakRefreshSessionData: nil,
wantErrorDescription: "The resource owner or authorization server denied the request. Username/password not accepted by LDAP provider.", wantAuthorizationErrorDescription: "The resource owner or authorization server denied the request. Username/password not accepted by LDAP provider.",
wantErrorType: "access_denied", wantAuthorizationErrorType: "access_denied",
}, },
{ {
name: "ldap refresh fails when username changes from email as username to dn as username", name: "ldap refresh fails when username changes from email as username to dn as username",
@ -1366,6 +1370,30 @@ func TestSupervisorLogin_Browser(t *testing.T) {
}, },
wantDownstreamIDTokenGroups: env.SupervisorUpstreamActiveDirectory.TestUserIndirectGroupsSAMAccountPlusDomainNames, wantDownstreamIDTokenGroups: env.SupervisorUpstreamActiveDirectory.TestUserIndirectGroupsSAMAccountPlusDomainNames,
}, },
{
name: "ldap upstream with downstream dynamic client, failed client authentication",
maybeSkip: skipLDAPTests,
createIDP: func(t *testing.T) string {
idp, _ := createLDAPIdentityProvider(t, nil)
return idp.Name
},
createOIDCClient: func(t *testing.T, callbackURL string) (string, string) {
clientID, _ := testlib.CreateOIDCClient(t, configv1alpha1.OIDCClientSpec{
AllowedRedirectURIs: []configv1alpha1.RedirectURI{configv1alpha1.RedirectURI(callbackURL)},
AllowedGrantTypes: []configv1alpha1.GrantType{"authorization_code", "urn:ietf:params:oauth:grant-type:token-exchange", "refresh_token"},
AllowedScopes: []configv1alpha1.Scope{"openid", "offline_access", "pinniped:request-audience", "username", "groups"},
}, configv1alpha1.PhaseReady)
return clientID, "wrong-client-secret"
},
testUser: func(t *testing.T) (string, string) {
// return the username and password of the existing user that we want to use for this test
return env.SupervisorUpstreamLDAP.TestUserMailAttributeValue, // username to present to server during login
env.SupervisorUpstreamLDAP.TestUserPassword // password to present to server during login
},
requestAuthorization: requestAuthorizationUsingBrowserAuthcodeFlowLDAP,
wantAuthcodeExchangeError: "oauth2: cannot fetch token: 401 Unauthorized\n" +
`Response: {"error":"invalid_client","error_description":"Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method)."}`,
},
} }
for _, test := range tests { for _, test := range tests {
@ -1373,7 +1401,8 @@ func TestSupervisorLogin_Browser(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
tt.maybeSkip(t) tt.maybeSkip(t)
testSupervisorLogin(t, testSupervisorLogin(
t,
tt.createIDP, tt.createIDP,
tt.requestAuthorization, tt.requestAuthorization,
tt.editRefreshSessionDataWithoutBreaking, tt.editRefreshSessionDataWithoutBreaking,
@ -1386,8 +1415,9 @@ func TestSupervisorLogin_Browser(t *testing.T) {
tt.wantDownstreamIDTokenSubjectToMatch, tt.wantDownstreamIDTokenSubjectToMatch,
tt.wantDownstreamIDTokenUsernameToMatch, tt.wantDownstreamIDTokenUsernameToMatch,
tt.wantDownstreamIDTokenGroups, tt.wantDownstreamIDTokenGroups,
tt.wantErrorDescription, tt.wantAuthorizationErrorType,
tt.wantErrorType, tt.wantAuthorizationErrorDescription,
tt.wantAuthcodeExchangeError,
tt.wantTokenExchangeResponse, tt.wantTokenExchangeResponse,
) )
}) })
@ -1516,8 +1546,8 @@ func testSupervisorLogin(
t *testing.T, t *testing.T,
createIDP func(t *testing.T) string, createIDP func(t *testing.T) string,
requestAuthorization func(t *testing.T, downstreamIssuer string, downstreamAuthorizeURL string, downstreamCallbackURL string, username string, password string, httpClient *http.Client), requestAuthorization func(t *testing.T, downstreamIssuer string, downstreamAuthorizeURL string, downstreamCallbackURL string, username string, password string, httpClient *http.Client),
editRefreshSessionDataWithoutBreaking func(t *testing.T, pinnipedSession *psession.PinnipedSession, idpName, username string) []string, editRefreshSessionDataWithoutBreaking func(t *testing.T, pinnipedSession *psession.PinnipedSession, idpName string, username string) []string,
breakRefreshSessionData func(t *testing.T, pinnipedSession *psession.PinnipedSession, idpName, username string), breakRefreshSessionData func(t *testing.T, pinnipedSession *psession.PinnipedSession, idpName string, username string),
testUser func(t *testing.T) (string, string), testUser func(t *testing.T) (string, string),
createOIDCClient func(t *testing.T, callbackURL string) (string, string), createOIDCClient func(t *testing.T, callbackURL string) (string, string),
downstreamScopes []string, downstreamScopes []string,
@ -1526,8 +1556,9 @@ func testSupervisorLogin(
wantDownstreamIDTokenSubjectToMatch string, wantDownstreamIDTokenSubjectToMatch string,
wantDownstreamIDTokenUsernameToMatch func(username string) string, wantDownstreamIDTokenUsernameToMatch func(username string) string,
wantDownstreamIDTokenGroups []string, wantDownstreamIDTokenGroups []string,
wantErrorDescription string, wantAuthorizationErrorType string,
wantErrorType string, wantAuthorizationErrorDescription string,
wantAuthcodeExchangeError string,
wantTokenExchangeResponse func(t *testing.T, status int, body string), wantTokenExchangeResponse func(t *testing.T, status int, body string),
) { ) {
env := testlib.IntegrationEnv(t) env := testlib.IntegrationEnv(t)
@ -1693,7 +1724,16 @@ func testSupervisorLogin(
require.NoError(t, err) require.NoError(t, err)
t.Logf("got callback request: %s", testlib.MaskTokens(callback.URL.String())) t.Logf("got callback request: %s", testlib.MaskTokens(callback.URL.String()))
if wantErrorType == "" { // nolint:nestif
if wantAuthorizationErrorType != "" {
errorDescription := callback.URL.Query().Get("error_description")
errorType := callback.URL.Query().Get("error")
require.Equal(t, errorDescription, wantAuthorizationErrorDescription)
require.Equal(t, errorType, wantAuthorizationErrorType)
// The authorization has failed, so can't continue the login flow, making this the end of the test case.
return
}
require.Equal(t, stateParam.String(), callback.URL.Query().Get("state")) require.Equal(t, stateParam.String(), callback.URL.Query().Get("state"))
require.ElementsMatch(t, downstreamScopes, strings.Split(callback.URL.Query().Get("scope"), " ")) require.ElementsMatch(t, downstreamScopes, strings.Split(callback.URL.Query().Get("scope"), " "))
authcode := callback.URL.Query().Get("code") authcode := callback.URL.Query().Get("code")
@ -1704,8 +1744,13 @@ func testSupervisorLogin(
// Call the token endpoint to get tokens. // Call the token endpoint to get tokens.
tokenResponse, err := downstreamOAuth2Config.Exchange(oidcHTTPClientContext, authcode, pkceParam.Verifier()) tokenResponse, err := downstreamOAuth2Config.Exchange(oidcHTTPClientContext, authcode, pkceParam.Verifier())
if wantAuthcodeExchangeError != "" {
require.EqualError(t, err, wantAuthcodeExchangeError)
// The authcode exchange has failed, so can't continue the login flow, making this the end of the test case.
return
} else {
require.NoError(t, err) require.NoError(t, err)
}
expectedIDTokenClaims := []string{"iss", "exp", "sub", "aud", "auth_time", "iat", "jti", "nonce", "rat", "username"} expectedIDTokenClaims := []string{"iss", "exp", "sub", "aud", "auth_time", "iat", "jti", "nonce", "rat", "username"}
if slices.Contains(downstreamScopes, "groups") { if slices.Contains(downstreamScopes, "groups") {
expectedIDTokenClaims = append(expectedIDTokenClaims, "groups") expectedIDTokenClaims = append(expectedIDTokenClaims, "groups")
@ -1798,12 +1843,6 @@ func testSupervisorLogin(
err.Error(), err.Error(),
) )
} }
} else {
errorDescription := callback.URL.Query().Get("error_description")
errorType := callback.URL.Query().Get("error")
require.Equal(t, errorDescription, wantErrorDescription)
require.Equal(t, errorType, wantErrorType)
}
} }
// getFositeDataSignature returns the signature of the provided data. The provided data could be an auth code, access // getFositeDataSignature returns the signature of the provided data. The provided data could be an auth code, access