diff --git a/config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/LdapAuthenticationProviderBuilderSecurityBuilderTests.java b/config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/LdapAuthenticationProviderBuilderSecurityBuilderTests.java index 34a4be0934d..9ff0675bc20 100644 --- a/config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/LdapAuthenticationProviderBuilderSecurityBuilderTests.java +++ b/config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/LdapAuthenticationProviderBuilderSecurityBuilderTests.java @@ -42,7 +42,7 @@ import java.io.IOException; import java.net.ServerSocket; import java.util.List; - +import javax.naming.directory.SearchControls; import static java.util.Collections.singleton; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; @@ -67,6 +67,8 @@ public void defaultConfiguration() { assertThat(authoritiesPopulator).hasFieldOrPropertyWithValue("groupRoleAttribute", "cn"); assertThat(authoritiesPopulator).hasFieldOrPropertyWithValue("groupSearchBase", ""); assertThat(authoritiesPopulator).hasFieldOrPropertyWithValue("groupSearchFilter", "(uniqueMember={0})"); + assertThat(authoritiesPopulator).extracting("searchControls").hasFieldOrPropertyWithValue("searchScope", + SearchControls.ONELEVEL_SCOPE); assertThat(ReflectionTestUtils.getField(getAuthoritiesMapper(provider), "prefix")).isEqualTo("ROLE_"); } @@ -124,6 +126,29 @@ protected void configure(AuthenticationManagerBuilder auth) throws Exception { // @formatter:on } + @Test + public void groupSubtreeSearchCustom() { + this.spring.register(GroupSubtreeSearchConfig.class).autowire(); + LdapAuthenticationProvider provider = ldapProvider(); + + assertThat(ReflectionTestUtils.getField(getAuthoritiesPopulator(provider), "searchControls")) + .extracting("searchScope").isEqualTo(SearchControls.SUBTREE_SCOPE); + } + + @EnableWebSecurity + static class GroupSubtreeSearchConfig extends BaseLdapProviderConfig { + // @formatter:off + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth + .ldapAuthentication() + .contextSource(contextSource()) + .userDnPatterns("uid={0},ou=people") + .groupSearchFilter("ou=groupName") + .groupSearchSubtree(true); + } + // @formatter:on + } + @Test public void rolePrefixCustom() { this.spring.register(RolePrefixConfig.class).autowire(); diff --git a/config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/LdapAuthenticationProviderConfigurerTests.java b/config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/LdapAuthenticationProviderConfigurerTests.java index a6a39dcffeb..3cf29d63386 100644 --- a/config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/LdapAuthenticationProviderConfigurerTests.java +++ b/config/src/integration-test/java/org/springframework/security/config/annotation/authentication/ldap/LdapAuthenticationProviderConfigurerTests.java @@ -21,9 +21,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.authentication.ldap.LdapAuthenticationProviderBuilderSecurityBuilderTests.BaseLdapProviderConfig; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.test.SpringTestRule; +import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.test.web.servlet.MockMvc; @@ -70,6 +72,15 @@ public void authenticationManagerWhenPortZeroThenAuthenticates() throws Exceptio .andExpect(authenticated().withUsername("bob")); } + @Test + public void authenticationManagerWhenSearchSubtreeThenNestedGroupFound() throws Exception { + this.spring.register(GroupSubtreeSearchConfig.class).autowire(); + + this.mockMvc.perform(formLogin().user("ben").password("benspassword")) + .andExpect(authenticated().withUsername("ben").withAuthorities( + AuthorityUtils.createAuthorityList("ROLE_SUBMANAGERS", "ROLE_MANAGERS", "ROLE_DEVELOPERS"))); + } + @EnableWebSecurity static class MultiLdapAuthenticationProvidersConfig extends WebSecurityConfigurerAdapter { // @formatter:off @@ -121,4 +132,18 @@ protected void configure(AuthenticationManagerBuilder auth) throws Exception { .port(0); } } + + @EnableWebSecurity + static class GroupSubtreeSearchConfig extends BaseLdapProviderConfig { + // @formatter:off + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth + .ldapAuthentication() + .groupSearchBase("ou=groups") + .groupSearchFilter("(member={0})") + .groupSearchSubtree(true) + .userDnPatterns("uid={0},ou=people"); + } + // @formatter:on + } } diff --git a/config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/ldap/LdapAuthenticationProviderConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/ldap/LdapAuthenticationProviderConfigurer.java index a5a45106f93..51338a9babf 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/ldap/LdapAuthenticationProviderConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/authentication/configurers/ldap/LdapAuthenticationProviderConfigurer.java @@ -61,6 +61,7 @@ public class LdapAuthenticationProviderConfigurer { private String groupRoleAttribute = "cn"; private String groupSearchBase = ""; + private boolean groupSearchSubtree = false; private String groupSearchFilter = "(uniqueMember={0})"; private String rolePrefix = "ROLE_"; private String userSearchBase = ""; // only for search @@ -130,6 +131,7 @@ private LdapAuthoritiesPopulator getLdapAuthoritiesPopulator() { contextSource, groupSearchBase); defaultAuthoritiesPopulator.setGroupRoleAttribute(groupRoleAttribute); defaultAuthoritiesPopulator.setGroupSearchFilter(groupSearchFilter); + defaultAuthoritiesPopulator.setSearchSubtree(groupSearchSubtree); defaultAuthoritiesPopulator.setRolePrefix(this.rolePrefix); this.ldapAuthoritiesPopulator = defaultAuthoritiesPopulator; @@ -320,6 +322,19 @@ public LdapAuthenticationProviderConfigurer groupSearchBase(String groupSearc return this; } + /** + * If set to true, a subtree scope search will be performed for group membership. If false a + * single-level search is used. + * + * @param searchSubtree set to true to enable searching of the entire tree below the + * groupSearchBase. + * @return the {@link LdapAuthenticationProviderConfigurer} for further customizations + */ + public LdapAuthenticationProviderConfigurer groupSearchSubtree(boolean groupSearchSubtree) { + this.groupSearchSubtree = groupSearchSubtree; + return this; + } + /** * The LDAP filter to search for groups. Defaults to "(uniqueMember={0})". The * substituted parameter is the DN of the user.