Skip to content

Commit eec6729

Browse files
dkodippilySteve Riesenberg
authored andcommitted
Fix DefaultLdapAuthoritiesPopulator NPE
Closes gh-12090
1 parent 885f1d3 commit eec6729

File tree

3 files changed

+159
-2
lines changed

3 files changed

+159
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.ldap.userdetails;
18+
19+
import java.util.Set;
20+
21+
import org.junit.jupiter.api.BeforeEach;
22+
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.api.extension.ExtendWith;
24+
25+
import org.springframework.beans.factory.DisposableBean;
26+
import org.springframework.beans.factory.annotation.Autowired;
27+
import org.springframework.context.annotation.Bean;
28+
import org.springframework.context.annotation.Configuration;
29+
import org.springframework.ldap.core.ContextSource;
30+
import org.springframework.ldap.core.DirContextAdapter;
31+
import org.springframework.ldap.core.DistinguishedName;
32+
import org.springframework.security.core.authority.AuthorityUtils;
33+
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
34+
import org.springframework.security.ldap.server.ApacheDSContainer;
35+
import org.springframework.test.context.ContextConfiguration;
36+
import org.springframework.test.context.junit.jupiter.SpringExtension;
37+
38+
import static org.assertj.core.api.Assertions.assertThat;
39+
40+
/**
41+
* @author Dayan Kodippily
42+
*/
43+
@ExtendWith(SpringExtension.class)
44+
@ContextConfiguration(
45+
classes = DefaultLdapAuthoritiesPopulatorGetGrantedAuthoritiesTests.ApacheDsContainerWithUndefinedGroupRoleAttributeConfig.class)
46+
public class DefaultLdapAuthoritiesPopulatorGetGrantedAuthoritiesTests {
47+
48+
@Autowired
49+
private DefaultSpringSecurityContextSource contextSource;
50+
51+
private DefaultLdapAuthoritiesPopulator populator;
52+
53+
@BeforeEach
54+
public void setUp() {
55+
this.populator = new DefaultLdapAuthoritiesPopulator(this.contextSource, "ou=groups");
56+
this.populator.setIgnorePartialResultException(false);
57+
}
58+
59+
@Test
60+
public void groupSearchDoesNotAllowNullRoles() {
61+
this.populator.setRolePrefix("ROLE_");
62+
this.populator.setGroupRoleAttribute("ou");
63+
this.populator.setSearchSubtree(true);
64+
this.populator.setSearchSubtree(false);
65+
this.populator.setConvertToUpperCase(true);
66+
this.populator.setGroupSearchFilter("(member={0})");
67+
68+
DirContextAdapter ctx = new DirContextAdapter(
69+
new DistinguishedName("uid=dayan,ou=people,dc=springframework,dc=org"));
70+
71+
Set<String> authorities = AuthorityUtils.authorityListToSet(this.populator.getGrantedAuthorities(ctx, "dayan"));
72+
73+
assertThat(authorities).as("Should have 1 role").hasSize(2);
74+
75+
assertThat(authorities.contains("ROLE_DEVELOPER")).isTrue();
76+
assertThat(authorities.contains("ROLE_")).isTrue();
77+
}
78+
79+
@Configuration
80+
static class ApacheDsContainerWithUndefinedGroupRoleAttributeConfig implements DisposableBean {
81+
82+
private ApacheDSContainer container;
83+
84+
@Bean
85+
ApacheDSContainer ldapContainer() throws Exception {
86+
this.container = new ApacheDSContainer("dc=springframework,dc=org",
87+
"classpath:test-server-with-undefined-group-role-attributes.ldif");
88+
this.container.setPort(0);
89+
return this.container;
90+
}
91+
92+
@Bean
93+
ContextSource contextSource(ApacheDSContainer ldapContainer) {
94+
return new DefaultSpringSecurityContextSource(
95+
"ldap://127.0.0.1:" + ldapContainer.getLocalPort() + "/dc=springframework,dc=org");
96+
}
97+
98+
@Override
99+
public void destroy() {
100+
this.container.stop();
101+
}
102+
103+
}
104+
105+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
dn: ou=groups,dc=springframework,dc=org
2+
objectclass: top
3+
objectclass: organizationalUnit
4+
ou: groups
5+
6+
dn: ou=people,dc=springframework,dc=org
7+
objectclass: top
8+
objectclass: organizationalUnit
9+
ou: people
10+
11+
dn: uid=dayan,ou=people,dc=springframework,dc=org
12+
objectclass: top
13+
objectclass: person
14+
objectclass: organizationalPerson
15+
objectclass: inetOrgPerson
16+
cn: Dayan K
17+
sn: Dayan
18+
uid: dayan
19+
userPassword: dayanspassword
20+
21+
22+
23+
dn: cn=managers,ou=groups,dc=springframework,dc=org
24+
objectclass: top
25+
objectclass: groupOfNames
26+
cn: managers
27+
ou:
28+
member: uid=dayan,ou=people,dc=springframework,dc=org
29+
30+
dn: cn=researchers,ou=groups,dc=springframework,dc=org
31+
objectclass: top
32+
objectclass: groupOfNames
33+
cn: researchers
34+
member: uid=dayan,ou=people,dc=springframework,dc=org
35+
36+
dn: cn=developers,ou=groups,dc=springframework,dc=org
37+
objectclass: top
38+
objectclass: groupOfNames
39+
cn: developers
40+
ou: developer
41+
member: uid=dayan,ou=people,dc=springframework,dc=org

ldap/src/main/java/org/springframework/security/ldap/userdetails/DefaultLdapAuthoritiesPopulator.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.springframework.security.core.authority.SimpleGrantedAuthority;
3838
import org.springframework.security.ldap.SpringSecurityLdapTemplate;
3939
import org.springframework.util.Assert;
40+
import org.springframework.util.CollectionUtils;
4041

4142
/**
4243
* The default strategy for obtaining user role information from the directory.
@@ -169,7 +170,14 @@ else if (groupSearchBase.length() == 0) {
169170
logger.info("Will perform group search from the context source base since groupSearchBase is empty.");
170171
}
171172
this.authorityMapper = (record) -> {
172-
String role = record.get(this.groupRoleAttribute).get(0);
173+
List<String> roles = record.get(this.groupRoleAttribute);
174+
if (CollectionUtils.isEmpty(roles)) {
175+
return null;
176+
}
177+
String role = roles.get(0);
178+
if (role == null) {
179+
return null;
180+
}
173181
if (this.convertToUpperCase) {
174182
role = role.toUpperCase();
175183
}
@@ -225,7 +233,10 @@ public Set<GrantedAuthority> getGroupMembershipRoles(String userDn, String usern
225233
new String[] { this.groupRoleAttribute });
226234
logger.debug(LogMessage.of(() -> "Found roles from search " + userRoles));
227235
for (Map<String, List<String>> role : userRoles) {
228-
authorities.add(this.authorityMapper.apply(role));
236+
GrantedAuthority authority = this.authorityMapper.apply(role);
237+
if (authority != null) {
238+
authorities.add(authority);
239+
}
229240
}
230241
return authorities;
231242
}

0 commit comments

Comments
 (0)