diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManager.java b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManager.java index 871fa284030..e5721ab18d2 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManager.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/ExternalLoginAuthenticationManager.java @@ -46,6 +46,9 @@ import org.springframework.util.MultiValueMap; import java.util.Date; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; public class ExternalLoginAuthenticationManager implements AuthenticationManager, ApplicationEventPublisherAware, BeanNameAware { @@ -121,7 +124,9 @@ public Authentication authenticate(Authentication request) throws Authentication } UaaAuthentication success = new UaaAuthentication(new UaaPrincipal(user), user.getAuthorities(), uaaAuthenticationDetails); if (request.getPrincipal() instanceof UserDetails) { - success.setUserAttributes(getUserAttributes((UserDetails) request.getPrincipal())); + UserDetails userDetails = (UserDetails) request.getPrincipal(); + success.setUserAttributes(getUserAttributes(userDetails)); + success.setExternalGroups(new HashSet<>(getExternalUserAuthorities(userDetails))); } publish(new UserAuthenticationSuccessEvent(user, success)); return success; @@ -131,6 +136,10 @@ protected MultiValueMap getUserAttributes(UserDetails request) { return new LinkedMultiValueMap<>(); } + protected List getExternalUserAuthorities(UserDetails request) { + return new LinkedList<>(); + } + protected void publish(ApplicationEvent event) { if (eventPublisher != null) { eventPublisher.publishEvent(event); diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManager.java b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManager.java index f285e305dc7..54490b21605 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManager.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManager.java @@ -18,21 +18,29 @@ import org.apache.commons.lang.StringUtils; import org.cloudfoundry.identity.uaa.ldap.ExtendedLdapUserDetails; import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.ldap.extension.LdapAuthority; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.zone.IdentityProvider; import org.cloudfoundry.identity.uaa.zone.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.util.MultiValueMap; import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; import java.util.Map; +import java.util.Set; + +import static java.util.Collections.EMPTY_LIST; public class LdapLoginAuthenticationManager extends ExternalLoginAuthenticationManager { public static final String USER_ATTRIBUTE_PREFIX = "user.attribute."; - private boolean autoAddAuthorities = false; private IdentityProviderProvisioning provisioning; public void setProvisioning(IdentityProviderProvisioning provisioning) { @@ -62,6 +70,34 @@ protected MultiValueMap getUserAttributes(UserDetails request) { return result; } + @Override + protected List getExternalUserAuthorities(UserDetails request) { + List result = super.getExternalUserAuthorities(request); + if (provisioning!=null) { + IdentityProvider provider = provisioning.retrieveByOrigin(getOrigin(), IdentityZoneHolder.get().getId()); + LdapIdentityProviderDefinition ldapIdentityProviderDefinition = provider.getConfigValue(LdapIdentityProviderDefinition.class); + List externalWhiteList = ldapIdentityProviderDefinition.getExternalGroupsWhitelist(); + result = new LinkedList<>(getAuthoritesAsNames(request.getAuthorities())); + result.retainAll(externalWhiteList); + } + return result; + } + + protected Set getAuthoritesAsNames(Collection authorities) { + Set result = new HashSet<>(); + authorities = new LinkedList(authorities!=null?authorities: EMPTY_LIST); + for (GrantedAuthority a : authorities) { + if (a instanceof LdapAuthority) { + LdapAuthority la = (LdapAuthority)a; + String[] groupNames = la.getAttributeValues("cn"); + if (groupNames!=null) { + result.addAll(Arrays.asList(groupNames)); + } + } + } + return result; + } + @Override protected UaaUser userAuthenticated(Authentication request, UaaUser user) { boolean userModified = false; @@ -78,12 +114,16 @@ protected UaaUser userAuthenticated(Authentication request, UaaUser user) { return getUserDatabase().retrieveUserById(user.getId()); } - public boolean isAutoAddAuthorities() { - return autoAddAuthorities; - } - - public void setAutoAddAuthorities(boolean autoAddAuthorities) { - this.autoAddAuthorities = autoAddAuthorities; + protected boolean isAutoAddAuthorities() { + Boolean result = true; + if (provisioning!=null) { + IdentityProvider provider = provisioning.retrieveByOrigin(getOrigin(), IdentityZoneHolder.get().getId()); + LdapIdentityProviderDefinition ldapIdentityProviderDefinition = provider.getConfigValue(LdapIdentityProviderDefinition.class); + if (ldapIdentityProviderDefinition!=null) { + result = ldapIdentityProviderDefinition.isAutoAddGroups(); + } + } + return result!=null ? result.booleanValue() : true; } private boolean haveUserAttributesChanged(UaaUser existingUser, UaaUser user) { diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManagerTests.java b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManagerTests.java index ce1ddaa4ada..3672d78cbb3 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManagerTests.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/LdapLoginAuthenticationManagerTests.java @@ -169,13 +169,15 @@ public void testGetUserWithNonLdapInfo() throws Exception { @Test public void testUserAuthenticated() throws Exception { + + UaaUser user = getUaaUser(); - am.setAutoAddAuthorities(true); + definition.setAutoAddGroups(true); UaaUser result = am.userAuthenticated(auth, user); assertSame(dbUser, result); verify(publisher, times(1)).publishEvent(Matchers.anyObject()); - am.setAutoAddAuthorities(false); + definition.setAutoAddGroups(false); result = am.userAuthenticated(auth, user); assertSame(dbUser, result); verify(publisher, times(2)).publishEvent(Matchers.anyObject()); diff --git a/uaa/src/main/resources/ldap_init.ldif b/uaa/src/main/resources/ldap_init.ldif index da90ec9ec15..15c289c942c 100644 --- a/uaa/src/main/resources/ldap_init.ldif +++ b/uaa/src/main/resources/ldap_init.ldif @@ -173,6 +173,14 @@ cn: uaa.admin member: cn=admin,ou=Users,dc=test,dc=com member: cn=marissa3,ou=Users,dc=test,dc=com +dn: cn=thirdmarissa,ou=scopes,dc=test,dc=com +changetype: add +objectClass: groupOfNames +objectClass: top +cn: thirdmarissa +description: thirdmarissa +member: cn=marissa3,ou=Users,dc=test,dc=com + dn: cn=developers,ou=scopes,dc=test,dc=com changetype: add objectClass: groupOfNames diff --git a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml index cf5637ce585..c8cff6e777c 100755 --- a/uaa/src/main/webapp/WEB-INF/spring-servlet.xml +++ b/uaa/src/main/webapp/WEB-INF/spring-servlet.xml @@ -298,14 +298,9 @@ - - - - - diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegationTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegationTests.java index 09f19d2c2f1..244ae3f7986 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegationTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/LdapIntegationTests.java @@ -114,6 +114,9 @@ public void test_LDAP_Custom_User_Attributes_In_ID_Token() throws Exception { true); ldapIdentityProviderDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX+COST_CENTERS, COST_CENTER); ldapIdentityProviderDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX+MANAGERS, MANAGER); + ldapIdentityProviderDefinition.addWhiteListedGroup("marissaniner"); + ldapIdentityProviderDefinition.addWhiteListedGroup("marissaniner2"); + IdentityProvider provider = new IdentityProvider(); provider.setIdentityZoneId(zoneId); @@ -130,7 +133,7 @@ public void test_LDAP_Custom_User_Attributes_In_ID_Token() throws Exception { List idps = Arrays.asList(provider.getOriginKey()); String adminClientInZone = new RandomValueStringGenerator().generate(); - BaseClientDetails clientDetails = new BaseClientDetails(adminClientInZone, null, "openid,user_attributes", "password,authorization_code,client_credentials", "uaa.admin,scim.read,scim.write,uaa.resource", zoneUrl); + BaseClientDetails clientDetails = new BaseClientDetails(adminClientInZone, null, "openid,user_attributes,roles", "password,authorization_code,client_credentials", "uaa.admin,scim.read,scim.write,uaa.resource", zoneUrl); clientDetails.setClientSecret("secret"); clientDetails.addAdditionalInformation(ClientConstants.AUTO_APPROVE, true); clientDetails.addAdditionalInformation(ClientConstants.ALLOWED_PROVIDERS, idps); @@ -145,7 +148,7 @@ public void test_LDAP_Custom_User_Attributes_In_ID_Token() throws Exception { clientDetails.getClientSecret(), "marissa9", "ldap9", - "openid user_attributes") + "openid user_attributes roles") .get("id_token"); assertNotNull(idToken); @@ -158,6 +161,11 @@ public void test_LDAP_Custom_User_Attributes_In_ID_Token() throws Exception { assertThat(userAttributes.get(COST_CENTERS), containsInAnyOrder(DENVER_CO)); assertThat(userAttributes.get(MANAGERS), containsInAnyOrder(JOHN_THE_SLOTH, KARI_THE_ANT_EATER)); + + assertNotNull(claims.get(Claims.ROLES)); + List roles = (List) claims.get(Claims.ROLES); + assertThat(roles, containsInAnyOrder("marissaniner", "marissaniner2")); + //no user_attribute scope provided idToken = (String) IntegrationTestUtils.getPasswordToken(zoneUrl, @@ -173,6 +181,7 @@ public void test_LDAP_Custom_User_Attributes_In_ID_Token() throws Exception { idTokenClaims = JwtHelper.decode(idToken); claims = JsonUtils.readValue(idTokenClaims.getClaims(), new TypeReference>() {}); assertNull(claims.get(Claims.USER_ATTRIBUTES)); + assertNull(claims.get(Claims.ROLES)); } protected boolean doesSupportZoneDNS_and_isLdapEnabled() { diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapMockMvcTests.java index 7b91e7a6b15..c7355ef4abd 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapMockMvcTests.java @@ -89,6 +89,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; @@ -220,9 +221,48 @@ private void deleteLdapUsers() { jdbcTemplate.update("delete from users where origin='" + Origin.LDAP + "'"); } + @Test + public void test_whitelisted_external_groups() throws Exception { + Assume.assumeThat("ldap-groups-map-to-scopes.xml, ldap-groups-as-scopes.xml", StringContains.containsString(ldapGroup)); + setUp(); + IdentityProviderProvisioning idpProvisioning = mainContext.getBean(IdentityProviderProvisioning.class); + IdentityProvider idp = idpProvisioning.retrieveByOrigin(Origin.LDAP, IdentityZone.getUaa().getId()); + LdapIdentityProviderDefinition def = idp.getConfigValue(LdapIdentityProviderDefinition.class); + def.addWhiteListedGroup("admins"); + def.addWhiteListedGroup("thirdmarissa"); + idp.setConfig(JsonUtils.writeValueAsString(def)); + idpProvisioning.update(idp); + AuthenticationManager manager = mainContext.getBean(DynamicZoneAwareAuthenticationManager.class); + UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("marissa3", "ldap3"); + Authentication auth = manager.authenticate(token); + assertNotNull(auth); + assertTrue(auth instanceof UaaAuthentication); + UaaAuthentication uaaAuth = (UaaAuthentication) auth; + Set externalGroups = uaaAuth.getExternalGroups(); + assertNotNull(externalGroups); + assertEquals(2, externalGroups.size()); + assertThat(externalGroups, containsInAnyOrder("admins", "thirdmarissa")); + } + + @Test + public void test_external_groups_with_default_whitelist() throws Exception { + Assume.assumeThat("ldap-groups-map-to-scopes.xml, ldap-groups-as-scopes.xml", StringContains.containsString(ldapGroup)); + setUp(); + AuthenticationManager manager = mainContext.getBean(DynamicZoneAwareAuthenticationManager.class); + UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("marissa3", "ldap3"); + Authentication auth = manager.authenticate(token); + assertNotNull(auth); + assertTrue(auth instanceof UaaAuthentication); + UaaAuthentication uaaAuth = (UaaAuthentication) auth; + Set externalGroups = uaaAuth.getExternalGroups(); + assertNotNull(externalGroups); + assertEquals(0, externalGroups.size()); + } + + @Test public void testCustomUserAttributes() throws Exception { - Assume.assumeThat("ldap-groups-null.xml", StringContains.containsString(ldapGroup)); + Assume.assumeThat("ldap-groups-map-to-scopes.xml, ldap-groups-as-scopes.xml", StringContains.containsString(ldapGroup)); final String MANAGER = "uaaManager"; final String MANAGERS = "managers"; @@ -957,7 +997,8 @@ public void testLdapScopes() throws Exception { assertNotNull(auth); String[] list = new String[]{ "uaa.admin", - "cloud_controller.read" + "cloud_controller.read", + "thirdmarissa" }; assertThat(list, arrayContainingInAnyOrder(getAuthorities(auth.getAuthorities()))); } @@ -984,7 +1025,8 @@ public void testLdapScopesFromChainedAuth() throws Exception { "oauth.approvals", "uaa.user", "cloud_controller.read", - "user_attributes" + "user_attributes", + "thirdmarissa" }; assertThat(list, arrayContainingInAnyOrder(getAuthorities(auth.getAuthorities()))); }