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:
@@ -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";
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user