diff --git a/ldap/src/integration-test/java/org/springframework/security/ldap/userdetails/DefaultLdapAuthoritiesPopulatorTests.java b/ldap/src/integration-test/java/org/springframework/security/ldap/userdetails/DefaultLdapAuthoritiesPopulatorTests.java index b96a69738e..f1789129ae 100644 --- a/ldap/src/integration-test/java/org/springframework/security/ldap/userdetails/DefaultLdapAuthoritiesPopulatorTests.java +++ b/ldap/src/integration-test/java/org/springframework/security/ldap/userdetails/DefaultLdapAuthoritiesPopulatorTests.java @@ -18,9 +18,12 @@ package org.springframework.security.ldap.userdetails; import static org.assertj.core.api.Assertions.*; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + import org.junit.*; import org.junit.runner.RunWith; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.ldap.core.ContextSource; import org.springframework.ldap.core.DirContextAdapter; @@ -29,11 +32,10 @@ import org.springframework.ldap.core.DistinguishedName; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.ldap.ApacheDsContainerConfig; +import org.springframework.security.ldap.SpringSecurityLdapTemplate; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; -import java.util.*; - /** * * @author Luke Taylor @@ -185,4 +187,25 @@ public class DefaultLdapAuthoritiesPopulatorTests { assertThat(authorities).as("Should have 1 role").hasSize(1); assertThat(authorities.contains("ROLE_MANAGER")).isTrue(); } + + @Test + public void customAuthoritiesMappingFunction() { + populator.setAuthorityMapper(record -> { + String dn = record.get(SpringSecurityLdapTemplate.DN_KEY).get(0); + String role = record.get(populator.getGroupRoleAttribute()).get(0); + return new LdapAuthority(role, dn); + }); + + DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName( + "cn=mouse\\, jerry,ou=people,dc=springframework,dc=org")); + + Collection authorities = populator.getGrantedAuthorities(ctx, "notused"); + + assertThat(authorities).allMatch(LdapAuthority.class::isInstance); + } + + @Test(expected = IllegalArgumentException.class) + public void customAuthoritiesMappingFunctionThrowsIfNull() { + populator.setAuthorityMapper(null); + } } diff --git a/ldap/src/main/java/org/springframework/security/ldap/userdetails/DefaultLdapAuthoritiesPopulator.java b/ldap/src/main/java/org/springframework/security/ldap/userdetails/DefaultLdapAuthoritiesPopulator.java index 9e1289b8ab..12f311f1c8 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/userdetails/DefaultLdapAuthoritiesPopulator.java +++ b/ldap/src/main/java/org/springframework/security/ldap/userdetails/DefaultLdapAuthoritiesPopulator.java @@ -20,13 +20,14 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.function.Function; import javax.naming.directory.SearchControls; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.ldap.core.ContextSource; import org.springframework.ldap.core.DirContextOperations; import org.springframework.ldap.core.LdapTemplate; @@ -137,15 +138,22 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator * The pattern to be used for the user search. {0} is the user's DN */ private String groupSearchFilter = "(member={0})"; + /** * The role prefix that will be prepended to each role name */ private String rolePrefix = "ROLE_"; + /** * Should we convert the role name to uppercase */ private boolean convertToUpperCase = true; + /** + * The mapping function to be used to populate authorities. + */ + private Function>, GrantedAuthority> authorityMapper; + // ~ Constructors // =================================================================================================== @@ -171,6 +179,16 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator logger.info( "groupSearchBase is empty. Searches will be performed from the context source base"); } + + this.authorityMapper = record -> { + String role = record.get(this.groupRoleAttribute).get(0); + + if (this.convertToUpperCase) { + role = role.toUpperCase(); + } + + return new SimpleGrantedAuthority(this.rolePrefix + role); + }; } // ~ Methods @@ -238,21 +256,18 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator + " in search base '" + getGroupSearchBase() + "'"); } - Set userRoles = getLdapTemplate().searchForSingleAttributeValues( - getGroupSearchBase(), this.groupSearchFilter, - new String[] { userDn, username }, this.groupRoleAttribute); + Set>> userRoles = getLdapTemplate() + .searchForMultipleAttributeValues(getGroupSearchBase(), + this.groupSearchFilter, + new String[] { userDn, username }, + new String[] { this.groupRoleAttribute }); if (logger.isDebugEnabled()) { logger.debug("Roles from search: " + userRoles); } - for (String role : userRoles) { - - if (this.convertToUpperCase) { - role = role.toUpperCase(); - } - - authorities.add(new SimpleGrantedAuthority(this.rolePrefix + role)); + for (Map> role : userRoles) { + authorities.add(authorityMapper.apply(role)); } return authorities; @@ -325,6 +340,17 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator getLdapTemplate().setIgnorePartialResultException(ignore); } + /** + * Sets the mapping function which will be used to create instances of {@link GrantedAuthority} + * given the context record. + * + * @param authorityMapper the mapping function + */ + public void setAuthorityMapper(Function>, GrantedAuthority> authorityMapper) { + Assert.notNull(authorityMapper, "authorityMapper must not be null"); + this.authorityMapper = authorityMapper; + } + /** * Returns the current LDAP template. Method available so that classes extending this * can override the template used