From 0b959ad5c83b2e3fa43d9a02b512776b7a43a544 Mon Sep 17 00:00:00 2001 From: Yevgen Bykov Date: Tue, 7 Apr 2026 12:16:18 +0200 Subject: [PATCH 1/3] Fix external OAuth role mapping regression in v78 --- .../provider/oauth/ExternalOAuthAuthenticationManager.java | 2 +- .../oauth/ExternalOAuthAuthenticationManagerTest.java | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManager.java index c0933008e5b..9376708bd87 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManager.java @@ -394,7 +394,7 @@ protected void populateAuthenticationAttributes(UaaAuthentication authentication authentication.setUserAttributes(userAttributes); authentication.setExternalGroups( - Optional.ofNullable(authenticationData.getExternalAuthorities()) + Optional.ofNullable(authenticationData.getAuthorities()) .orElse(emptyList()) .stream() .map(GrantedAuthority::getAuthority) diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerTest.java index 4fe49ca89dd..a568e3fe262 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerTest.java @@ -606,7 +606,7 @@ void getUser_doesThrowWhenIdTokenMappingIsWrongType() { } @Test - void populateAuthenticationAttributes_setsIdpIdTokenAndExternalGroups() { + void populateAuthenticationAttributes_setsIdpIdTokenAndExternalGroupsFromMappedAuthorities() { UaaAuthentication authentication = new UaaAuthentication(new UaaPrincipal("user-guid", "marissa", "marissa@test.org", "uaa", "", ""), Collections.emptyList(), null); Map header = map( entry(HeaderParameterNames.ALGORITHM, JWSAlgorithm.RS256.getName()), @@ -627,10 +627,11 @@ void populateAuthenticationAttributes_setsIdpIdTokenAndExternalGroups() { String idTokenJwt = UaaTokenUtils.constructToken(header, claims, signer); ExternalOAuthCodeToken oidcAuthentication = new ExternalOAuthCodeToken(null, ORIGIN, "http://google.com", idTokenJwt, "accesstoken", "signedrequest"); ExternalOAuthAuthenticationManager.AuthenticationData authenticationData = authManager.getExternalAuthenticationDetails(oidcAuthentication); - authenticationData.setExternalAuthorities(List.of(new SimpleGrantedAuthority("uaa-authorities"))); + authenticationData.setAuthorities(List.of(new SimpleGrantedAuthority("uaa-authorities"))); + authenticationData.setExternalAuthorities(List.of(new SimpleGrantedAuthority("raw_external_group"))); authManager.populateAuthenticationAttributes(authentication, oidcAuthentication, authenticationData); assertThat(authentication.getIdpIdToken()).isEqualTo(idTokenJwt); - assertThat(authentication.getExternalGroups()).containsAll(List.of("uaa-authorities")); + assertThat(authentication.getExternalGroups()).containsExactlyInAnyOrder("uaa-authorities"); } @Test From 7555fdbd3a1c87eba2df82cfdd90a8b62c5ecee1 Mon Sep 17 00:00:00 2001 From: strehle Date: Thu, 9 Apr 2026 15:51:30 +0200 Subject: [PATCH 2/3] Add integration test for role mapping with OIDC IdPs --- .../uaa/integration/feature/OIDCLoginIT.java | 46 ++++++++++++++++++- .../util/IntegrationTestUtils.java | 15 +++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/OIDCLoginIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/OIDCLoginIT.java index 12bd9380a40..e8b80955a0c 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/OIDCLoginIT.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/OIDCLoginIT.java @@ -14,6 +14,8 @@ package org.cloudfoundry.identity.uaa.integration.feature; import com.fasterxml.jackson.core.type.TypeReference; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.JWTParser; import org.cloudfoundry.identity.uaa.ServerRunningExtension; import org.cloudfoundry.identity.uaa.account.UserInfoResponse; import org.cloudfoundry.identity.uaa.client.UaaClientDetails; @@ -68,18 +70,23 @@ import java.net.URLEncoder; import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; +import java.text.ParseException; import java.time.Duration; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; import static org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils.isMember; import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.SUB; import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_AUTHORIZATION_CODE; +import static org.cloudfoundry.identity.uaa.oauth.token.TokenConstants.GRANT_TYPE_PASSWORD; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_NAME_ATTRIBUTE_NAME; @SpringJUnitConfig(classes = DefaultIntegrationTestConfig.class) @@ -435,6 +442,42 @@ void shadowUserNameDefaultsToOIDCSubjectClaim() { assertThat(shadowUser.getUserName()).isEqualTo(expectedUsername); } + @Test + void roleMappingAndUserAttributesFromIdTokenOfZone() throws ParseException { + // test role and user_attribute claims in id token with external group membership assignment, see issue https://github.com/cloudfoundry/uaa/issues/3813 + Map attributeMappings = new HashMap<>(identityProvider.getConfig().getAttributeMappings()); + attributeMappings.remove(USER_NAME_ATTRIBUTE_NAME); + attributeMappings.put("user.attribute.roles", "scope"); + if (identityProvider.getConfig() instanceof OIDCIdentityProviderDefinition oidcConfig) { + oidcConfig.setStoreCustomAttributes(true); + oidcConfig.setPasswordGrantEnabled(true); + oidcConfig.setAttributeMappings(attributeMappings); + } + updateProvider(); + + String clientId = "client" + new RandomValueStringGenerator(5).generate(); + UaaClientDetails passwordClient = new UaaClientDetails(clientId, null, "openid,roles,user_attributes,"+createdGroup.getDisplayName(), GRANT_TYPE_PASSWORD, "uaa.none", baseUrl); + passwordClient.setClientSecret("clientsecret"); + passwordClient.setAutoApproveScopes(Collections.singletonList("true")); + IntegrationTestUtils.createClientAsZoneAdmin(clientCredentialsToken, baseUrl, zone.getId(), passwordClient); + Map response = IntegrationTestUtils.getPasswordToken(zoneUrl, passwordClient.getClientId(), "clientsecret", testAccounts.getUserName(), testAccounts.getPassword(), null, + identityProvider.getOriginKey()); + assertThat(response).isNotNull(); + String idToken = response.get("id_token") instanceof String idString ? idString : null; + assertThat(idToken).isNotNull(); + + JWTClaimsSet jwtClaimsSet = JWTParser.parse(idToken).getJWTClaimsSet(); + assertThat(jwtClaimsSet.getStringClaim("origin")).isEqualTo(identityProvider.getOriginKey()); + Set rolesInJwt = Arrays.stream(jwtClaimsSet.getStringArrayClaim("roles")).collect(Collectors.toSet()); + assertThat(rolesInJwt).isNotNull().contains(createdGroup.getDisplayName()); + Map userAttributeJwt = jwtClaimsSet.getJSONObjectClaim("user_attributes"); + assertThat(userAttributeJwt).isInstanceOf(Map.class); + List attr1 = userAttributeJwt.get("the_client_id") instanceof ArrayList arrayList ? arrayList : null; + assertThat(attr1).isNotNull().contains("identity"); + List attr2 = userAttributeJwt.get("roles") instanceof ArrayList arrayList ? arrayList : null; + assertThat(attr2).isNotNull().contains("openid"); + } + @Test void claimsComeFromUserInfoEndpoint() { AbstractExternalOAuthIdentityProviderDefinition oldConfig = identityProvider.getConfig(); @@ -442,6 +485,7 @@ void claimsComeFromUserInfoEndpoint() { attributeMappings.remove(USER_NAME_ATTRIBUTE_NAME); oldConfig.setAttributeMappings(attributeMappings); oldConfig.setLinkText("My Oauth2.0 Provider"); + oldConfig.setStoreCustomAttributes(true); //change the type so that we will use the /userinfo endpoint identityProvider.setType(OriginKeys.OAUTH20); updateProvider(); @@ -461,7 +505,7 @@ void claimsComeFromUserInfoEndpoint() { localhostServerRunning.setHostName("localhost"); String clientId = "client" + new RandomValueStringGenerator(5).generate(); - UaaClientDetails client = new UaaClientDetails(clientId, null, "openid", GRANT_TYPE_AUTHORIZATION_CODE, "openid", baseUrl); + UaaClientDetails client = new UaaClientDetails(clientId, null, "openid,roles,user_attributes,"+createdGroup.getDisplayName(), GRANT_TYPE_AUTHORIZATION_CODE, "openid", baseUrl); client.setClientSecret("clientsecret"); client.setAutoApproveScopes(Collections.singletonList("true")); IntegrationTestUtils.createClient(adminToken, baseUrl, client); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java index b50f7547871..1bbc40ca0c7 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/util/IntegrationTestUtils.java @@ -1127,12 +1127,22 @@ public static String getClientCredentialsToken(String baseUrl, return accessToken.getValue(); } + public static Map getPasswordToken(String baseUrl, + String clientId, + String clientSecret, + String username, + String password, + String scopes) { + return getPasswordToken(baseUrl, clientId, clientSecret, username, password, scopes, null); + } + public static Map getPasswordToken(String baseUrl, String clientId, String clientSecret, String username, String password, - String scopes) { + String scopes, + String loginHint) { RestTemplate template = new RestTemplate(); template.getMessageConverters().addFirst(new StringHttpMessageConverter(StandardCharsets.UTF_8)); template.setRequestFactory(new StatelessRequestFactory()); @@ -1145,6 +1155,9 @@ public static Map getPasswordToken(String baseUrl, if (hasText(scopes)) { formData.add("scope", scopes); } + if (loginHint != null) { + formData.add("login_hint", "{\"origin\": \""+loginHint+"\"}"); + } HttpHeaders headers = new HttpHeaders(); headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); From 6ed4734cee9e6cd655fe1c494870cc4563338c3e Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Thu, 9 Apr 2026 14:34:00 -0700 Subject: [PATCH 3/3] Add in a configuration option that allows external roles be populated with the UAA internally mapped group names Default behavior when this option is not set, is to align with LDAP and SAML, and documented group mapping strategy. --- docs/UAA-Configuration-Reference.md | 13 +++++ scripts/boot/uaa.yml | 3 + .../beans/OauthEndpointBeanConfiguration.java | 6 +- .../ExternalOAuthAuthenticationManager.java | 10 +++- ...lOAuthAuthenticationManagerGithubTest.java | 2 +- .../ExternalOAuthAuthenticationManagerIT.java | 2 +- ...xternalOAuthAuthenticationManagerTest.java | 56 +++++++++++++++---- ...changeOverrideAuthManagerMockMvcTests.java | 3 +- 8 files changed, 77 insertions(+), 18 deletions(-) diff --git a/docs/UAA-Configuration-Reference.md b/docs/UAA-Configuration-Reference.md index 757cc032a5d..de3cb4c17a4 100644 --- a/docs/UAA-Configuration-Reference.md +++ b/docs/UAA-Configuration-Reference.md @@ -204,6 +204,7 @@ or `$CLOUDFOUNDRY_CONFIG_PATH/uaa.yml`. | `login.allowOriginLoop` | `true`| Allow origin loop| | `login.aliasEntitiesEnabled` | `false`| Enable alias entities| | `login.oauth.providers` | —| External OAuth/OIDC providers| +| `login.oauth.externalGroupsFromMappedAuthorities` | `false`| Use mapped UAA authorities for external OAuth external groups| ### SAML Service Provider @@ -1866,6 +1867,18 @@ External OAuth 2.0 and OIDC provider definitions. Each provider entry includes: --- +### `login.oauth.externalGroupsFromMappedAuthorities` + +**Default:** `false` +**Source:** `@Value("${login.oauth.externalGroupsFromMappedAuthorities:false}")` in [`OauthEndpointBeanConfiguration`](../server/src/main/java/org/cloudfoundry/identity/uaa/oauth/beans/OauthEndpointBeanConfiguration.java) (bean `externalOAuthAuthenticationManager`) +**Type:** `boolean` + +When `false` (default), external OAuth login populates `UaaAuthentication` external group names from IdP token authorities **before** UAA group mapping (`externalAuthorities`). When `true`, external group names are taken from **mapped** UAA authorities (`authorities` after `mapExternalGroups`), for deployments that want downstream external-group membership logic to follow mapped group or scope names instead of raw IdP values. + +[Back to table](#login--branding) + +--- + ### `login.saml.activeKeyId` **Default:** — (none) diff --git a/scripts/boot/uaa.yml b/scripts/boot/uaa.yml index 9409809259b..6bf8be76eb5 100644 --- a/scripts/boot/uaa.yml +++ b/scripts/boot/uaa.yml @@ -63,6 +63,9 @@ login: # The entity base url is the location of this application (Used for SAML SP metadata generation) # Not set for integration tests - allows UAA to use request URL for zone subdomains entityBaseURL: null + # Non-default: integration server exercises mapped-authorities path for external OAuth external groups + oauth: + externalGroupsFromMappedAuthorities: true saml: activeKeyId: key1 keys: diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/beans/OauthEndpointBeanConfiguration.java b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/beans/OauthEndpointBeanConfiguration.java index 1a41869a065..079a9cf003a 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/beans/OauthEndpointBeanConfiguration.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/oauth/beans/OauthEndpointBeanConfiguration.java @@ -555,7 +555,8 @@ ExternalOAuthAuthenticationManager externalOAuthAuthenticationManager( @Qualifier("keyInfoService") KeyInfoService keyInfoService, @Qualifier("oidcMetadataFetcher") OidcMetadataFetcher oidcMetadataFetcher, @Qualifier("userDatabase") UaaUserDatabase userDatabase, - @Qualifier("externalGroupMembershipManager") ScimGroupExternalMembershipManager externalMembershipManager + @Qualifier("externalGroupMembershipManager") ScimGroupExternalMembershipManager externalMembershipManager, + @Value("${login.oauth.externalGroupsFromMappedAuthorities:false}") boolean externalGroupsFromMappedAuthorities ) { ExternalOAuthAuthenticationManager bean = new ExternalOAuthAuthenticationManager( providerProvisioning, @@ -564,7 +565,8 @@ ExternalOAuthAuthenticationManager externalOAuthAuthenticationManager( nonTrustingRestTemplate, tokenEndpointBuilder, keyInfoService, - oidcMetadataFetcher + oidcMetadataFetcher, + externalGroupsFromMappedAuthorities ); bean.setUserDatabase(userDatabase); bean.setExternalMembershipManager(externalMembershipManager); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManager.java index 9376708bd87..7706380db2f 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManager.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManager.java @@ -148,6 +148,7 @@ public class ExternalOAuthAuthenticationManager extends ExternalLoginAuthenticat @Getter private final KeyInfoService keyInfoService; private final IdentityZoneManager identityZoneManager; + private final boolean externalGroupsFromMappedAuthorities; public ExternalOAuthAuthenticationManager( IdentityProviderProvisioning providerProvisioning, @@ -156,7 +157,8 @@ public ExternalOAuthAuthenticationManager( RestTemplate nonTrustingRestTemplate, TokenEndpointBuilder tokenEndpointBuilder, KeyInfoService keyInfoService, - OidcMetadataFetcher oidcMetadataFetcher + OidcMetadataFetcher oidcMetadataFetcher, + boolean externalGroupsFromMappedAuthorities ) { super(providerProvisioning); this.identityZoneManager = identityZoneManager; @@ -165,6 +167,7 @@ public ExternalOAuthAuthenticationManager( this.tokenEndpointBuilder = tokenEndpointBuilder; this.keyInfoService = keyInfoService; this.oidcMetadataFetcher = oidcMetadataFetcher; + this.externalGroupsFromMappedAuthorities = externalGroupsFromMappedAuthorities; } /** @@ -393,8 +396,11 @@ protected void populateAuthenticationAttributes(UaaAuthentication authentication } authentication.setUserAttributes(userAttributes); + List authoritiesForExternalGroups = externalGroupsFromMappedAuthorities + ? authenticationData.getAuthorities() + : authenticationData.getExternalAuthorities(); authentication.setExternalGroups( - Optional.ofNullable(authenticationData.getAuthorities()) + Optional.ofNullable(authoritiesForExternalGroups) .orElse(emptyList()) .stream() .map(GrantedAuthority::getAuthority) diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerGithubTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerGithubTest.java index 99976067ed5..1f20f6d4f72 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerGithubTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerGithubTest.java @@ -92,7 +92,7 @@ void beforeEach() throws Exception { nonTrustingRestTemplate ); authManager = new ExternalOAuthAuthenticationManager(identityProviderProvisioning, new IdentityZoneManagerImpl(), trustingRestTemplate, - nonTrustingRestTemplate, tokenEndpointBuilder, new KeyInfoService(uaaIssuerBaseUrl), oidcMetadataFetcher); + nonTrustingRestTemplate, tokenEndpointBuilder, new KeyInfoService(uaaIssuerBaseUrl), oidcMetadataFetcher, false); } @AfterEach diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerIT.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerIT.java index 52444bfa6a2..237145a686a 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerIT.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerIT.java @@ -230,7 +230,7 @@ void setUp() throws Exception { identityZoneProvisioning, identityZoneManager) ); - externalOAuthAuthenticationManager = spy(new ExternalOAuthAuthenticationManager(externalOAuthProviderConfigurator, identityZoneManager, trustingRestTemplate, nonTrustingRestTemplate, tokenEndpointBuilder, new KeyInfoService(UAA_ISSUER_URL), oidcMetadataFetcher)); + externalOAuthAuthenticationManager = spy(new ExternalOAuthAuthenticationManager(externalOAuthProviderConfigurator, identityZoneManager, trustingRestTemplate, nonTrustingRestTemplate, tokenEndpointBuilder, new KeyInfoService(UAA_ISSUER_URL), oidcMetadataFetcher, false)); externalOAuthAuthenticationManager.setUserDatabase(userDatabase); externalOAuthAuthenticationManager.setExternalMembershipManager(externalMembershipManager); externalOAuthAuthenticationManager.setApplicationEventPublisher(publisher); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerTest.java index a568e3fe262..f31894bd580 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerTest.java @@ -220,7 +220,7 @@ void beforeEach() throws Exception { new RestTemplate(), new RestTemplate() ); - authManager = new ExternalOAuthAuthenticationManager(identityProviderProvisioning, new IdentityZoneManagerImpl(), new RestTemplate(), new RestTemplate(), tokenEndpointBuilder, new KeyInfoService(UAA_ISSUER_BASE_URL), oidcMetadataFetcher); + authManager = new ExternalOAuthAuthenticationManager(identityProviderProvisioning, new IdentityZoneManagerImpl(), new RestTemplate(), new RestTemplate(), tokenEndpointBuilder, new KeyInfoService(UAA_ISSUER_BASE_URL), oidcMetadataFetcher, false); authManager.setExternalMembershipManager(externalMembershipManager); authManager.setUserDatabase(userDatabase); } @@ -606,7 +606,40 @@ void getUser_doesThrowWhenIdTokenMappingIsWrongType() { } @Test - void populateAuthenticationAttributes_setsIdpIdTokenAndExternalGroupsFromMappedAuthorities() { + void populateAuthenticationAttributes_setsIdpIdTokenAndExternalGroupsFromExternalAuthoritiesByDefault() { + UaaAuthentication authentication = new UaaAuthentication(new UaaPrincipal("user-guid", "marissa", "marissa@test.org", "uaa", "", ""), Collections.emptyList(), null); + Map header = map( + entry(HeaderParameterNames.ALGORITHM, JWSAlgorithm.RS256.getName()), + entry(HeaderParameterNames.KEY_ID, OIDC_PROVIDER_KEY) + ); + JWSSigner signer = new KeyInfo("uaa-key", OIDC_PROVIDER_TOKEN_SIGNING_KEY, DEFAULT_UAA_URL).getSigner(); + Map entryMap = map( + entry("external_map_name", Arrays.asList("bar", "baz")) + ); + Map claims = map( + entry("external_family_name", entryMap), + entry(ISS, oidcConfig.getIssuer()), + entry(AUD, "uaa-relying-party"), + entry(EXPIRY_IN_SECONDS, ((int) (System.currentTimeMillis() / 1000L)) + 60), + entry(SUB, "abc-def-asdf") + ); + IdentityZoneHolder.get().getConfig().getTokenPolicy().setKeys(Collections.singletonMap("uaa-key", UAA_IDENTITY_ZONE_TOKEN_SIGNING_KEY)); + String idTokenJwt = UaaTokenUtils.constructToken(header, claims, signer); + ExternalOAuthCodeToken oidcAuthentication = new ExternalOAuthCodeToken(null, ORIGIN, "http://google.com", idTokenJwt, "accesstoken", "signedrequest"); + ExternalOAuthAuthenticationManager.AuthenticationData authenticationData = authManager.getExternalAuthenticationDetails(oidcAuthentication); + authenticationData.setAuthorities(List.of(new SimpleGrantedAuthority("uaa-authorities"))); + authenticationData.setExternalAuthorities(List.of(new SimpleGrantedAuthority("raw_external_group"))); + authManager.populateAuthenticationAttributes(authentication, oidcAuthentication, authenticationData); + assertThat(authentication.getIdpIdToken()).isEqualTo(idTokenJwt); + assertThat(authentication.getExternalGroups()).containsExactlyInAnyOrder("raw_external_group"); + } + + @Test + void populateAuthenticationAttributes_setsExternalGroupsFromMappedAuthoritiesWhenEnabled() { + authManager = new ExternalOAuthAuthenticationManager(identityProviderProvisioning, new IdentityZoneManagerImpl(), new RestTemplate(), new RestTemplate(), tokenEndpointBuilder, new KeyInfoService(UAA_ISSUER_BASE_URL), oidcMetadataFetcher, true); + authManager.setExternalMembershipManager(externalMembershipManager); + authManager.setUserDatabase(userDatabase); + UaaAuthentication authentication = new UaaAuthentication(new UaaPrincipal("user-guid", "marissa", "marissa@test.org", "uaa", "", ""), Collections.emptyList(), null); Map header = map( entry(HeaderParameterNames.ALGORITHM, JWSAlgorithm.RS256.getName()), @@ -654,7 +687,7 @@ void getClaimsFromToken_setsIdToken() { String idTokenJwt = UaaTokenUtils.constructToken(header, claims, signer); ExternalOAuthCodeToken codeToken = new ExternalOAuthCodeToken("thecode", ORIGIN, "http://google.com", null, "accesstoken", "signedrequest"); - authManager = new ExternalOAuthAuthenticationManager(identityProviderProvisioning, new IdentityZoneManagerImpl(), new RestTemplate(), new RestTemplate(), tokenEndpointBuilder, new KeyInfoService(UAA_ISSUER_BASE_URL), null) { + authManager = new ExternalOAuthAuthenticationManager(identityProviderProvisioning, new IdentityZoneManagerImpl(), new RestTemplate(), new RestTemplate(), tokenEndpointBuilder, new KeyInfoService(UAA_ISSUER_BASE_URL), null, false) { @Override protected > String getTokenFromCode( ExternalOAuthCodeToken codeToken, @@ -675,7 +708,7 @@ protected > String void fetchOidcMetadata() throws OidcMetadataFetchingException { OIDCIdentityProviderDefinition mockedProviderDefinition = mock(OIDCIdentityProviderDefinition.class); OidcMetadataFetcher mockedOidcMetadataFetcher = mock(OidcMetadataFetcher.class); - authManager = new ExternalOAuthAuthenticationManager(identityProviderProvisioning, new IdentityZoneManagerImpl(), new RestTemplate(), new RestTemplate(), tokenEndpointBuilder, new KeyInfoService(UAA_ISSUER_BASE_URL), mockedOidcMetadataFetcher); + authManager = new ExternalOAuthAuthenticationManager(identityProviderProvisioning, new IdentityZoneManagerImpl(), new RestTemplate(), new RestTemplate(), tokenEndpointBuilder, new KeyInfoService(UAA_ISSUER_BASE_URL), mockedOidcMetadataFetcher, false); doThrow(new OidcMetadataFetchingException("error")).when(mockedOidcMetadataFetcher).fetchMetadataAndUpdateDefinition(mockedProviderDefinition); assertThatNoException().isThrownBy(() -> authManager.fetchMetadataAndUpdateDefinition(mockedProviderDefinition)); } @@ -830,7 +863,7 @@ void oidcPasswordGrantProviderJwtClientCredentials() throws ParseException, JOSE when(rt.exchange(eq("http://localhost:8080/uaa/oauth/token"), eq(HttpMethod.POST), any(HttpEntity.class), any(ParameterizedTypeReference.class))).thenReturn(responseEntity); when(responseEntity.hasBody()).thenReturn(true); when(responseEntity.getBody()).thenReturn(Map.of("id_token", "dummy")); - authManager = new ExternalOAuthAuthenticationManager(identityProviderProvisioning, new IdentityZoneManagerImpl(), rt, rt, tokenEndpointBuilder, keyInfoService, oidcMetadataFetcher); + authManager = new ExternalOAuthAuthenticationManager(identityProviderProvisioning, new IdentityZoneManagerImpl(), rt, rt, tokenEndpointBuilder, keyInfoService, oidcMetadataFetcher, false); // When authManager.oauthTokenRequest(null, mockOidcIdentityProvider(), GRANT_TYPE_PASSWORD, new LinkedMaskingMultiValueMap<>()); @@ -867,7 +900,7 @@ void oidcJwtBearerProviderJwtClientCredentials() throws ParseException, JOSEExce when(rt.exchange(eq("http://localhost:8080/uaa/oauth/token"), eq(HttpMethod.POST), any(HttpEntity.class), any(ParameterizedTypeReference.class))).thenReturn(responseEntity); when(responseEntity.hasBody()).thenReturn(true); when(responseEntity.getBody()).thenReturn(Map.of("id_token", "dummy")); - authManager = new ExternalOAuthAuthenticationManager(identityProviderProvisioning, new IdentityZoneManagerImpl(), rt, rt, tokenEndpointBuilder, keyInfoService, oidcMetadataFetcher); + authManager = new ExternalOAuthAuthenticationManager(identityProviderProvisioning, new IdentityZoneManagerImpl(), rt, rt, tokenEndpointBuilder, keyInfoService, oidcMetadataFetcher, false); // When assertThat(authManager.oidcJwtBearerGrant(uaaAuthenticationDetails, mockOidcIdentityProvider() , "proxy-token")).isEqualTo("dummy"); @@ -907,7 +940,7 @@ void oidcJwtBearerProviderProxyThrowException() throws JOSEException, MalformedU when(config.getRelyingPartySecret()).thenReturn("secret"); doReturn(false).when(config).isClientAuthInBody(); when(rt.exchange(eq("http://localhost:8080/uaa/oauth/token"), eq(HttpMethod.POST), any(HttpEntity.class), any(ParameterizedTypeReference.class))).thenThrow(new HttpClientErrorException(HttpStatus.UNAUTHORIZED)); - authManager = new ExternalOAuthAuthenticationManager(identityProviderProvisioning, new IdentityZoneManagerImpl(), rt, rt, tokenEndpointBuilder, keyInfoService, oidcMetadataFetcher); + authManager = new ExternalOAuthAuthenticationManager(identityProviderProvisioning, new IdentityZoneManagerImpl(), rt, rt, tokenEndpointBuilder, keyInfoService, oidcMetadataFetcher, false); // When assertThatThrownBy(() -> authManager.oidcJwtBearerGrant(uaaAuthenticationDetails, identityProvider, "proxy-token")) @@ -940,7 +973,7 @@ void oidcPasswordGrantWithForwardHeader() throws JOSEException, MalformedURLExce when(auth.getDetails()).thenReturn(details); RestTemplate rt = mock(RestTemplate.class); - authManager = new ExternalOAuthAuthenticationManager(identityProviderProvisioning, new IdentityZoneManagerImpl(), rt, rt, tokenEndpointBuilder, keyInfoService, oidcMetadataFetcher); + authManager = new ExternalOAuthAuthenticationManager(identityProviderProvisioning, new IdentityZoneManagerImpl(), rt, rt, tokenEndpointBuilder, keyInfoService, oidcMetadataFetcher, false); ResponseEntity> response = mock(ResponseEntity.class); when(response.hasBody()).thenReturn(true); @@ -993,7 +1026,7 @@ void oidcPasswordGrant_credentialsMustBeStringButNoSecretNeeded() throws Malform when(restTemplate.exchange(anyString(), any(HttpMethod.class), any(HttpEntity.class), any(ParameterizedTypeReference.class))).thenReturn(responseEntity); when(responseEntity.hasBody()).thenReturn(true); when(responseEntity.getBody()).thenReturn(Map.of("id_token", "dummy")); - authManager = new ExternalOAuthAuthenticationManager(identityProviderProvisioning, new IdentityZoneManagerImpl(), restTemplate, restTemplate, tokenEndpointBuilder, mockKeyInfoService(), oidcMetadataFetcher); + authManager = new ExternalOAuthAuthenticationManager(identityProviderProvisioning, new IdentityZoneManagerImpl(), restTemplate, restTemplate, tokenEndpointBuilder, mockKeyInfoService(), oidcMetadataFetcher, false); final IdentityProvider localIdp = new IdentityProvider<>(); localIdp.setOriginKey(new AlphanumericRandomValueStringGenerator(8).generate().toLowerCase()); @@ -1045,7 +1078,7 @@ void oidcPasswordGrantWithPrompts() throws MalformedURLException, JOSEException when(config.getScopes()).thenReturn(null); RestTemplate rt = mock(RestTemplate.class); - authManager = new ExternalOAuthAuthenticationManager(identityProviderProvisioning, new IdentityZoneManagerImpl(), rt, rt, tokenEndpointBuilder, keyInfoService, oidcMetadataFetcher); + authManager = new ExternalOAuthAuthenticationManager(identityProviderProvisioning, new IdentityZoneManagerImpl(), rt, rt, tokenEndpointBuilder, keyInfoService, oidcMetadataFetcher, false); ResponseEntity> response = mock(ResponseEntity.class); when(response.hasBody()).thenReturn(true); @@ -1103,7 +1136,8 @@ void oauth20AuthorizationFlowWithUserInfo() throws Exception { restTemplate, tokenEndpointBuilder, new KeyInfoService("http://uaa.example.com"), - null + null, + false ) { @Override protected > String getTokenFromCode( diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenExchangeOverrideAuthManagerMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenExchangeOverrideAuthManagerMockMvcTests.java index 4e2a85c7485..ea124da2a27 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenExchangeOverrideAuthManagerMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenExchangeOverrideAuthManagerMockMvcTests.java @@ -59,7 +59,8 @@ ExternalOAuthAuthenticationManager tokenExchangeAuthenticationManager( nonTrustingRestTemplate, tokenEndpointBuilder, keyInfoService, - oidcMetadataFetcher + oidcMetadataFetcher, + false ) { @Override public AuthenticationData getExternalAuthenticationDetails(Authentication authentication) {