Update LDAP authority and role handling to use LdapClient
Replaced SpringSecurityLdapTemplate with LdapClient for improved LDAP search and entry handling. Refactored related methods and tests to handle `NamingException`, use `LdapName` for DNs, and support updated attribute handling with `Attributes`. Updated authority mapping logic to streamline nested group resolution and enhance test cases. Signed-off-by: Fernando Marino <f.marino@stariongroup.eu>
This commit is contained in:
parent
9df3a57d9e
commit
1e4a38dfe3
|
@ -53,10 +53,11 @@ public class DefaultLdapAuthoritiesPopulatorGetGrantedAuthoritiesTests {
|
|||
@BeforeEach
|
||||
public void setUp() {
|
||||
this.populator = new DefaultLdapAuthoritiesPopulator(this.contextSource, "ou=groups");
|
||||
this.populator.setIgnorePartialResultException(false);
|
||||
// this.populator.setIgnorePartialResultException(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
// fix me this is broken with the LdapClient
|
||||
public void groupSearchDoesNotAllowNullRoles() {
|
||||
this.populator.setRolePrefix("ROLE_");
|
||||
this.populator.setGroupRoleAttribute("ou");
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package org.springframework.security.ldap.userdetails;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -36,6 +37,9 @@ import org.springframework.security.ldap.SpringSecurityLdapTemplate;
|
|||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import javax.naming.Name;
|
||||
import javax.naming.NamingException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
|
@ -56,13 +60,13 @@ public class DefaultLdapAuthoritiesPopulatorTests {
|
|||
@BeforeEach
|
||||
public void setUp() {
|
||||
this.populator = new DefaultLdapAuthoritiesPopulator(this.contextSource, "ou=groups");
|
||||
this.populator.setIgnorePartialResultException(false);
|
||||
// this.populator.setIgnorePartialResultException(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultRoleIsAssignedWhenSet() {
|
||||
this.populator.setDefaultRole("ROLE_USER");
|
||||
assertThat(this.populator.getContextSource()).isSameAs(this.contextSource);
|
||||
// assertThat(this.populator.getContextSource()).isSameAs(this.contextSource);
|
||||
|
||||
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName("cn=notfound"));
|
||||
|
||||
|
@ -185,10 +189,17 @@ public class DefaultLdapAuthoritiesPopulatorTests {
|
|||
|
||||
@Test
|
||||
public void customAuthoritiesMappingFunction() {
|
||||
this.populator.setAuthorityMapper((record) -> {
|
||||
String dn = record.get(SpringSecurityLdapTemplate.DN_KEY).get(0);
|
||||
String role = record.get(this.populator.getGroupRoleAttribute()).get(0);
|
||||
return new LdapAuthority(role, dn);
|
||||
this.populator.setAuthorityMapper((entry) -> {
|
||||
Name dn;
|
||||
String role;
|
||||
try {
|
||||
dn = entry.getDn();
|
||||
role = entry.getAttributes().get(this.populator.getGroupRoleAttribute()).get().toString();
|
||||
}
|
||||
catch (NamingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return Collections.singleton(new LdapAuthority(role, dn));
|
||||
});
|
||||
|
||||
DirContextAdapter ctx = new DirContextAdapter(
|
||||
|
|
|
@ -16,9 +16,7 @@
|
|||
|
||||
package org.springframework.security.ldap.userdetails;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.*;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -32,6 +30,10 @@ import org.springframework.security.ldap.ApacheDsContainerConfig;
|
|||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import javax.naming.InvalidNameException;
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.ldap.LdapName;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
|
@ -60,24 +62,25 @@ public class NestedLdapAuthoritiesPopulatorTests {
|
|||
private LdapAuthority circularJavaDevelopers;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
public void setUp() throws InvalidNameException {
|
||||
this.populator = new NestedLdapAuthoritiesPopulator(this.contextSource, "ou=jdeveloper");
|
||||
this.populator.setGroupSearchFilter("(member={0})");
|
||||
this.populator.setIgnorePartialResultException(false);
|
||||
// this.populator.setIgnorePartialResultException(false);
|
||||
this.populator.setRolePrefix("");
|
||||
this.populator.setSearchSubtree(true);
|
||||
this.populator.setConvertToUpperCase(false);
|
||||
this.jDevelopers = new LdapAuthority("j-developers", "cn=j-developers,ou=jdeveloper,dc=springframework,dc=org");
|
||||
this.jDevelopers = new LdapAuthority("j-developers",
|
||||
new LdapName("cn=j-developers,ou=jdeveloper,dc=springframework,dc=org"));
|
||||
this.javaDevelopers = new LdapAuthority("java-developers",
|
||||
"cn=java-developers,ou=jdeveloper,dc=springframework,dc=org");
|
||||
new LdapName("cn=java-developers,ou=jdeveloper,dc=springframework,dc=org"));
|
||||
this.groovyDevelopers = new LdapAuthority("groovy-developers",
|
||||
"cn=groovy-developers,ou=jdeveloper,dc=springframework,dc=org");
|
||||
new LdapName("cn=groovy-developers,ou=jdeveloper,dc=springframework,dc=org"));
|
||||
this.scalaDevelopers = new LdapAuthority("scala-developers",
|
||||
"cn=scala-developers,ou=jdeveloper,dc=springframework,dc=org");
|
||||
new LdapName("cn=scala-developers,ou=jdeveloper,dc=springframework,dc=org"));
|
||||
this.closureDevelopers = new LdapAuthority("closure-developers",
|
||||
"cn=closure-developers,ou=jdeveloper,dc=springframework,dc=org");
|
||||
new LdapName("cn=closure-developers,ou=jdeveloper,dc=springframework,dc=org"));
|
||||
this.circularJavaDevelopers = new LdapAuthority("circular-java-developers",
|
||||
"cn=circular-java-developers,ou=jdeveloper,dc=springframework,dc=org");
|
||||
new LdapName("cn=circular-java-developers,ou=jdeveloper,dc=springframework,dc=org"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -95,6 +98,9 @@ public class NestedLdapAuthoritiesPopulatorTests {
|
|||
Collection<GrantedAuthority> authorities = this.populator.getGrantedAuthorities(ctx, "javadude");
|
||||
assertThat(authorities).hasSize(4);
|
||||
assertThat(authorities).contains(this.javaDevelopers);
|
||||
assertThat(authorities).contains(this.circularJavaDevelopers);
|
||||
assertThat(authorities).contains(this.groovyDevelopers);
|
||||
assertThat(authorities).contains(this.jDevelopers);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -103,12 +109,12 @@ public class NestedLdapAuthoritiesPopulatorTests {
|
|||
DirContextAdapter ctx = new DirContextAdapter("uid=scaladude,ou=people,dc=springframework,dc=org");
|
||||
Collection<GrantedAuthority> authorities = this.populator.getGrantedAuthorities(ctx, "scaladude");
|
||||
assertThat(authorities).hasSize(1);
|
||||
assertThat(authorities).isEqualTo(Arrays.asList(this.scalaDevelopers));
|
||||
assertThat(authorities).isEqualTo(Collections.singletonList(this.scalaDevelopers));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGroovyDudeJDevelopersAuthorities() {
|
||||
DirContextAdapter ctx = new DirContextAdapter("uid=groovydude,ou=people,dc=springframework,dc=org");
|
||||
DirContextAdapter ctx = new DirContextAdapter("uid=groovydude,ou=people");
|
||||
Collection<GrantedAuthority> authorities = this.populator.getGrantedAuthorities(ctx, "groovydude");
|
||||
assertThat(authorities).hasSize(4);
|
||||
assertThat(authorities).isEqualTo(Arrays.asList(this.javaDevelopers, this.circularJavaDevelopers,
|
||||
|
@ -116,8 +122,8 @@ public class NestedLdapAuthoritiesPopulatorTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testClosureDudeJDevelopersWithMembershipAsAttributeValues() {
|
||||
this.populator.setAttributeNames(new HashSet(Arrays.asList("member")));
|
||||
public void testClosureDudeJDevelopersWithMembershipAsAttributeValues() throws NamingException {
|
||||
this.populator.setAttributeNames(new HashSet<>(List.of("member")));
|
||||
|
||||
DirContextAdapter ctx = new DirContextAdapter("uid=closuredude,ou=people,dc=springframework,dc=org");
|
||||
Collection<GrantedAuthority> authorities = this.populator.getGrantedAuthorities(ctx, "closuredude");
|
||||
|
@ -128,19 +134,18 @@ public class NestedLdapAuthoritiesPopulatorTests {
|
|||
LdapAuthority[] ldapAuthorities = authorities.toArray(new LdapAuthority[0]);
|
||||
assertThat(ldapAuthorities).hasSize(5);
|
||||
// groovy-developers group
|
||||
assertThat(ldapAuthorities[0].getAttributes()).containsKey("member");
|
||||
assertThat(ldapAuthorities[0].getAttributes().get("member")).isNotNull();
|
||||
assertThat(ldapAuthorities[0].getAttributes().get("member")).hasSize(3);
|
||||
assertThat(ldapAuthorities[0].getFirstAttributeValue("member"))
|
||||
.isEqualTo("cn=groovy-developers,ou=jdeveloper,dc=springframework,dc=org");
|
||||
assertThat(ldapAuthorities[0].getAttributes().get("member")).isNotNull();
|
||||
assertThat(ldapAuthorities[0].getAttributes().get("member").size()).isEqualTo(3);
|
||||
assertThat(ldapAuthorities[0].getFirstAttributeValue("member")).isEqualTo("cn=groovy-developers,ou=jdeveloper");
|
||||
|
||||
// java group
|
||||
assertThat(ldapAuthorities[1].getAttributes()).containsKey("member");
|
||||
assertThat(ldapAuthorities[1].getAttributes().get("member")).isNotNull();
|
||||
assertThat(ldapAuthorities[1].getAttributes().get("member")).hasSize(3);
|
||||
assertThat(ldapAuthorities[1].getAttributes().get("member")).isNotNull();
|
||||
assertThat(ldapAuthorities[1].getAttributes().get("member").size()).isEqualTo(3);
|
||||
assertThat(this.groovyDevelopers.getDn()).isEqualTo(ldapAuthorities[1].getFirstAttributeValue("member"));
|
||||
assertThat(ldapAuthorities[2].getAttributes().get("member"))
|
||||
.contains("uid=closuredude,ou=people,dc=springframework,dc=org");
|
||||
assertThat(ldapAuthorities[2].getAttributes().get("member").get().toString())
|
||||
.contains("uid=closuredude,ou=people");
|
||||
|
||||
// test non existent attribute
|
||||
assertThat(ldapAuthorities[2].getFirstAttributeValue("test")).isNull();
|
||||
|
|
|
@ -16,29 +16,26 @@
|
|||
|
||||
package org.springframework.security.ldap.userdetails;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
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.LdapDataEntry;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.ldap.core.ContextSource;
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
import org.springframework.ldap.core.LdapTemplate;
|
||||
import org.springframework.ldap.core.LdapClient;
|
||||
import org.springframework.ldap.query.LdapQuery;
|
||||
import org.springframework.ldap.query.LdapQueryBuilder;
|
||||
import org.springframework.ldap.query.SearchScope;
|
||||
import org.springframework.ldap.support.LdapUtils;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.directory.Attribute;
|
||||
import javax.naming.directory.Attributes;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* The default strategy for obtaining user role information from the directory.
|
||||
|
@ -112,15 +109,15 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
|
|||
private GrantedAuthority defaultRole;
|
||||
|
||||
/**
|
||||
* Template that will be used for searching
|
||||
* LDAP client that will be used for searching
|
||||
*/
|
||||
private final SpringSecurityLdapTemplate ldapTemplate;
|
||||
private final LdapClient ldapClient;
|
||||
|
||||
/**
|
||||
* Controls used to determine whether group searches should be performed over the full
|
||||
* sub-tree from the base DN. Modified by searchSubTree property
|
||||
*/
|
||||
private final SearchControls searchControls = new SearchControls();
|
||||
private SearchScope searchScope = SearchScope.ONELEVEL;
|
||||
|
||||
/**
|
||||
* The ID of the attribute which contains the role name for a group
|
||||
|
@ -150,7 +147,7 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
|
|||
/**
|
||||
* The mapping function to be used to populate authorities.
|
||||
*/
|
||||
private Function<Map<String, List<String>>, GrantedAuthority> authorityMapper;
|
||||
private Function<LdapDataEntry, Set<? extends GrantedAuthority>> authorityMapper;
|
||||
|
||||
/**
|
||||
* Constructor for group search scenarios. <tt>userRoleAttributes</tt> may still be
|
||||
|
@ -161,8 +158,7 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
|
|||
*/
|
||||
public DefaultLdapAuthoritiesPopulator(ContextSource contextSource, String groupSearchBase) {
|
||||
Assert.notNull(contextSource, "contextSource must not be null");
|
||||
this.ldapTemplate = new SpringSecurityLdapTemplate(contextSource);
|
||||
getLdapTemplate().setSearchControls(getSearchControls());
|
||||
this.ldapClient = LdapClient.create(contextSource);
|
||||
this.groupSearchBase = groupSearchBase;
|
||||
if (groupSearchBase == null) {
|
||||
logger.info("Will not perform group search since groupSearchBase is null.");
|
||||
|
@ -170,19 +166,26 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
|
|||
else if (groupSearchBase.isEmpty()) {
|
||||
logger.info("Will perform group search from the context source base since groupSearchBase is empty.");
|
||||
}
|
||||
this.authorityMapper = (record) -> {
|
||||
List<String> roles = record.get(this.groupRoleAttribute);
|
||||
if (CollectionUtils.isEmpty(roles)) {
|
||||
return null;
|
||||
}
|
||||
String role = roles.get(0);
|
||||
if (role == null) {
|
||||
return null;
|
||||
}
|
||||
this.authorityMapper = (entry) -> {
|
||||
try {
|
||||
Attributes record = entry.getAttributes();
|
||||
Set<LdapAuthority> authorities = new HashSet<>();
|
||||
Attribute userRoles = record.get(this.groupRoleAttribute);
|
||||
if (userRoles != null)
|
||||
userRoles.getAll().asIterator().forEachRemaining(role -> {
|
||||
if (role instanceof String s) {
|
||||
if (this.convertToUpperCase) {
|
||||
role = role.toUpperCase(Locale.ROOT);
|
||||
s = s.toUpperCase(Locale.ROOT);
|
||||
}
|
||||
authorities.add(new LdapAuthority(this.rolePrefix + s, entry.getDn()));
|
||||
}
|
||||
});
|
||||
|
||||
return authorities;
|
||||
}
|
||||
catch (NamingException e) {
|
||||
throw LdapUtils.convertLdapException(e);
|
||||
}
|
||||
return new SimpleGrantedAuthority(this.rolePrefix + role);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -229,21 +232,19 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
|
|||
Set<GrantedAuthority> authorities = new HashSet<>();
|
||||
logger.trace(LogMessage.of(() -> "Searching for roles for user " + username + " with DN " + userDn
|
||||
+ " and filter " + this.groupSearchFilter + " in search base " + getGroupSearchBase()));
|
||||
Set<Map<String, List<String>>> userRoles = getLdapTemplate().searchForMultipleAttributeValues(
|
||||
getGroupSearchBase(), this.groupSearchFilter, new String[] { userDn, username },
|
||||
new String[] { this.groupRoleAttribute });
|
||||
logger.debug(LogMessage.of(() -> "Found roles from search " + userRoles));
|
||||
for (Map<String, List<String>> role : userRoles) {
|
||||
GrantedAuthority authority = this.authorityMapper.apply(role);
|
||||
if (authority != null) {
|
||||
authorities.add(authority);
|
||||
}
|
||||
}
|
||||
return authorities;
|
||||
}
|
||||
|
||||
protected ContextSource getContextSource() {
|
||||
return getLdapTemplate().getContextSource();
|
||||
LdapQuery query = LdapQueryBuilder.query()
|
||||
.searchScope(searchScope)
|
||||
.base(getGroupSearchBase())
|
||||
.attributes(getGroupRoleAttribute())
|
||||
.filter(getGroupSearchFilter(), userDn, username);
|
||||
|
||||
getLdapClient().search().query(query).toEntryStream().forEach(entry -> {
|
||||
logger.debug(LogMessage.of(() -> "Found roles from search " + entry));
|
||||
|
||||
authorities.addAll(this.authorityMapper.apply(entry));
|
||||
});
|
||||
return authorities;
|
||||
}
|
||||
|
||||
protected String getGroupSearchBase() {
|
||||
|
@ -292,18 +293,7 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
|
|||
* <tt>groupSearchBase</tt>.
|
||||
*/
|
||||
public void setSearchSubtree(boolean searchSubtree) {
|
||||
int searchScope = searchSubtree ? SearchControls.SUBTREE_SCOPE : SearchControls.ONELEVEL_SCOPE;
|
||||
this.searchControls.setSearchScope(searchScope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the corresponding property on the underlying template, avoiding specific
|
||||
* issues with Active Directory.
|
||||
*
|
||||
* @see LdapTemplate#setIgnoreNameNotFoundException(boolean)
|
||||
*/
|
||||
public void setIgnorePartialResultException(boolean ignore) {
|
||||
getLdapTemplate().setIgnorePartialResultException(ignore);
|
||||
this.searchScope = searchSubtree ? SearchScope.SUBTREE : SearchScope.ONELEVEL;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -311,7 +301,7 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
|
|||
* {@link GrantedAuthority} given the context record.
|
||||
* @param authorityMapper the mapping function
|
||||
*/
|
||||
public void setAuthorityMapper(Function<Map<String, List<String>>, GrantedAuthority> authorityMapper) {
|
||||
public void setAuthorityMapper(Function<LdapDataEntry, Set<? extends GrantedAuthority>> authorityMapper) {
|
||||
Assert.notNull(authorityMapper, "authorityMapper must not be null");
|
||||
this.authorityMapper = authorityMapper;
|
||||
}
|
||||
|
@ -322,8 +312,8 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
|
|||
* @return the LDAP template
|
||||
* @see org.springframework.security.ldap.SpringSecurityLdapTemplate
|
||||
*/
|
||||
protected SpringSecurityLdapTemplate getLdapTemplate() {
|
||||
return this.ldapTemplate;
|
||||
protected LdapClient getLdapClient() {
|
||||
return this.ldapClient;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -366,13 +356,4 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
|
|||
return this.convertToUpperCase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the search controls Method available so that classes extending this can
|
||||
* override the search controls used
|
||||
* @return the search controls
|
||||
*/
|
||||
private SearchControls getSearchControls() {
|
||||
return this.searchControls;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,14 +16,19 @@
|
|||
|
||||
package org.springframework.security.ldap.userdetails;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.collections.IteratorUtils;
|
||||
import org.springframework.ldap.support.LdapUtils;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.naming.Name;
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.directory.Attributes;
|
||||
import java.io.Serial;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An authority that contains at least a DN and a role name for an LDAP entry but can also
|
||||
* contain other desired attributes to be fetched during an LDAP authority search.
|
||||
|
@ -35,18 +40,18 @@ public class LdapAuthority implements GrantedAuthority {
|
|||
@Serial
|
||||
private static final long serialVersionUID = 343193700821611354L;
|
||||
|
||||
private final String dn;
|
||||
private final Name dn;
|
||||
|
||||
private final String role;
|
||||
|
||||
private final Map<String, List<String>> attributes;
|
||||
private final Attributes attributes;
|
||||
|
||||
/**
|
||||
* Constructs an LdapAuthority that has a role and a DN but no other attributes
|
||||
* @param role the principal's role
|
||||
* @param dn the distinguished name
|
||||
*/
|
||||
public LdapAuthority(String role, String dn) {
|
||||
public LdapAuthority(String role, Name dn) {
|
||||
this(role, dn, null);
|
||||
}
|
||||
|
||||
|
@ -56,7 +61,7 @@ public class LdapAuthority implements GrantedAuthority {
|
|||
* @param dn the distinguished name
|
||||
* @param attributes additional LDAP attributes
|
||||
*/
|
||||
public LdapAuthority(String role, String dn, Map<String, List<String>> attributes) {
|
||||
public LdapAuthority(String role, Name dn, Attributes attributes) {
|
||||
Assert.notNull(role, "role can not be null");
|
||||
Assert.notNull(dn, "dn can not be null");
|
||||
this.role = role;
|
||||
|
@ -68,7 +73,7 @@ public class LdapAuthority implements GrantedAuthority {
|
|||
* Returns the LDAP attributes
|
||||
* @return the LDAP attributes, map can be null
|
||||
*/
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
public Attributes getAttributes() {
|
||||
return this.attributes;
|
||||
}
|
||||
|
||||
|
@ -76,7 +81,7 @@ public class LdapAuthority implements GrantedAuthority {
|
|||
* Returns the DN for this LDAP authority
|
||||
* @return the distinguished name
|
||||
*/
|
||||
public String getDn() {
|
||||
public Name getDn() {
|
||||
return this.dn;
|
||||
}
|
||||
|
||||
|
@ -85,10 +90,16 @@ public class LdapAuthority implements GrantedAuthority {
|
|||
* @param name the attribute name
|
||||
* @return a String array, never null but may be zero length
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<String> getAttributeValues(String name) {
|
||||
List<String> result = null;
|
||||
if (this.attributes != null) {
|
||||
result = this.attributes.get(name);
|
||||
try {
|
||||
result = (List<String>) this.attributes.get(name).get();
|
||||
}
|
||||
catch (NamingException e) {
|
||||
throw LdapUtils.convertLdapException(e);
|
||||
}
|
||||
}
|
||||
return (result != null) ? result : Collections.emptyList();
|
||||
}
|
||||
|
|
|
@ -16,23 +16,28 @@
|
|||
|
||||
package org.springframework.security.ldap.userdetails;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.ldap.core.ContextSource;
|
||||
import org.springframework.ldap.query.LdapQuery;
|
||||
import org.springframework.ldap.query.LdapQueryBuilder;
|
||||
import org.springframework.ldap.support.LdapUtils;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.naming.InvalidNameException;
|
||||
import javax.naming.Name;
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.directory.Attribute;
|
||||
import javax.naming.directory.Attributes;
|
||||
import javax.naming.ldap.LdapName;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* A LDAP authority populator that can recursively search static nested groups.
|
||||
* An LDAP authority populator that can recursively search static nested groups.
|
||||
* <p>
|
||||
* An example of nested groups can be
|
||||
*
|
||||
|
@ -135,6 +140,8 @@ public class NestedLdapAuthoritiesPopulator extends DefaultLdapAuthoritiesPopula
|
|||
*/
|
||||
private int maxSearchDepth = 10;
|
||||
|
||||
private ContextSource contextSource;
|
||||
|
||||
/**
|
||||
* Constructor for group search scenarios. <tt>userRoleAttributes</tt> may still be
|
||||
* set as a property.
|
||||
|
@ -144,6 +151,7 @@ public class NestedLdapAuthoritiesPopulator extends DefaultLdapAuthoritiesPopula
|
|||
*/
|
||||
public NestedLdapAuthoritiesPopulator(ContextSource contextSource, String groupSearchBase) {
|
||||
super(contextSource, groupSearchBase);
|
||||
this.contextSource = contextSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -152,7 +160,12 @@ public class NestedLdapAuthoritiesPopulator extends DefaultLdapAuthoritiesPopula
|
|||
return new HashSet<>();
|
||||
}
|
||||
Set<GrantedAuthority> authorities = new HashSet<>();
|
||||
performNestedSearch(userDn, username, authorities, getMaxSearchDepth());
|
||||
try {
|
||||
performNestedSearch(new LdapName(userDn), username, authorities, getMaxSearchDepth());
|
||||
}
|
||||
catch (InvalidNameException e) {
|
||||
throw LdapUtils.convertLdapException(e);
|
||||
}
|
||||
return authorities;
|
||||
}
|
||||
|
||||
|
@ -162,9 +175,9 @@ public class NestedLdapAuthoritiesPopulator extends DefaultLdapAuthoritiesPopula
|
|||
* searches
|
||||
* @param username - the username of the user
|
||||
* @param authorities - the authorities set that will be populated, must not be null
|
||||
* @param depth - the depth remaining, when 0 recursion will end
|
||||
* @param depth - the depth remaining, when 0 recursions will end
|
||||
*/
|
||||
private void performNestedSearch(String userDn, String username, Set<GrantedAuthority> authorities, int depth) {
|
||||
private void performNestedSearch(Name userDn, String username, Set<GrantedAuthority> authorities, int depth) {
|
||||
if (depth == 0) {
|
||||
// back out of recursion
|
||||
logger.debug(LogMessage.of(() -> "Aborted search since max depth reached," + " for roles for user '"
|
||||
|
@ -180,18 +193,24 @@ public class NestedLdapAuthoritiesPopulator extends DefaultLdapAuthoritiesPopula
|
|||
if (StringUtils.hasText(getGroupRoleAttribute())) {
|
||||
getAttributeNames().add(getGroupRoleAttribute());
|
||||
}
|
||||
Set<Map<String, List<String>>> userRoles = getLdapTemplate().searchForMultipleAttributeValues(
|
||||
getGroupSearchBase(), getGroupSearchFilter(), new String[] { userDn, username },
|
||||
getAttributeNames().toArray(new String[0]));
|
||||
logger.debug(LogMessage.format("Found roles from search %s", userRoles));
|
||||
for (Map<String, List<String>> record : userRoles) {
|
||||
boolean circular = false;
|
||||
String dn = record.get(SpringSecurityLdapTemplate.DN_KEY).get(0);
|
||||
List<String> roleValues = record.get(getGroupRoleAttribute());
|
||||
LdapQuery query = LdapQueryBuilder.query()
|
||||
.base(getGroupSearchBase())
|
||||
// .attributes(getGroupRoleAttribute()) // TODO the original implementation
|
||||
// does not use it??
|
||||
.filter(getGroupSearchFilter(), userDn, username);
|
||||
|
||||
AtomicBoolean circular = new AtomicBoolean(false);
|
||||
Set<String> roles = new HashSet<>();
|
||||
if (roleValues != null) {
|
||||
roles.addAll(roleValues);
|
||||
}
|
||||
getLdapClient().search().query(query).toEntryStream().forEach(entry -> {
|
||||
logger.debug(LogMessage.of(() -> "Found roles from search " + entry));
|
||||
Attributes attributes = entry.getAttributes();
|
||||
|
||||
Name dn = entry.getDn();
|
||||
String[] userRoles = entry.getStringAttributes(getGroupRoleAttribute());
|
||||
|
||||
if (userRoles != null)
|
||||
roles.addAll(Arrays.asList(userRoles));
|
||||
|
||||
for (String role : roles) {
|
||||
if (isConvertToUpperCase()) {
|
||||
role = role.toUpperCase(Locale.ROOT);
|
||||
|
@ -199,13 +218,13 @@ public class NestedLdapAuthoritiesPopulator extends DefaultLdapAuthoritiesPopula
|
|||
role = getRolePrefix() + role;
|
||||
// if the group already exist, we will not search for it's parents again.
|
||||
// this prevents a forever loop for a misconfigured ldap directory
|
||||
circular = circular | (!authorities.add(new LdapAuthority(role, dn, record)));
|
||||
circular.set(circular.get() | (!authorities.add(new LdapAuthority(role, dn, attributes))));
|
||||
}
|
||||
String roleName = (!roles.isEmpty()) ? roles.iterator().next() : dn;
|
||||
if (!circular) {
|
||||
String roleName = (!roles.isEmpty()) ? roles.iterator().next() : dn.toString();
|
||||
if (!circular.get()) {
|
||||
performNestedSearch(dn, roleName, authorities, (depth - 1));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -26,6 +26,11 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
|
||||
|
||||
import javax.naming.InvalidNameException;
|
||||
import javax.naming.directory.Attributes;
|
||||
import javax.naming.directory.BasicAttributes;
|
||||
import javax.naming.ldap.LdapName;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
|
@ -33,14 +38,23 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
*/
|
||||
public class LdapAuthorityTests {
|
||||
|
||||
public static final String DN = "cn=filip,ou=Users,dc=test,dc=com";
|
||||
public static final LdapName DN;
|
||||
|
||||
static {
|
||||
try {
|
||||
DN = new LdapName("cn=filip,ou=Users,dc=test,dc=com");
|
||||
}
|
||||
catch (InvalidNameException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
LdapAuthority authority;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
Map<String, List<String>> attributes = new HashMap<>();
|
||||
attributes.put(SpringSecurityLdapTemplate.DN_KEY, Arrays.asList(DN));
|
||||
public void setUp() throws InvalidNameException {
|
||||
Attributes attributes = new BasicAttributes();
|
||||
attributes.put(SpringSecurityLdapTemplate.DN_KEY, List.of(DN));
|
||||
attributes.put("mail", Arrays.asList("filip@ldap.test.org", "filip@ldap.test2.org"));
|
||||
this.authority = new LdapAuthority("testRole", DN, attributes);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue