mirror of
https://github.com/vmware-tanzu/pinniped.git
synced 2026-01-03 11:45:45 +00:00
Don't skip upstream group memberships when groups scope is not granted
Background: For dynamic clients, the groups scope is not always allowed and/or requested by the client, so it will not always be granted by the Supervisor for an authorization request. Previously, when the groups scope was not granted, we would skip searching for upstream groups in some scenarios. This commit changes the behavior of authorization flows so that even when the groups scope is not granted we still search for the upstream group memberships as configured, and we pass the upstream group memberships into any configured identity transformations. The identity transformations could potentially reject the user's authentication based on their upstream group membership. When the groups scope is not granted, we don't include the groups in the final Supervisor-issued ID token. This behavior is not changed.
This commit is contained in:
@@ -42,6 +42,7 @@ import (
|
||||
|
||||
configv1alpha1 "go.pinniped.dev/generated/latest/apis/supervisor/config/v1alpha1"
|
||||
supervisorfake "go.pinniped.dev/generated/latest/client/supervisor/clientset/versioned/fake"
|
||||
"go.pinniped.dev/internal/celtransformer"
|
||||
"go.pinniped.dev/internal/crud"
|
||||
"go.pinniped.dev/internal/federationdomain/clientregistry"
|
||||
"go.pinniped.dev/internal/federationdomain/endpoints/jwks"
|
||||
@@ -2907,6 +2908,64 @@ func TestRefreshGrant(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "refresh grant when the upstream refresh when groups scope not requested on original request, when using dynamic client, " +
|
||||
"still runs identity transformations with upstream groups in case transforms want to reject auth based on groups, even though groups would not be included in final ID token",
|
||||
idps: testidplister.NewUpstreamIDPListerBuilder().WithOIDC(
|
||||
upstreamOIDCIdentityProviderBuilder().WithGroupsClaim("my-groups-claim").WithValidatedAndMergedWithUserInfoTokens(&oidctypes.Token{
|
||||
IDToken: &oidctypes.IDToken{
|
||||
Claims: map[string]interface{}{
|
||||
"sub": goodUpstreamSubject,
|
||||
"my-groups-claim": []string{"new-group1", "new-group2", "new-group3"}, // refreshed claims includes updated groups
|
||||
},
|
||||
},
|
||||
}).WithRefreshedTokens(refreshedUpstreamTokensWithIDAndRefreshTokens()).
|
||||
WithTransformsForFederationDomain(transformtestutil.NewPipeline(t,
|
||||
[]celtransformer.CELTransformation{
|
||||
&celtransformer.AllowAuthenticationPolicy{
|
||||
Expression: `!groups.exists(g, g in ["` + "new-group1" + `"])`, // reject auth for users who belongs to an upstream group
|
||||
RejectedAuthenticationMessage: `users who belong to certain upstream group are not allowed`,
|
||||
},
|
||||
}),
|
||||
).Build()),
|
||||
kubeResources: addFullyCapableDynamicClientAndSecretToKubeResources,
|
||||
authcodeExchange: authcodeExchangeInputs{
|
||||
modifyAuthRequest: func(r *http.Request) {
|
||||
addDynamicClientIDToFormPostBody(r)
|
||||
r.Form.Set("scope", "openid offline_access username")
|
||||
},
|
||||
modifyTokenRequest: modifyAuthcodeTokenRequestWithDynamicClientAuth,
|
||||
customSessionData: initialUpstreamOIDCRefreshTokenCustomSessionData(),
|
||||
want: tokenEndpointResponseExpectedValues{
|
||||
wantStatus: http.StatusOK,
|
||||
wantClientID: dynamicClientID,
|
||||
wantSuccessBodyFields: []string{"id_token", "refresh_token", "access_token", "token_type", "expires_in", "scope"},
|
||||
wantRequestedScopes: []string{"openid", "offline_access", "username"},
|
||||
wantGrantedScopes: []string{"openid", "offline_access", "username"},
|
||||
wantCustomSessionDataStored: initialUpstreamOIDCRefreshTokenCustomSessionData(),
|
||||
wantUsername: goodUsername,
|
||||
wantGroups: nil,
|
||||
},
|
||||
},
|
||||
refreshRequest: refreshRequestInputs{
|
||||
modifyTokenRequest: func(r *http.Request, refreshToken string, accessToken string) {
|
||||
r.Body = happyRefreshRequestBody(refreshToken).WithClientID("").WithScope("openid offline_access username").ReadCloser()
|
||||
r.SetBasicAuth(dynamicClientID, testutil.PlaintextPassword1) // Use basic auth header instead.
|
||||
},
|
||||
want: tokenEndpointResponseExpectedValues{
|
||||
wantUpstreamRefreshCall: happyOIDCUpstreamRefreshCall(),
|
||||
wantUpstreamOIDCValidateTokenCall: happyUpstreamValidateTokenCall(refreshedUpstreamTokensWithIDAndRefreshTokens(), true),
|
||||
wantStatus: http.StatusUnauthorized,
|
||||
// auth was rejected because of the upstream group to which the user belonged, as shown by the configured RejectedAuthenticationMessage appearing here
|
||||
wantErrorResponseBody: here.Doc(`
|
||||
{
|
||||
"error": "error",
|
||||
"error_description": "Error during upstream refresh. Upstream refresh rejected by configured identity policy: users who belong to certain upstream group are not allowed."
|
||||
}
|
||||
`),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// fosite does not look at the scopes provided in refresh requests, although it is a valid parameter.
|
||||
// even if 'groups' is not sent in the refresh request, we will send groups all the same.
|
||||
|
||||
Reference in New Issue
Block a user