started add units tests for identity transforms to token_handler_test.go
This commit is contained in:
parent
7f70fcf679
commit
5ad7e9a8ca
@ -186,7 +186,8 @@ func upstreamOIDCRefresh(
|
|||||||
}
|
}
|
||||||
mergedClaims := validatedTokens.IDToken.Claims
|
mergedClaims := validatedTokens.IDToken.Claims
|
||||||
|
|
||||||
// To the extent possible, check that the user's basic identity hasn't changed.
|
// To the extent possible, check that the user's basic identity hasn't changed. We check that their downstream
|
||||||
|
// username has not changed separately below, as part of reapplying the transformations.
|
||||||
err = validateSubjectAndIssuerUnchangedSinceInitialLogin(mergedClaims, session)
|
err = validateSubjectAndIssuerUnchangedSinceInitialLogin(mergedClaims, session)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -61,6 +61,7 @@ import (
|
|||||||
"go.pinniped.dev/internal/psession"
|
"go.pinniped.dev/internal/psession"
|
||||||
"go.pinniped.dev/internal/testutil"
|
"go.pinniped.dev/internal/testutil"
|
||||||
"go.pinniped.dev/internal/testutil/oidctestutil"
|
"go.pinniped.dev/internal/testutil/oidctestutil"
|
||||||
|
"go.pinniped.dev/internal/testutil/transformtestutil"
|
||||||
"go.pinniped.dev/pkg/oidcclient/oidctypes"
|
"go.pinniped.dev/pkg/oidcclient/oidctypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1749,6 +1750,9 @@ func TestRefreshGrant(t *testing.T) {
|
|||||||
activeDirectoryUpstreamResourceUID = "ad-resource-uid"
|
activeDirectoryUpstreamResourceUID = "ad-resource-uid"
|
||||||
activeDirectoryUpstreamType = "activedirectory"
|
activeDirectoryUpstreamType = "activedirectory"
|
||||||
activeDirectoryUpstreamDN = "some-ad-user-dn"
|
activeDirectoryUpstreamDN = "some-ad-user-dn"
|
||||||
|
|
||||||
|
transformationUsernamePrefix = "username_prefix:"
|
||||||
|
transformationGroupsPrefix = "groups_prefix:"
|
||||||
)
|
)
|
||||||
|
|
||||||
ldapUpstreamURL, _ := url.Parse("some-url")
|
ldapUpstreamURL, _ := url.Parse("some-url")
|
||||||
@ -1861,6 +1865,13 @@ func TestRefreshGrant(t *testing.T) {
|
|||||||
return want
|
return want
|
||||||
}
|
}
|
||||||
|
|
||||||
|
happyAuthcodeExchangeTokenResponseForOpenIDAndOfflineAccessWithUsernameAndGroups := func(wantCustomSessionDataStored *psession.CustomSessionData, wantDownstreamUsername string, wantDownsteamGroups []string) tokenEndpointResponseExpectedValues {
|
||||||
|
want := happyAuthcodeExchangeTokenResponseForOpenIDAndOfflineAccess(wantCustomSessionDataStored)
|
||||||
|
want.wantUsername = wantDownstreamUsername
|
||||||
|
want.wantGroups = wantDownsteamGroups
|
||||||
|
return want
|
||||||
|
}
|
||||||
|
|
||||||
withWantDynamicClientID := func(w tokenEndpointResponseExpectedValues) tokenEndpointResponseExpectedValues {
|
withWantDynamicClientID := func(w tokenEndpointResponseExpectedValues) tokenEndpointResponseExpectedValues {
|
||||||
w.wantClientID = dynamicClientID
|
w.wantClientID = dynamicClientID
|
||||||
return w
|
return w
|
||||||
@ -1895,6 +1906,12 @@ func TestRefreshGrant(t *testing.T) {
|
|||||||
return want
|
return want
|
||||||
}
|
}
|
||||||
|
|
||||||
|
happyRefreshTokenResponseForLDAPWithUsernameAndGroups := func(wantCustomSessionDataStored *psession.CustomSessionData, wantDownstreamUsername string, wantDownsteamGroups []string) tokenEndpointResponseExpectedValues {
|
||||||
|
want := happyAuthcodeExchangeTokenResponseForOpenIDAndOfflineAccessWithUsernameAndGroups(wantCustomSessionDataStored, wantDownstreamUsername, wantDownsteamGroups)
|
||||||
|
want.wantUpstreamRefreshCall = happyLDAPUpstreamRefreshCall()
|
||||||
|
return want
|
||||||
|
}
|
||||||
|
|
||||||
happyRefreshTokenResponseForActiveDirectory := func(wantCustomSessionDataStored *psession.CustomSessionData) tokenEndpointResponseExpectedValues {
|
happyRefreshTokenResponseForActiveDirectory := func(wantCustomSessionDataStored *psession.CustomSessionData) tokenEndpointResponseExpectedValues {
|
||||||
want := happyAuthcodeExchangeTokenResponseForOpenIDAndOfflineAccess(wantCustomSessionDataStored)
|
want := happyAuthcodeExchangeTokenResponseForOpenIDAndOfflineAccess(wantCustomSessionDataStored)
|
||||||
want.wantUpstreamRefreshCall = happyActiveDirectoryUpstreamRefreshCall()
|
want.wantUpstreamRefreshCall = happyActiveDirectoryUpstreamRefreshCall()
|
||||||
@ -1945,10 +1962,20 @@ func TestRefreshGrant(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
happyLDAPCustomSessionDataWithUsername := func(wantDownstreamUsername string) *psession.CustomSessionData {
|
||||||
|
copyOfCustomSession := *happyLDAPCustomSessionData
|
||||||
|
copyOfLDAP := *(happyLDAPCustomSessionData.LDAP)
|
||||||
|
copyOfCustomSession.LDAP = ©OfLDAP
|
||||||
|
copyOfCustomSession.Username = wantDownstreamUsername
|
||||||
|
return ©OfCustomSession
|
||||||
|
}
|
||||||
|
|
||||||
happyAuthcodeExchangeInputsForOIDCUpstream := authcodeExchangeInputs{
|
happyAuthcodeExchangeInputsForOIDCUpstream := authcodeExchangeInputs{
|
||||||
customSessionData: initialUpstreamOIDCRefreshTokenCustomSessionData(),
|
|
||||||
modifyAuthRequest: func(r *http.Request) { r.Form.Set("scope", "openid offline_access username groups") },
|
modifyAuthRequest: func(r *http.Request) { r.Form.Set("scope", "openid offline_access username groups") },
|
||||||
want: happyAuthcodeExchangeTokenResponseForOpenIDAndOfflineAccess(initialUpstreamOIDCRefreshTokenCustomSessionData()),
|
customSessionData: initialUpstreamOIDCRefreshTokenCustomSessionData(),
|
||||||
|
want: happyAuthcodeExchangeTokenResponseForOpenIDAndOfflineAccess(
|
||||||
|
initialUpstreamOIDCRefreshTokenCustomSessionData(),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
happyAuthcodeExchangeInputsForLDAPUpstream := authcodeExchangeInputs{
|
happyAuthcodeExchangeInputsForLDAPUpstream := authcodeExchangeInputs{
|
||||||
@ -1959,6 +1986,8 @@ func TestRefreshGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prefixUsernameAndGroupsPipeline := transformtestutil.NewPrefixingPipeline(t, transformationUsernamePrefix, transformationGroupsPrefix)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
idps *oidctestutil.UpstreamIDPListerBuilder
|
idps *oidctestutil.UpstreamIDPListerBuilder
|
||||||
@ -3498,6 +3527,39 @@ func TestRefreshGrant(t *testing.T) {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "upstream ldap refresh happy path with identity transformations which modify the username and group names",
|
||||||
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(oidctestutil.NewTestUpstreamLDAPIdentityProviderBuilder().
|
||||||
|
WithName(ldapUpstreamName).
|
||||||
|
WithResourceUID(ldapUpstreamResourceUID).
|
||||||
|
WithURL(ldapUpstreamURL).
|
||||||
|
WithPerformRefreshGroups(goodGroups).
|
||||||
|
WithTransformsForFederationDomain(prefixUsernameAndGroupsPipeline).
|
||||||
|
Build(),
|
||||||
|
),
|
||||||
|
authcodeExchange: authcodeExchangeInputs{
|
||||||
|
modifyAuthRequest: func(r *http.Request) { r.Form.Set("scope", "openid offline_access username groups") },
|
||||||
|
customSessionData: happyLDAPCustomSessionDataWithUsername(transformationUsernamePrefix + goodUsername),
|
||||||
|
modifySession: func(session *psession.PinnipedSession) {
|
||||||
|
// The authorization flow would have run the transformation pipeline and stored the transformed
|
||||||
|
// downstream identity in this part of the session, so simulate that by setting the expected result.
|
||||||
|
session.IDTokenClaims().Extra["username"] = transformationUsernamePrefix + goodUsername
|
||||||
|
session.IDTokenClaims().Extra["groups"] = testutil.AddPrefixToEach(transformationGroupsPrefix, goodGroups)
|
||||||
|
},
|
||||||
|
want: happyAuthcodeExchangeTokenResponseForOpenIDAndOfflineAccessWithUsernameAndGroups(
|
||||||
|
happyLDAPCustomSessionDataWithUsername(transformationUsernamePrefix+goodUsername),
|
||||||
|
transformationUsernamePrefix+goodUsername,
|
||||||
|
testutil.AddPrefixToEach(transformationGroupsPrefix, goodGroups),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
refreshRequest: refreshRequestInputs{
|
||||||
|
want: happyRefreshTokenResponseForLDAPWithUsernameAndGroups(
|
||||||
|
happyLDAPCustomSessionDataWithUsername(transformationUsernamePrefix+goodUsername),
|
||||||
|
transformationUsernamePrefix+goodUsername,
|
||||||
|
testutil.AddPrefixToEach(transformationGroupsPrefix, goodGroups),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "upstream ldap refresh happy path using dynamic client",
|
name: "upstream ldap refresh happy path using dynamic client",
|
||||||
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(oidctestutil.NewTestUpstreamLDAPIdentityProviderBuilder().
|
idps: oidctestutil.NewUpstreamIDPListerBuilder().WithLDAP(oidctestutil.NewTestUpstreamLDAPIdentityProviderBuilder().
|
||||||
@ -4436,7 +4498,11 @@ func (s *singleUseJWKProvider) GetJWKS(issuerName string) (jwks *jose.JSONWebKey
|
|||||||
return s.DynamicJWKSProvider.GetJWKS(issuerName)
|
return s.DynamicJWKSProvider.GetJWKS(issuerName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simulate the auth endpoint running so Fosite code will fill the store with realistic values.
|
// Simulate the results of the auth endpoint (and possibly also the related callback or login endpoints) by getting
|
||||||
|
// fosite's code to fill the session store with realistic values. Regardless of the specific flow that the user uses to
|
||||||
|
// become authorized, all authorization flows conclude with the user's identity saved into a fosite session and an
|
||||||
|
// authorization code being issued to the client. So the goal of this function is to save the user's identity into a
|
||||||
|
// session in the same way that the production code for those other endpoints would have done it.
|
||||||
func simulateAuthEndpointHavingAlreadyRun(
|
func simulateAuthEndpointHavingAlreadyRun(
|
||||||
t *testing.T,
|
t *testing.T,
|
||||||
authRequest *http.Request,
|
authRequest *http.Request,
|
||||||
@ -4459,9 +4525,6 @@ func simulateAuthEndpointHavingAlreadyRun(
|
|||||||
},
|
},
|
||||||
Custom: initialCustomSessionData,
|
Custom: initialCustomSessionData,
|
||||||
}
|
}
|
||||||
if modifySession != nil {
|
|
||||||
modifySession(session)
|
|
||||||
}
|
|
||||||
|
|
||||||
authRequester, err := oauthHelper.NewAuthorizeRequest(ctx, authRequest)
|
authRequester, err := oauthHelper.NewAuthorizeRequest(ctx, authRequest)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -4475,9 +4538,13 @@ func simulateAuthEndpointHavingAlreadyRun(
|
|||||||
authRequester.GrantScope("pinniped:request-audience")
|
authRequester.GrantScope("pinniped:request-audience")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the downstream username and group names that normally would have been determined by the authorize and related
|
||||||
|
// endpoints. These are stored into the fosite "extra" claims by the other endpoints, and when the token endpoint is
|
||||||
|
// called later, it will be able to find this information inside the "extra" claims in the session.
|
||||||
// The authorization endpoint makes a special exception for the pinniped-cli client for backwards compatibility
|
// The authorization endpoint makes a special exception for the pinniped-cli client for backwards compatibility
|
||||||
// and grants the username and groups scopes to that client even if it did not ask for them. Simulate that
|
// and grants the username and groups scopes to that client even if it did not ask for them. Simulate that
|
||||||
// behavior here too.
|
// behavior here too by always adding these extras when the client_id is the Pinniped CLI client.
|
||||||
|
// Note that these (and anything else in the session) can be overridden by the modifySession param.
|
||||||
if strings.Contains(authRequest.Form.Get("scope"), "username") || authRequest.Form.Get("client_id") == pinnipedCLIClientID {
|
if strings.Contains(authRequest.Form.Get("scope"), "username") || authRequest.Form.Get("client_id") == pinnipedCLIClientID {
|
||||||
authRequester.GrantScope("username")
|
authRequester.GrantScope("username")
|
||||||
session.Fosite.Claims.Extra["username"] = goodUsername
|
session.Fosite.Claims.Extra["username"] = goodUsername
|
||||||
@ -4490,6 +4557,11 @@ func simulateAuthEndpointHavingAlreadyRun(
|
|||||||
// The authorization endpoint sets the authorized party to the client ID of the original requester.
|
// The authorization endpoint sets the authorized party to the client ID of the original requester.
|
||||||
session.Fosite.Claims.Extra["azp"] = authRequester.GetClient().GetID()
|
session.Fosite.Claims.Extra["azp"] = authRequester.GetClient().GetID()
|
||||||
|
|
||||||
|
// Allow some tests to further modify the session before it is stored.
|
||||||
|
if modifySession != nil {
|
||||||
|
modifySession(session)
|
||||||
|
}
|
||||||
|
|
||||||
authResponder, err := oauthHelper.NewAuthorizeResponse(ctx, authRequester, session)
|
authResponder, err := oauthHelper.NewAuthorizeResponse(ctx, authRequester, session)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return authResponder
|
return authResponder
|
||||||
|
Loading…
Reference in New Issue
Block a user