MOB-710: LDAP queries now performed with RFC 2696 paging and a configurable page size, defaulting to 1000 (the AD default maximum)

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@14648 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Dave Ward
2009-06-11 09:04:24 +00:00
parent b76f5fe5e1
commit a4b283068c
5 changed files with 406 additions and 243 deletions

View File

@@ -149,6 +149,16 @@
<property name="active"> <property name="active">
<value>${ldap.synchronization.active}</value> <value>${ldap.synchronization.active}</value>
</property> </property>
<!--
If positive, this property indicates that RFC 2696 paged results should be
used to split query results into batches of the specified size. This
overcomes any size limits imposed by the LDAP server.
-->
<property name="queryBatchSize">
<value>${ldap.synchronization.queryBatchSize}</value>
</property>
<!-- <!--
The query to select all objects that represent the groups to import. The query to select all objects that represent the groups to import.

View File

@@ -49,6 +49,11 @@ ldap.authentication.defaultAdministratorUserNames=
# authentication, in which case this flag should be set to false. # authentication, in which case this flag should be set to false.
ldap.synchronization.active=true ldap.synchronization.active=true
# If positive, this property indicates that RFC 2696 paged results should be
# used to split query results into batches of the specified size. This
# overcomes any size limits imposed by the LDAP server.
ldap.synchronization.queryBatchSize=1000
# The query to select all objects that represent the groups to import. # The query to select all objects that represent the groups to import.
ldap.synchronization.groupQuery=(objectclass\=groupOfNames) ldap.synchronization.groupQuery=(objectclass\=groupOfNames)

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2005-2007 Alfresco Software Limited. * Copyright (C) 2005-2009 Alfresco Software Limited.
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@@ -18,7 +18,7 @@
* As a special exception to the terms and conditions of version 2.0 of * As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre * the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's * and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing * FLOSS exception. You should have received a copy of the text describing
* the FLOSS exception, and it is also available here: * the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing" * http://www.alfresco.com/legal/licensing"
*/ */
@@ -26,6 +26,7 @@ package org.alfresco.repo.security.authentication.ldap;
import java.util.Map; import java.util.Map;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext; import javax.naming.directory.InitialDirContext;
import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.AuthenticationException;
@@ -44,6 +45,19 @@ public interface LDAPInitialDirContextFactory
*/ */
public void setInitialDirContextEnvironment(Map<String, String> environment); public void setInitialDirContextEnvironment(Map<String, String> environment);
/**
* Use the environment properties and connect to the LDAP server, optionally configuring RFC 2696 paged results.
* Used to obtain read only access to the LDAP server.
*
* @param pageSize
* if a positive value, indicates that a LDAP v3 RFC 2696 paged results control should be used. The
* results of a search operation should be returned by the LDAP server in batches of the specified size.
* @return the default intial dir context
* @throws AuthenticationException
* the authentication exception
*/
public InitialDirContext getDefaultIntialDirContext(int pageSize) throws AuthenticationException;
/** /**
* Use the environment properties and connect to the LDAP server. * Use the environment properties and connect to the LDAP server.
* Used to obtain read only access to the LDAP server. * Used to obtain read only access to the LDAP server.
@@ -53,6 +67,19 @@ public interface LDAPInitialDirContextFactory
*/ */
public InitialDirContext getDefaultIntialDirContext() throws AuthenticationException; public InitialDirContext getDefaultIntialDirContext() throws AuthenticationException;
/**
* Determines whether there is another page to fetch from the last search to be run in this context. Also prepares
* the request controls so that the appropriate cookie will be passed in the next search.
*
* @param ctx
* the context
* @param pageSize
* if a positive value, indicates that a LDAP v3 RFC 2696 paged results control should be used. The
* results of a search operation should be returned by the LDAP server in batches of the specified size.
* @return true, if is ready for next page
*/
public boolean hasNextPage(DirContext ctx, int pageSize);
/** /**
* Augment the connection environment with the identity and credentials and bind to the ldap server. * Augment the connection environment with the identity and credentials and bind to the ldap server.
* Mainly used to validate a user's credentials during authentication. * Mainly used to validate a user's credentials during authentication.

View File

@@ -24,6 +24,7 @@
*/ */
package org.alfresco.repo.security.authentication.ldap; package org.alfresco.repo.security.authentication.ldap;
import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.Hashtable; import java.util.Hashtable;
import java.util.Map; import java.util.Map;
@@ -35,7 +36,13 @@ import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes; import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute; import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes; import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext; import javax.naming.directory.InitialDirContext;
import javax.naming.ldap.Control;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import javax.naming.ldap.PagedResultsControl;
import javax.naming.ldap.PagedResultsResponseControl;
import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.AuthenticationException;
import org.alfresco.util.ApplicationContextHelper; import org.alfresco.util.ApplicationContextHelper;
@@ -71,18 +78,37 @@ public class LDAPInitialDirContextFactoryImpl implements LDAPInitialDirContextFa
} }
public InitialDirContext getDefaultIntialDirContext() throws AuthenticationException public InitialDirContext getDefaultIntialDirContext() throws AuthenticationException
{
return getDefaultIntialDirContext(0);
}
public InitialDirContext getDefaultIntialDirContext(int pageSize) throws AuthenticationException
{ {
Hashtable<String, String> env = new Hashtable<String, String>(initialDirContextEnvironment.size()); Hashtable<String, String> env = new Hashtable<String, String>(initialDirContextEnvironment.size());
env.putAll(initialDirContextEnvironment); env.putAll(initialDirContextEnvironment);
env.put("javax.security.auth.useSubjectCredsOnly", "false"); env.put("javax.security.auth.useSubjectCredsOnly", "false");
return buildInitialDirContext(env); return buildInitialDirContext(env, pageSize);
} }
private InitialDirContext buildInitialDirContext(Hashtable<String, String> env) throws AuthenticationException private InitialDirContext buildInitialDirContext(Hashtable<String, String> env, int pageSize)
throws AuthenticationException
{ {
try try
{ {
return new InitialDirContext(env); // If a page size has been requested, use LDAP v3 paging
if (pageSize > 0)
{
InitialLdapContext ctx = new InitialLdapContext(env, null);
ctx.setRequestControls(new Control[]
{
new PagedResultsControl(pageSize, Control.CRITICAL)
});
return ctx;
}
else
{
return new InitialDirContext(env);
}
} }
catch (javax.naming.AuthenticationException ax) catch (javax.naming.AuthenticationException ax)
{ {
@@ -92,6 +118,54 @@ public class LDAPInitialDirContextFactoryImpl implements LDAPInitialDirContextFa
{ {
throw new AuthenticationException("Unable to connect to LDAP Server; check LDAP configuration", nx); throw new AuthenticationException("Unable to connect to LDAP Server; check LDAP configuration", nx);
} }
catch (IOException e)
{
throw new AuthenticationException("Unable to encode LDAP v3 request controls; check LDAP configuration", e);
}
}
public boolean hasNextPage(DirContext ctx, int pageSize)
{
if (pageSize > 0)
{
try
{
LdapContext ldapContext = (LdapContext) ctx;
Control[] controls = ldapContext.getResponseControls();
// Retrieve the paged result cookie if there is one
if (controls != null)
{
for (Control control : controls)
{
if (control instanceof PagedResultsResponseControl)
{
byte[] cookie = ((PagedResultsResponseControl) control).getCookie();
if (cookie != null)
{
// Prepare for next page
ldapContext.setRequestControls(new Control[]
{
new PagedResultsControl(pageSize, cookie, Control.CRITICAL)
});
return true;
}
}
}
}
}
catch (NamingException nx)
{
throw new AuthenticationException("Unable to connect to LDAP Server; check LDAP configuration", nx);
}
catch (IOException e)
{
throw new AuthenticationException(
"Unable to encode LDAP v3 request controls; check LDAP configuration", e);
}
}
return false;
} }
public InitialDirContext getInitialDirContext(String principal, String credentials) throws AuthenticationException public InitialDirContext getInitialDirContext(String principal, String credentials) throws AuthenticationException
@@ -100,7 +174,7 @@ public class LDAPInitialDirContextFactoryImpl implements LDAPInitialDirContextFa
{ {
throw new AuthenticationException("Null user name provided."); throw new AuthenticationException("Null user name provided.");
} }
if (principal.length() == 0) if (principal.length() == 0)
{ {
throw new AuthenticationException("Empty user name provided."); throw new AuthenticationException("Empty user name provided.");
@@ -110,18 +184,18 @@ public class LDAPInitialDirContextFactoryImpl implements LDAPInitialDirContextFa
{ {
throw new AuthenticationException("No credentials provided."); throw new AuthenticationException("No credentials provided.");
} }
if (credentials.length() == 0) if (credentials.length() == 0)
{ {
throw new AuthenticationException("Empty credentials provided."); throw new AuthenticationException("Empty credentials provided.");
} }
Hashtable<String, String> env = new Hashtable<String, String>(initialDirContextEnvironment.size()); Hashtable<String, String> env = new Hashtable<String, String>(initialDirContextEnvironment.size());
env.putAll(initialDirContextEnvironment); env.putAll(initialDirContextEnvironment);
env.put(Context.SECURITY_PRINCIPAL, principal); env.put(Context.SECURITY_PRINCIPAL, principal);
env.put(Context.SECURITY_CREDENTIALS, credentials); env.put(Context.SECURITY_CREDENTIALS, credentials);
return buildInitialDirContext(env); return buildInitialDirContext(env, 0);
} }
public static void main(String[] args) public static void main(String[] args)
@@ -214,7 +288,7 @@ public class LDAPInitialDirContextFactoryImpl implements LDAPInitialDirContextFa
public void afterPropertiesSet() throws Exception public void afterPropertiesSet() throws Exception
{ {
// Check Anonymous bind // Check Anonymous bind
Hashtable<String, String> env = new Hashtable<String, String>(initialDirContextEnvironment.size()); Hashtable<String, String> env = new Hashtable<String, String>(initialDirContextEnvironment.size());
env.putAll(initialDirContextEnvironment); env.putAll(initialDirContextEnvironment);
env.remove(Context.SECURITY_PRINCIPAL); env.remove(Context.SECURITY_PRINCIPAL);
@@ -229,18 +303,18 @@ public class LDAPInitialDirContextFactoryImpl implements LDAPInitialDirContextFa
{ {
} }
catch(AuthenticationNotSupportedException e) catch (AuthenticationNotSupportedException e)
{ {
} }
catch (NamingException nx) catch (NamingException nx)
{ {
logger.error("Unable to connect to LDAP Server; check LDAP configuration", nx); logger.error("Unable to connect to LDAP Server; check LDAP configuration", nx);
return; return;
} }
// Simple DN and password // Simple DN and password
env = new Hashtable<String, String>(initialDirContextEnvironment.size()); env = new Hashtable<String, String>(initialDirContextEnvironment.size());
env.putAll(initialDirContextEnvironment); env.putAll(initialDirContextEnvironment);
env.put(Context.SECURITY_PRINCIPAL, "daftAsABrush"); env.put(Context.SECURITY_PRINCIPAL, "daftAsABrush");
@@ -259,7 +333,7 @@ public class LDAPInitialDirContextFactoryImpl implements LDAPInitialDirContextFa
{ {
logger.info("LDAP server does not fall back to anonymous bind for a string uid and password at " + env.get(Context.PROVIDER_URL)); logger.info("LDAP server does not fall back to anonymous bind for a string uid and password at " + env.get(Context.PROVIDER_URL));
} }
catch(AuthenticationNotSupportedException e) catch (AuthenticationNotSupportedException e)
{ {
logger.info("LDAP server does not fall back to anonymous bind for a string uid and password at " + env.get(Context.PROVIDER_URL)); logger.info("LDAP server does not fall back to anonymous bind for a string uid and password at " + env.get(Context.PROVIDER_URL));
} }
@@ -267,9 +341,9 @@ public class LDAPInitialDirContextFactoryImpl implements LDAPInitialDirContextFa
{ {
logger.info("LDAP server does not support simple string user ids and invalid credentials at "+ env.get(Context.PROVIDER_URL)); logger.info("LDAP server does not support simple string user ids and invalid credentials at "+ env.get(Context.PROVIDER_URL));
} }
// DN and password // DN and password
env = new Hashtable<String, String>(initialDirContextEnvironment.size()); env = new Hashtable<String, String>(initialDirContextEnvironment.size());
env.putAll(initialDirContextEnvironment); env.putAll(initialDirContextEnvironment);
env.put(Context.SECURITY_PRINCIPAL, "cn=daftAsABrush,dc=woof"); env.put(Context.SECURITY_PRINCIPAL, "cn=daftAsABrush,dc=woof");
@@ -288,7 +362,7 @@ public class LDAPInitialDirContextFactoryImpl implements LDAPInitialDirContextFa
{ {
logger.info("LDAP server does not fall back to anonymous bind for a simple dn and password at " + env.get(Context.PROVIDER_URL)); logger.info("LDAP server does not fall back to anonymous bind for a simple dn and password at " + env.get(Context.PROVIDER_URL));
} }
catch(AuthenticationNotSupportedException e) catch (AuthenticationNotSupportedException e)
{ {
logger.info("LDAP server does not fall back to anonymous bind for a simple dn and password at " + env.get(Context.PROVIDER_URL)); logger.info("LDAP server does not fall back to anonymous bind for a simple dn and password at " + env.get(Context.PROVIDER_URL));
} }
@@ -298,13 +372,13 @@ public class LDAPInitialDirContextFactoryImpl implements LDAPInitialDirContextFa
} }
// Check more if we have a real principal we expect to work // Check more if we have a real principal we expect to work
env = new Hashtable<String, String>(initialDirContextEnvironment.size()); env = new Hashtable<String, String>(initialDirContextEnvironment.size());
env.putAll(initialDirContextEnvironment); env.putAll(initialDirContextEnvironment);
if(env.get(Context.SECURITY_PRINCIPAL) != null) if (env.get(Context.SECURITY_PRINCIPAL) != null)
{ {
// Correct principal invalid password // Correct principal invalid password
env = new Hashtable<String, String>(initialDirContextEnvironment.size()); env = new Hashtable<String, String>(initialDirContextEnvironment.size());
env.putAll(initialDirContextEnvironment); env.putAll(initialDirContextEnvironment);
env.put(Context.SECURITY_CREDENTIALS, "sdasdasdasdasd123123123"); env.put(Context.SECURITY_CREDENTIALS, "sdasdasdasdasd123123123");
@@ -322,7 +396,7 @@ public class LDAPInitialDirContextFactoryImpl implements LDAPInitialDirContextFa
{ {
logger.info("LDAP server does not fall back to anonymous bind for known principal and invalid credentials at " + env.get(Context.PROVIDER_URL)); logger.info("LDAP server does not fall back to anonymous bind for known principal and invalid credentials at " + env.get(Context.PROVIDER_URL));
} }
catch(AuthenticationNotSupportedException e) catch (AuthenticationNotSupportedException e)
{ {
logger.info("LDAP server does not support the required authentication mechanism"); logger.info("LDAP server does not support the required authentication mechanism");
} }

View File

@@ -120,6 +120,12 @@ public class LDAPUserRegistry implements UserRegistry, InitializingBean, Activat
/** The attribute defaults. */ /** The attribute defaults. */
private Map<String, String> attributeDefaults; private Map<String, String> attributeDefaults;
/**
* The query batch size. If positive, indicates that RFC 2696 paged results should be used to split query results
* into batches of the specified size. Overcomes any size limits imposed by the LDAP server.
*/
private int queryBatchSize;
/** Should we error on missing group members? */ /** Should we error on missing group members? */
private boolean errorOnMissingMembers; private boolean errorOnMissingMembers;
@@ -377,6 +383,18 @@ public class LDAPUserRegistry implements UserRegistry, InitializingBean, Activat
this.attributeMapping = attributeMapping; this.attributeMapping = attributeMapping;
} }
/**
* Sets the query batch size.
*
* @param queryBatchSize
* If positive, indicates that RFC 2696 paged results should be used to split query results into batches
* of the specified size. Overcomes any size limits imposed by the LDAP server.
*/
public void setQueryBatchSize(int queryBatchSize)
{
this.queryBatchSize = queryBatchSize;
}
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see org.alfresco.repo.management.subsystems.ActivateableBean#isActive() * @see org.alfresco.repo.management.subsystems.ActivateableBean#isActive()
@@ -426,183 +444,187 @@ public class LDAPUserRegistry implements UserRegistry, InitializingBean, Activat
public Iterator<NodeDescription> getGroups(Date modifiedSince) public Iterator<NodeDescription> getGroups(Date modifiedSince)
{ {
Map<String, NodeDescription> lookup = new TreeMap<String, NodeDescription>(); Map<String, NodeDescription> lookup = new TreeMap<String, NodeDescription>();
SearchControls userSearchCtls = new SearchControls();
userSearchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
userSearchCtls.setReturningAttributes(this.groupAttributeNames);
InitialDirContext ctx = null; InitialDirContext ctx = null;
try try
{ {
ctx = this.ldapInitialContextFactory.getDefaultIntialDirContext(); ctx = this.ldapInitialContextFactory.getDefaultIntialDirContext(this.queryBatchSize);
do
SearchControls userSearchCtls = new SearchControls();
userSearchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
userSearchCtls.setReturningAttributes(this.groupAttributeNames);
NamingEnumeration<SearchResult> searchResults;
if (modifiedSince == null)
{ {
searchResults = ctx.search(this.groupSearchBase, this.groupQuery, userSearchCtls); NamingEnumeration<SearchResult> searchResults;
}
else
{
searchResults = ctx.search(this.groupSearchBase, this.groupDifferentialQuery, new Object[]
{
LDAPUserRegistry.LDAP_GENERALIZED_TIME_FORMAT.format(modifiedSince)
}, userSearchCtls);
}
LdapName groupDistinguishedNamePrefix = new LdapName(this.groupSearchBase); if (modifiedSince == null)
LdapName userDistinguishedNamePrefix = new LdapName(this.userSearchBase);
while (searchResults.hasMoreElements())
{
SearchResult result = searchResults.next();
Attributes attributes = result.getAttributes();
Attribute gidAttribute = attributes.get(this.groupIdAttributeName);
if (gidAttribute == null)
{ {
if (this.errorOnMissingGID) searchResults = ctx.search(this.groupSearchBase, this.groupQuery, userSearchCtls);
{
throw new AlfrescoRuntimeException(
"NodeDescription returned by group search does not have mandatory group id attribute "
+ attributes);
}
else
{
LDAPUserRegistry.logger.warn("Missing GID on " + attributes);
continue;
}
}
String gid = "GROUP_" + gidAttribute.get(0);
NodeDescription group = lookup.get(gid);
if (group == null)
{
group = new NodeDescription();
group.getProperties().put(ContentModel.PROP_AUTHORITY_NAME, gid);
lookup.put(gid, group);
}
else if (this.errorOnDuplicateGID)
{
throw new AlfrescoRuntimeException("Duplicate group id found for " + gid);
} }
else else
{ {
LDAPUserRegistry.logger.warn("Duplicate gid found for " + gid + " -> merging definitions"); searchResults = ctx.search(this.groupSearchBase, this.groupDifferentialQuery, new Object[]
}
Attribute modifyTimestamp = attributes.get(this.modifyTimestampAttributeName);
if (modifyTimestamp != null)
{
group.setLastModified(LDAPUserRegistry.LDAP_GENERALIZED_TIME_FORMAT.parse(modifyTimestamp.get()
.toString()));
}
Set<String> childAssocs = group.getChildAssociations();
Attribute memAttribute = attributes.get(this.memberAttributeName);
// check for null
if (memAttribute != null)
{
for (int i = 0; i < memAttribute.size(); i++)
{ {
String attribute = (String) memAttribute.get(i); LDAPUserRegistry.LDAP_GENERALIZED_TIME_FORMAT.format(modifiedSince)
if (attribute != null) }, userSearchCtls);
}
LdapName groupDistinguishedNamePrefix = new LdapName(this.groupSearchBase);
LdapName userDistinguishedNamePrefix = new LdapName(this.userSearchBase);
while (searchResults.hasMoreElements())
{
SearchResult result = searchResults.next();
Attributes attributes = result.getAttributes();
Attribute gidAttribute = attributes.get(this.groupIdAttributeName);
if (gidAttribute == null)
{
if (this.errorOnMissingGID)
{ {
LdapName distinguishedName = new LdapName(attribute); throw new AlfrescoRuntimeException(
Attribute nameAttribute; "NodeDescription returned by group search does not have mandatory group id attribute "
+ attributes);
}
else
{
LDAPUserRegistry.logger.warn("Missing GID on " + attributes);
continue;
}
}
String gid = "GROUP_" + gidAttribute.get(0);
// If the user and group search bases are different we may be able to recognise user and NodeDescription group = lookup.get(gid);
// group DNs without a secondary lookup if (group == null)
if (!this.userSearchBase.equals(this.groupSearchBase)) {
group = new NodeDescription();
group.getProperties().put(ContentModel.PROP_AUTHORITY_NAME, gid);
lookup.put(gid, group);
}
else if (this.errorOnDuplicateGID)
{
throw new AlfrescoRuntimeException("Duplicate group id found for " + gid);
}
else
{
LDAPUserRegistry.logger.warn("Duplicate gid found for " + gid + " -> merging definitions");
}
Attribute modifyTimestamp = attributes.get(this.modifyTimestampAttributeName);
if (modifyTimestamp != null)
{
group.setLastModified(LDAPUserRegistry.LDAP_GENERALIZED_TIME_FORMAT.parse(modifyTimestamp.get()
.toString()));
}
Set<String> childAssocs = group.getChildAssociations();
Attribute memAttribute = attributes.get(this.memberAttributeName);
// check for null
if (memAttribute != null)
{
for (int i = 0; i < memAttribute.size(); i++)
{
String attribute = (String) memAttribute.get(i);
if (attribute != null)
{ {
Attributes nameAttributes = distinguishedName.getRdn(distinguishedName.size() - 1) LdapName distinguishedName = new LdapName(attribute);
.toAttributes(); Attribute nameAttribute;
// Recognise user DNs // If the user and group search bases are different we may be able to recognise user and
if (distinguishedName.startsWith(userDistinguishedNamePrefix) // group DNs without a secondary lookup
&& (nameAttribute = nameAttributes.get(this.userIdAttributeName)) != null) if (!this.userSearchBase.equalsIgnoreCase(this.groupSearchBase))
{ {
childAssocs.add((String) nameAttribute.get()); Attributes nameAttributes = distinguishedName.getRdn(distinguishedName.size() - 1)
continue; .toAttributes();
}
// Recognise group DNs // Recognise user DNs
if (distinguishedName.startsWith(groupDistinguishedNamePrefix) if (distinguishedName.startsWith(userDistinguishedNamePrefix)
&& (nameAttribute = nameAttributes.get(this.groupIdAttributeName)) != null) && (nameAttribute = nameAttributes.get(this.userIdAttributeName)) != null)
{
childAssocs.add("GROUP_" + nameAttribute.get());
continue;
}
}
// If we can't determine the name and type from the DN alone, try a directory lookup
if (distinguishedName.startsWith(userDistinguishedNamePrefix)
|| distinguishedName.startsWith(groupDistinguishedNamePrefix))
{
try
{
Attributes childAttributes = ctx.getAttributes(attribute, new String[]
{ {
"objectclass", this.groupIdAttributeName, this.userIdAttributeName
});
String objectclass = (String) childAttributes.get("objectclass").get();
if (objectclass.equals(this.personType))
{
nameAttribute = childAttributes.get(this.userIdAttributeName);
if (nameAttribute == null)
{
if (this.errorOnMissingUID)
{
throw new AlfrescoRuntimeException(
"User missing user id attribute DN =" + attribute + " att = "
+ this.userIdAttributeName);
}
else
{
LDAPUserRegistry.logger.warn("User missing user id attribute DN ="
+ attribute + " att = " + this.userIdAttributeName);
continue;
}
}
childAssocs.add((String) nameAttribute.get()); childAssocs.add((String) nameAttribute.get());
continue; continue;
} }
else if (objectclass.equals(this.groupType))
{
nameAttribute = childAttributes.get(this.groupIdAttributeName); // Recognise group DNs
if (nameAttribute == null) if (distinguishedName.startsWith(groupDistinguishedNamePrefix)
{ && (nameAttribute = nameAttributes.get(this.groupIdAttributeName)) != null)
if (this.errorOnMissingGID) {
{
throw new AlfrescoRuntimeException(
"Group returned by group search does not have mandatory group id attribute "
+ attributes);
}
else
{
LDAPUserRegistry.logger.warn("Missing GID on " + childAttributes);
continue;
}
}
childAssocs.add("GROUP_" + nameAttribute.get()); childAssocs.add("GROUP_" + nameAttribute.get());
continue; continue;
} }
} }
catch (NamingException e)
// If we can't determine the name and type from the DN alone, try a directory lookup
if (distinguishedName.startsWith(userDistinguishedNamePrefix)
|| distinguishedName.startsWith(groupDistinguishedNamePrefix))
{ {
// Unresolvable name try
{
Attributes childAttributes = ctx.getAttributes(attribute, new String[]
{
"objectclass", this.groupIdAttributeName, this.userIdAttributeName
});
String objectclass = (String) childAttributes.get("objectclass").get();
if (objectclass.equalsIgnoreCase(this.personType))
{
nameAttribute = childAttributes.get(this.userIdAttributeName);
if (nameAttribute == null)
{
if (this.errorOnMissingUID)
{
throw new AlfrescoRuntimeException(
"User missing user id attribute DN =" + attribute
+ " att = " + this.userIdAttributeName);
}
else
{
LDAPUserRegistry.logger.warn("User missing user id attribute DN ="
+ attribute + " att = " + this.userIdAttributeName);
continue;
}
}
childAssocs.add((String) nameAttribute.get());
continue;
}
else if (objectclass.equalsIgnoreCase(this.groupType))
{
nameAttribute = childAttributes.get(this.groupIdAttributeName);
if (nameAttribute == null)
{
if (this.errorOnMissingGID)
{
throw new AlfrescoRuntimeException(
"Group returned by group search does not have mandatory group id attribute "
+ attributes);
}
else
{
LDAPUserRegistry.logger.warn("Missing GID on " + childAttributes);
continue;
}
}
childAssocs.add("GROUP_" + nameAttribute.get());
continue;
}
}
catch (NamingException e)
{
// Unresolvable name
}
} }
if (this.errorOnMissingMembers)
{
throw new AlfrescoRuntimeException("Failed to resolve distinguished name: "
+ attribute);
}
LDAPUserRegistry.logger.warn("Failed to resolve distinguished name: " + attribute);
} }
if (this.errorOnMissingMembers)
{
throw new AlfrescoRuntimeException("Failed to resolve distinguished name: " + attribute);
}
LDAPUserRegistry.logger.warn("Failed to resolve distinguished name: " + attribute);
} }
} }
} }
} }
while (this.ldapInitialContextFactory.hasNextPage(ctx, this.queryBatchSize));
if (LDAPUserRegistry.logger.isDebugEnabled()) if (LDAPUserRegistry.logger.isDebugEnabled())
{ {
@@ -643,6 +665,10 @@ public class LDAPUserRegistry implements UserRegistry, InitializingBean, Activat
/** The directory context. */ /** The directory context. */
private InitialDirContext ctx; private InitialDirContext ctx;
private SearchControls userSearchCtls;
private Date modifiedSince;
/** The search results. */ /** The search results. */
private NamingEnumeration<SearchResult> searchResults; private NamingEnumeration<SearchResult> searchResults;
@@ -663,29 +689,18 @@ public class LDAPUserRegistry implements UserRegistry, InitializingBean, Activat
{ {
try try
{ {
this.ctx = LDAPUserRegistry.this.ldapInitialContextFactory.getDefaultIntialDirContext(); this.ctx = LDAPUserRegistry.this.ldapInitialContextFactory
.getDefaultIntialDirContext(LDAPUserRegistry.this.queryBatchSize);
// Authentication has been successful. // Authentication has been successful.
// Set the current user, they are now authenticated. // Set the current user, they are now authenticated.
SearchControls userSearchCtls = new SearchControls(); this.userSearchCtls = new SearchControls();
userSearchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE); this.userSearchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
userSearchCtls.setCountLimit(Integer.MAX_VALUE); this.userSearchCtls.setReturningAttributes(LDAPUserRegistry.this.userAttributeNames);
userSearchCtls.setReturningAttributes(LDAPUserRegistry.this.userAttributeNames);
this.modifiedSince = modifiedSince;
if (modifiedSince == null)
{
this.searchResults = this.ctx.search(LDAPUserRegistry.this.userSearchBase,
LDAPUserRegistry.this.personQuery, userSearchCtls);
}
else
{
this.searchResults = this.ctx.search(LDAPUserRegistry.this.userSearchBase,
LDAPUserRegistry.this.personDifferentialQuery, new Object[]
{
LDAPUserRegistry.LDAP_GENERALIZED_TIME_FORMAT.format(modifiedSince)
}, userSearchCtls);
}
this.next = fetchNext(); this.next = fetchNext();
} }
catch (NamingException e) catch (NamingException e)
@@ -748,74 +763,88 @@ public class LDAPUserRegistry implements UserRegistry, InitializingBean, Activat
*/ */
private NodeDescription fetchNext() throws NamingException private NodeDescription fetchNext() throws NamingException
{ {
while (this.searchResults.hasMoreElements()) boolean readyForNextPage;
do
{ {
SearchResult result = this.searchResults.next(); readyForNextPage = this.searchResults == null;
Attributes attributes = result.getAttributes(); while (!readyForNextPage && this.searchResults.hasMoreElements())
Attribute uidAttribute = attributes.get(LDAPUserRegistry.this.userIdAttributeName);
if (uidAttribute == null)
{ {
if (LDAPUserRegistry.this.errorOnMissingUID) SearchResult result = this.searchResults.next();
Attributes attributes = result.getAttributes();
Attribute uidAttribute = attributes.get(LDAPUserRegistry.this.userIdAttributeName);
if (uidAttribute == null)
{ {
throw new AlfrescoRuntimeException( if (LDAPUserRegistry.this.errorOnMissingUID)
"User returned by user search does not have mandatory user id attribute " + attributes); {
throw new AlfrescoRuntimeException(
"User returned by user search does not have mandatory user id attribute "
+ attributes);
}
else
{
LDAPUserRegistry.logger
.warn("User returned by user search does not have mandatory user id attribute "
+ attributes);
continue;
}
} }
else String uid = (String) uidAttribute.get(0);
if (this.uids.contains(uid))
{ {
LDAPUserRegistry.logger LDAPUserRegistry.logger
.warn("User returned by user search does not have mandatory user id attribute " .warn("Duplicate uid found - there will be more than one person object for this user - "
+ attributes); + uid);
continue;
} }
}
String uid = (String) uidAttribute.get(0);
if (this.uids.contains(uid)) this.uids.add(uid);
{
LDAPUserRegistry.logger
.warn("Duplicate uid found - there will be more than one person object for this user - "
+ uid);
}
this.uids.add(uid); if (LDAPUserRegistry.logger.isDebugEnabled())
if (LDAPUserRegistry.logger.isDebugEnabled())
{
LDAPUserRegistry.logger.debug("Adding user for " + uid);
}
NodeDescription person = new NodeDescription();
Attribute modifyTimestamp = attributes.get(LDAPUserRegistry.this.modifyTimestampAttributeName);
if (modifyTimestamp != null)
{
try
{ {
person.setLastModified(LDAPUserRegistry.LDAP_GENERALIZED_TIME_FORMAT.parse(modifyTimestamp LDAPUserRegistry.logger.debug("Adding user for " + uid);
.get().toString()));
} }
catch (ParseException e)
{
throw new AlfrescoRuntimeException("Failed to import people.", e);
}
}
PropertyMap properties = person.getProperties(); NodeDescription person = new NodeDescription();
for (String key : LDAPUserRegistry.this.attributeMapping.keySet())
{
QName keyQName = QName.createQName(key, LDAPUserRegistry.this.namespaceService);
// cater for null Attribute modifyTimestamp = attributes.get(LDAPUserRegistry.this.modifyTimestampAttributeName);
String attributeName = LDAPUserRegistry.this.attributeMapping.get(key); if (modifyTimestamp != null)
if (attributeName != null)
{ {
Attribute attribute = attributes.get(attributeName); try
if (attribute != null)
{ {
String value = (String) attribute.get(0); person.setLastModified(LDAPUserRegistry.LDAP_GENERALIZED_TIME_FORMAT.parse(modifyTimestamp
if (value != null) .get().toString()));
}
catch (ParseException e)
{
throw new AlfrescoRuntimeException("Failed to import people.", e);
}
}
PropertyMap properties = person.getProperties();
for (String key : LDAPUserRegistry.this.attributeMapping.keySet())
{
QName keyQName = QName.createQName(key, LDAPUserRegistry.this.namespaceService);
// cater for null
String attributeName = LDAPUserRegistry.this.attributeMapping.get(key);
if (attributeName != null)
{
Attribute attribute = attributes.get(attributeName);
if (attribute != null)
{ {
properties.put(keyQName, value); String value = (String) attribute.get(0);
if (value != null)
{
properties.put(keyQName, value);
}
}
else
{
String defaultValue = LDAPUserRegistry.this.attributeDefaults.get(key);
if (defaultValue != null)
{
properties.put(keyQName, defaultValue);
}
} }
} }
else else
@@ -827,17 +856,35 @@ public class LDAPUserRegistry implements UserRegistry, InitializingBean, Activat
} }
} }
} }
return person;
}
// Examine the paged results control response for an indication that another page is available
if (!readyForNextPage)
{
readyForNextPage = LDAPUserRegistry.this.ldapInitialContextFactory.hasNextPage(this.ctx,
LDAPUserRegistry.this.queryBatchSize);
}
// Fetch the next page if there is one
if (readyForNextPage)
{
if (this.modifiedSince == null)
{
this.searchResults = this.ctx.search(LDAPUserRegistry.this.userSearchBase,
LDAPUserRegistry.this.personQuery, this.userSearchCtls);
}
else else
{ {
String defaultValue = LDAPUserRegistry.this.attributeDefaults.get(key); this.searchResults = this.ctx.search(LDAPUserRegistry.this.userSearchBase,
if (defaultValue != null) LDAPUserRegistry.this.personDifferentialQuery, new Object[]
{ {
properties.put(keyQName, defaultValue); LDAPUserRegistry.LDAP_GENERALIZED_TIME_FORMAT.format(this.modifiedSince)
} }, this.userSearchCtls);
} }
} }
return person;
} }
while (readyForNextPage);
this.searchResults.close(); this.searchResults.close();
this.searchResults = null; this.searchResults = null;
this.ctx.close(); this.ctx.close();