SEC-2690: Support static nested groups in LDAP

This refers to groups that have member: <another group DN> as an attribute
- Add in a utility method in the SpringSecurityLdapTemplate to retrieve multiple attributes and their values from an LDAP record
- Make the DefaultLdapAuthoritiesPopulator more extensible
- Add an LdapAuthority object that holds the DN in addition to other group attributes
- Add a NestedLdapAuthoritiesPopulator to search statically nested groups
This commit is contained in:
Filip Hanik
2014-06-19 11:39:56 -07:00
committed by Rob Winch
parent 8a2a1b7a5b
commit 93b863d2e5
10 changed files with 945 additions and 13 deletions

View File

@@ -17,6 +17,7 @@ package org.springframework.security.ldap;
import static org.junit.Assert.*;
import java.util.Map;
import java.util.Set;
import javax.naming.Context;
@@ -99,6 +100,69 @@ public class SpringSecurityLdapTemplateITests extends AbstractLdapIntegrationTes
assertTrue(values.contains("submanager"));
}
@Test
public void testMultiAttributeRetrievalWithNullAttributeNames() {
Set<Map<String, String[]>> values =
template.searchForMultipleAttributeValues(
"ou=people",
"(uid={0})",
new String[] {"bob"},
null);
assertEquals(1, values.size());
Map<String, String[]> record = (Map<String, String[]>)values.toArray()[0];
assertAttributeValue(record,"uid","bob");
assertAttributeValue(record,"objectclass","top","person","organizationalPerson","inetOrgPerson");
assertAttributeValue(record,"cn","Bob Hamilton");
assertAttributeValue(record,"sn","Hamilton");
assertFalse(record.containsKey("userPassword"));
}
@Test
public void testMultiAttributeRetrievalWithZeroLengthAttributeNames() {
Set<Map<String, String[]>> values =
template.searchForMultipleAttributeValues(
"ou=people",
"(uid={0})",
new String[] {"bob"},
new String[0]);
assertEquals(1, values.size());
Map<String, String[]> record = (Map<String, String[]>)values.toArray()[0];
assertAttributeValue(record,"uid","bob");
assertAttributeValue(record,"objectclass","top","person","organizationalPerson","inetOrgPerson");
assertAttributeValue(record,"cn","Bob Hamilton");
assertAttributeValue(record,"sn","Hamilton");
assertFalse(record.containsKey("userPassword"));
}
@Test
public void testMultiAttributeRetrievalWithSpecifiedAttributeNames() {
Set<Map<String, String[]>> values =
template.searchForMultipleAttributeValues(
"ou=people",
"(uid={0})",
new String[] {"bob"},
new String[] {
"uid",
"cn",
"sn"
});
assertEquals(1, values.size());
Map<String, String[]> record = (Map<String, String[]>)values.toArray()[0];
assertAttributeValue(record,"uid","bob");
assertAttributeValue(record,"cn","Bob Hamilton");
assertAttributeValue(record,"sn","Hamilton");
assertFalse(record.containsKey("userPassword"));
assertFalse(record.containsKey("objectclass"));
}
protected void assertAttributeValue(Map<String, String[]> record, String attributeName, String... values) {
assertTrue(record.containsKey(attributeName));
assertEquals(values.length,record.get(attributeName).length);
for (int i=0; i<values.length; i++) {
assertEquals(values[i],record.get(attributeName)[i]);
}
}
@Test
public void testRoleSearchForMissingAttributeFailsGracefully() {
String param = "uid=ben,ou=people,dc=springframework,dc=org";

View File

@@ -60,7 +60,7 @@ public class FilterBasedLdapUserSearchTests extends AbstractLdapIntegrationTests
@Test
public void extraFilterPartToExcludeBob() throws Exception {
FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=people",
"(&(cn=*)(!(|(uid={0})(uid=rod)(uid=jerry)(uid=slashguy))))", getContextSource());
"(&(cn=*)(!(|(uid={0})(uid=rod)(uid=jerry)(uid=slashguy)(uid=javadude)(uid=groovydude)(uid=closuredude)(uid=scaladude))))", getContextSource());
// Search for bob, get back ben...
DirContextOperations ben = locator.searchForUser("bob");

View File

@@ -0,0 +1,134 @@
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.ldap.userdetails;
import org.junit.Before;
import org.junit.Test;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.ldap.AbstractLdapIntegrationTests;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
* @author Filip Hanik
*/
public class NestedLdapAuthoritiesPopulatorTests extends AbstractLdapIntegrationTests {
private NestedLdapAuthoritiesPopulator populator;
private LdapAuthority javaDevelopers;
private LdapAuthority groovyDevelopers;
private LdapAuthority scalaDevelopers;
private LdapAuthority closureDevelopers;
private LdapAuthority jDevelopers;
private LdapAuthority circularJavaDevelopers;
//~ Methods ========================================================================================================
@Before
public void setUp() throws Exception {
populator = new NestedLdapAuthoritiesPopulator(getContextSource(), "ou=jdeveloper");
populator.setGroupSearchFilter("(member={0})");
populator.setIgnorePartialResultException(false);
populator.setRolePrefix("");
populator.setSearchSubtree(true);
populator.setConvertToUpperCase(false);
jDevelopers = new LdapAuthority("j-developers","cn=j-developers,ou=jdeveloper,dc=springframework,dc=org");
javaDevelopers = new LdapAuthority("java-developers","cn=java-developers,ou=jdeveloper,dc=springframework,dc=org");
groovyDevelopers = new LdapAuthority("groovy-developers","cn=groovy-developers,ou=jdeveloper,dc=springframework,dc=org");
scalaDevelopers = new LdapAuthority("scala-developers","cn=scala-developers,ou=jdeveloper,dc=springframework,dc=org");
closureDevelopers = new LdapAuthority("closure-developers","cn=closure-developers,ou=jdeveloper,dc=springframework,dc=org");
circularJavaDevelopers = new LdapAuthority("circular-java-developers","cn=circular-java-developers,ou=jdeveloper,dc=springframework,dc=org");
}
@Test
public void testScalaDudeJDevelopersAuthorities() {
DirContextAdapter ctx = new DirContextAdapter("uid=scaladude,ou=people,dc=springframework,dc=org");
Collection<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx,"scaladude");
assertEquals(5, authorities.size());
assertEquals(Arrays.asList(javaDevelopers, scalaDevelopers, circularJavaDevelopers, jDevelopers, groovyDevelopers), authorities);
}
@Test
public void testJavaDudeJDevelopersAuthorities() {
DirContextAdapter ctx = new DirContextAdapter("uid=javadude,ou=people,dc=springframework,dc=org");
Collection<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx,"javadude");
assertEquals(3, authorities.size());
assertEquals(Arrays.asList(javaDevelopers, circularJavaDevelopers, jDevelopers), authorities);
}
@Test
public void testScalaDudeJDevelopersAuthoritiesWithSearchLimit() {
populator.setMaxSearchDepth(1);
DirContextAdapter ctx = new DirContextAdapter("uid=scaladude,ou=people,dc=springframework,dc=org");
Collection<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx,"scaladude");
assertEquals(1, authorities.size());
assertEquals(Arrays.asList(scalaDevelopers), authorities);
}
@Test
public void testGroovyDudeJDevelopersAuthorities() {
DirContextAdapter ctx = new DirContextAdapter("uid=groovydude,ou=people,dc=springframework,dc=org");
Collection<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx,"groovydude");
assertEquals(4, authorities.size());
assertEquals(Arrays.asList(javaDevelopers,circularJavaDevelopers,jDevelopers,groovyDevelopers), authorities);
}
@Test
public void testClosureDudeJDevelopersWithMembershipAsAttributeValues() {
populator.setAttributeNames(new HashSet(Arrays.asList("member")));
DirContextAdapter ctx = new DirContextAdapter("uid=closuredude,ou=people,dc=springframework,dc=org");
Collection<GrantedAuthority> authorities = populator.getGrantedAuthorities(ctx,"closuredude");
assertEquals(5, authorities.size());
assertEquals(Arrays.asList(closureDevelopers,javaDevelopers,circularJavaDevelopers,jDevelopers,groovyDevelopers), authorities);
LdapAuthority[] ldapAuthorities = authorities.toArray(new LdapAuthority[0]);
assertEquals(5, ldapAuthorities.length);
//closure group
assertTrue(ldapAuthorities[0].getAttributes().containsKey("member"));
assertNotNull(ldapAuthorities[0].getAttributes().get("member"));
assertEquals(1, ldapAuthorities[0].getAttributes().get("member").length);
assertEquals("uid=closuredude,ou=people,dc=springframework,dc=org",ldapAuthorities[0].getFirstAttributeValue("member"));
//java group
assertTrue(ldapAuthorities[1].getAttributes().containsKey("member"));
assertNotNull(ldapAuthorities[1].getAttributes().get("member"));
assertEquals(3,ldapAuthorities[1].getAttributes().get("member").length);
assertEquals(groovyDevelopers.getDn(),ldapAuthorities[1].getFirstAttributeValue("member"));
assertEquals(
new String[] {
groovyDevelopers.getDn(),
scalaDevelopers.getDn(),
"uid=javadude,ou=people,dc=springframework,dc=org"
},
ldapAuthorities[1].getAttributes().get("member")
);
//test non existent attribute
assertNull(ldapAuthorities[2].getFirstAttributeValue("test"));
assertNotNull(ldapAuthorities[2].getAttributeValues("test"));
assertEquals(0, ldapAuthorities[2].getAttributeValues("test").length);
//test role name
assertEquals(jDevelopers.getAuthority(), ldapAuthorities[3].getAuthority());
}
}

View File

@@ -122,3 +122,109 @@ objectclass: groupOfNames
cn: submanagers
ou: submanager
member: uid=ben,ou=people,dc=springframework,dc=org
#Nested groups data
###################
dn: ou=jdeveloper,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: jdeveloper
# javadude is part of (in a nested search)
# circular-java-developers, java-developers, j-developers
dn: uid=javadude,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Java Dude
sn: Dude
uid: javadude
userPassword: javadudespassword
# groovydude is part of (in a nested search)
# groovy-developers, java-developers, circular-java-developers, j-developers
dn: uid=groovydude,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Groovy Dude
sn: Dude
uid: groovydude
userPassword: groovydudespassword
# closuredude is part of (in a nested search)
# closure-developers, groovy-developers, java-developers, circular-java-developers, j-developers
dn: uid=closuredude,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Closure Dude
sn: Dude
uid: closuredude
userPassword: closuredudespassword
# scaladude is part of (in a nested search)
# scala-developers, groovy-developers, java-developers, circular-java-developers, j-developers
dn: uid=scaladude,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Scala Dude
sn: Dude
uid: scaladude
userPassword: scaladudespassword
dn: cn=j-developers,ou=jdeveloper,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: j-developers
ou: jdeveloper
member: cn=java-developers,ou=jdeveloper,dc=springframework,dc=org
dn: cn=java-developers,ou=jdeveloper,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: java-developers
ou: jdeveloper
member: cn=groovy-developers,ou=jdeveloper,dc=springframework,dc=org
member: cn=scala-developers,ou=jdeveloper,dc=springframework,dc=org
member: uid=javadude,ou=people,dc=springframework,dc=org
dn: cn=circular-java-developers,ou=jdeveloper,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: circular-java-developers
ou: jdeveloper
member: cn=groovy-developers,ou=jdeveloper,dc=springframework,dc=org
member: cn=scala-developers,ou=jdeveloper,dc=springframework,dc=org
member: uid=javadude,ou=people,dc=springframework,dc=org
dn: cn=groovy-developers,ou=jdeveloper,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: groovy-developers
ou: jdeveloper
member: cn=closure-developers,ou=jdeveloper,dc=springframework,dc=org
member: uid=groovydude,ou=people,dc=springframework,dc=org
member: cn=circular-java-developers,ou=jdeveloper,dc=springframework,dc=org
dn: cn=closure-developers,ou=jdeveloper,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: closure-developers
ou: jdeveloper
member: uid=closuredude,ou=people,dc=springframework,dc=org
dn: cn=scala-developers,ou=jdeveloper,dc=springframework,dc=org
objectclass: top
objectclass: groupOfNames
cn: scala-developers
ou: jdeveloper
member: uid=scaladude,ou=people,dc=springframework,dc=org