I have found the issue that limit of number of users is from active directory. I have updated the script to use InitialLdapContext with PagedResultsControl and successfully imported around 12K users. Below is the updated script I have used:
package ldap
import java.util.Map.Entry
import java.util.logging.Level
import java.util.logging.Logger
import javax.naming.*;
import javax.naming.directory.*;
import javax.naming.ldap.*;
import net.datenwerke.security.service.usermanager.UserManagerService
import net.datenwerke.security.service.usermanager.entities.AbstractUserManagerNode
import net.datenwerke.security.service.usermanager.entities.Group
import net.datenwerke.security.service.usermanager.entities.OrganisationalUnit
import net.datenwerke.security.service.usermanager.entities.User
UserManagerService userManagerService = GLOBALS.getRsService(UserManagerService.class);
LdapUserLoader lul = new LdapUserLoader();
lul.setProviderUrl("ldap://directory.example.com:389");
lul.setSecurityPrincipal("CN=ldaptest,CN=Users,DC=directory,DC=example,DC=com");
lul.setSecurityCredentials("ldaptest");
lul.setLdapBase("OU=EXAMPLE,DC=directory,DC=example,DC=com");
OrganisationalUnit targetNode = (GLOBALS.findObject("/usermanager/external"));
if(null == targetNode){
AbstractUserManagerNode umRoot = userManagerService.getRoots().get(0);
targetNode = new OrganisationalUnit("external");
umRoot.addChild(targetNode);
userManagerService.persist(targetNode);
}
lul.setTargetNode(targetNode);
lul.run();
public class LdapUserLoader {
private final Logger logger = Logger.getLogger(getClass().getName());
private String ldapBase;
private String ldapFilter = "(|(objectClass=organizationalUnit)(objectClass=user)(objectClass=group))";
private String providerUrl;
private String securityCredentials;
private String securityPrincipal;
private OrganisationalUnit targetNode;
private boolean includeNamespace = false;
private Map<String, AbstractUserManagerNode> guidMap;
private Map<LdapName, AbstractUserManagerNode> nodesInDirectoryByName;
private Map<String, AbstractUserManagerNode> nodesInDirectoryByGuid;
private TreeMap<LdapName, SearchResult> searchResults;
private List<AbstractUserManagerNode> removedNodes;
private List<AbstractUserManagerNode> addedNodes;
private class AdGUID {
byte[] bytes;
public AdGUID(byte[] bytes) {
this.bytes = bytes;
}
private void addByte(StringBuffer sb, int k) {
if(k<=0xF)
sb.append("0");
sb.append(Integer.toHexString(k));
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
addByte(sb, (int)bytes[3] & 0xFF);
addByte(sb, (int)bytes[2] & 0xFF);
addByte(sb, (int)bytes[1] & 0xFF);
addByte(sb, (int)bytes[0] & 0xFF);
sb.append("-");
addByte(sb, (int)bytes[5] & 0xFF);
addByte(sb, (int)bytes[4] & 0xFF);
sb.append("-");
addByte(sb, (int)bytes[7] & 0xFF);
addByte(sb, (int)bytes[6] & 0xFF);
sb.append("-");
addByte(sb, (int)bytes[8] & 0xFF);
addByte(sb, (int)bytes[9] & 0xFF);
sb.append("-");
addByte(sb, (int)bytes[10] & 0xFF);
addByte(sb, (int)bytes[11] & 0xFF);
addByte(sb, (int)bytes[12] & 0xFF);
addByte(sb, (int)bytes[13] & 0xFF);
addByte(sb, (int)bytes[14] & 0xFF);
addByte(sb, (int)bytes[15] & 0xFF);
return sb.toString();
}
}
public LdapUserLoader() {
}
public String getLdapBase() {
return ldapBase;
}
public String getProviderUrl() {
return providerUrl;
}
public String getSecurityCredentials() {
return securityCredentials;
}
public String getSecurityPrincipal() {
return securityPrincipal;
}
public void setLdapBase(String ldapBase) {
this.ldapBase = ldapBase;
}
public void setProviderUrl(String providerUrl) {
this.providerUrl = providerUrl;
}
public void setSecurityCredentials(String securityCredentials) {
this.securityCredentials = securityCredentials;
}
public void setSecurityPrincipal(String securityPrincipal) {
this.securityPrincipal = securityPrincipal;
}
public void setTargetNode(OrganisationalUnit targetNode) {
this.targetNode = targetNode;
}
public boolean isIncludeNamespace() {
return includeNamespace;
}
public void setIncludeNamespace(boolean includeNamespace) {
this.includeNamespace = includeNamespace;
}
public String getLdapFilter() {
return ldapFilter;
}
public void setLdapFilter(String ldapFilter) {
this.ldapFilter = ldapFilter;
}
private Properties compileProperties(){
Properties props = new Properties();
props.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
props.setProperty(Context.PROVIDER_URL, providerUrl);
//props.setProperty(Context.URL_PKG_PREFIXES, "com.sun.jndi.url");
//props.setProperty(Context.REFERRAL, "throw");
props.setProperty(Context.SECURITY_AUTHENTICATION, "simple");
props.setProperty(Context.SECURITY_PRINCIPAL, securityPrincipal);
props.setProperty(Context.SECURITY_CREDENTIALS, securityCredentials);
/* return these as binary */
props.put("java.naming.ldap.attributes.binary","objectGUID");
return props;
}
private void createGuidMap(AbstractUserManagerNode current) {
Map<String, AbstractUserManagerNode> map = new HashMap<>();
createGuidMap(current, map );
guidMap = map;
}
private void createGuidMap(AbstractUserManagerNode current, Map<String, AbstractUserManagerNode> map){
map.put(current.getGuid(), current);
for(AbstractUserManagerNode cn : current.getChildren()){
createGuidMap(cn, map);
}
}
private String getStringAttribute(SearchResult sr, String attributeName) throws NamingException{
try{
return sr.getAttributes().get(attributeName).get().toString();
}catch(Exception e){
logger.log(Level.WARNING,"failed to retrieve attribute '" + attributeName + "' from " + sr.getNameInNamespace(), e);
return null;
}
}
private String getGuid(SearchResult sr) throws NamingException{
try{
AdGUID guid = new AdGUID((byte[]) sr.getAttributes().get("objectGUID").get());
return guid.toString();
}catch(Exception e){
throw new RuntimeException("failed to retrieve objectGUID from " + sr.getNameInNamespace(), e);
}
}
private void loadFromDirectory() throws NamingException {
Properties props = compileProperties();
String originBase = this.providerUrl.endsWith("/")?providerUrl:providerUrl + "/";
this.nodesInDirectoryByName = new HashMap<>();
this.nodesInDirectoryByGuid = new HashMap<>();
this.addedNodes = new ArrayList<>();
this.removedNodes = new ArrayList<>();
InitialLdapContext ctx = null ;
try {
ctx = new InitialLdapContext (props);
int pageSize = 500;
byte[] cookie = null;
ctx.setRequestControls([new PagedResultsControl(pageSize, Control.NONCRITICAL) ] as Control[]);
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
LdapName ldapBaseName = new LdapName(getLdapBase());
/* order search results by name to make sure children never get processed before their parent */
searchResults = new TreeMap<>();
int total;
while(true) {
NamingEnumeration<SearchResult> results = ctx.search(ldapBaseName, this.ldapFilter, searchControls);
while (results.hasMore()) {
SearchResult sr = (SearchResult) results.next();
searchResults.put(new LdapName(sr.getNameInNamespace()), sr);
}
Control[] controls = ctx.getResponseControls();
if (controls != null) {
for (int i = 0; i < controls.length; i++) {
if (controls[i] instanceof PagedResultsResponseControl) {
PagedResultsResponseControl prrc = (PagedResultsResponseControl)controls[i];
//total = prrc.getResultSize();
//logger.log(Level.INFO,"Total is: " + total.toString());
cookie = prrc.getCookie();
}
}
} else {
logger.log(Level.INFO,"No controls were sent from the server");
break;
}
ctx.setRequestControls([new PagedResultsControl(pageSize, cookie, Control.CRITICAL) ] as Control[]);
if(null == cookie)
break;
}
for(SearchResult sr : searchResults.values()){
try {
LdapName nodeName = new LdapName(isIncludeNamespace() ? sr.getNameInNamespace() : sr.getName());
LdapName nodeNameInNamespace = new LdapName(sr.getNameInNamespace());
/* skip empty nodes */
if(nodeName.size() == 0)
continue;
/* get parent node */
LdapName parentName = (LdapName) nodeNameInNamespace.getPrefix(Math.max(0, nodeNameInNamespace.size() - 1));
AbstractUserManagerNode parent = this.nodesInDirectoryByName.get(parentName);
if(null == parent){
parent = targetNode;
}
/* create node */
Attribute objectClass = sr.getAttributes().get("objectClass");
AbstractUserManagerNode umNode = null;
if(null != objectClass) {
if(objectClass.contains("organizationalUnit")) {
umNode = createOUNode(sr, parent);
} else if(objectClass.contains("user")) {
umNode = createUserNode(sr, parent);
} else if(objectClass.contains("group")){
umNode = createGroupNode(sr, parent);
}
if(null != umNode) {
/* set common attributes */
umNode.setWriteProtection(false);
umNode.setGuid(getGuid(sr));
umNode.setOrigin(originBase + sr.getNameInNamespace());
nodesInDirectoryByName.put(new LdapName(sr.getNameInNamespace()), umNode);
nodesInDirectoryByGuid.put(getGuid(sr), umNode);
}
}
}catch(Exception e){
throw new RuntimeException("Error processing search result: " + sr.getNameInNamespace() , e);
}
}
}finally{
try {
if(null != ctx)
ctx.close();
} catch (NamingException e) {
logger.log(Level.WARNING, e.getMessage(), e);
}
}
}
private AbstractUserManagerNode createGroupNode(SearchResult sr, AbstractUserManagerNode parent) throws NamingException {
Group node = (Group) guidMap.get(getGuid(sr));
if(null == node){
node = new Group();
addedNodes.add(node);
}
parent.addChild(node);
/* copy Group attributes */
node.setName(getStringAttribute(sr, "name"));
return node;
}
private AbstractUserManagerNode createUserNode(SearchResult sr, AbstractUserManagerNode parent) throws NamingException {
User node = (User) guidMap.get(getGuid(sr));
if(null == node){
node = new User();
addedNodes.add(node);
}
parent.addChild(node);
/* copy User attributes */
node.setFirstname(getStringAttribute(sr, "givenName"));
node.setLastname(getStringAttribute(sr, "sn"));
node.setUsername(getStringAttribute(sr, "sAMAccountName"));
return node;
}
private AbstractUserManagerNode createOUNode(SearchResult sr, AbstractUserManagerNode parent) throws NamingException {
OrganisationalUnit node = (OrganisationalUnit) guidMap.get(getGuid(sr));
if(null == node){
node = new OrganisationalUnit();
addedNodes.add(node);
}
parent.addChild(node);
/* copy OU attributes */
node.setName(getStringAttribute(sr, "name"));
return node;
}
private void postprocessGroups() throws NamingException {
/* clear */
for(Entry<LdapName, AbstractUserManagerNode> entry : nodesInDirectoryByName.entrySet()){
if(entry.getValue() instanceof Group){
Group group = (Group) entry.getValue();
group.getUsers().clear();
group.getOus().clear();
group.getReferencedGroups().clear();
}
}
/* add appropriate users */
for(Entry<LdapName, AbstractUserManagerNode> entry : nodesInDirectoryByName.entrySet()){
if(entry.getValue() instanceof Group){
Group group = (Group) entry.getValue();
SearchResult sr = searchResults.get(entry.getKey());
if(null != sr.getAttributes().get("member")){
NamingEnumeration<?> members = sr.getAttributes().get("member").getAll();
while(members.hasMore()){
LdapName memberName = new LdapName(members.next().toString());
AbstractUserManagerNode member = nodesInDirectoryByName.get(memberName);
if(null != member){
if(member instanceof User)
group.addUser((User) member);
if(member instanceof OrganisationalUnit)
group.addOu((OrganisationalUnit) member);
if(member instanceof Group)
group.addReferencedGroup((Group) member);
}
}
}
}
}
}
private void printTree(AbstractUserManagerNode current){
StringBuilder sb = new StringBuilder();
List<AbstractUserManagerNode> rl = current.getRootLine();
Collections.reverse(rl);
for(AbstractUserManagerNode node : rl){
sb.append(node.getName()).append(".");
}
sb.append(current.getName() + " [" + current.getClass().getSimpleName() + "]" );
if(current instanceof Group){
Group group = (Group) current;
sb.append(" (").append(group.getUsers().size() + group.getOus().size() + group.getReferencedGroups().size()).append(" members)");
}
System.out.println(sb.toString());
for(AbstractUserManagerNode cn : current.getChildren()){
printTree(cn);
}
}
private void deleteRemovedUsers(AbstractUserManagerNode current) {
for(AbstractUserManagerNode c : current.getChildren()){
deleteRemovedUsers(c);
}
if(null != current.getOrigin() && current.getOrigin().startsWith(providerUrl) && !nodesInDirectoryByGuid.containsKey(current.getGuid())){
current.getParent().removeChild(current);
removedNodes.add(current);
}
}
public void run() throws NamingException{
createGuidMap(targetNode);
loadFromDirectory();
postprocessGroups();
deleteRemovedUsers(targetNode);
//logger.log(Level.INFO,"Retrieved nodes from directory: " + nodesInDirectoryByGuid.size() );
//logger.log(Level.INFO,"Nodes added: " + addedNodes.size() );
//logger.log(Level.INFO,"Nodes removed: " + removedNodes.size() );
int overallCount = countNodes(targetNode) - 1;
//logger.log(Level.INFO,"Overall nodes in rs: " + overallCount);
if(overallCount != nodesInDirectoryByGuid.size())
throw new RuntimeException("Failed to import user data from directory");
else
logger.log(Level.INFO,"done.");
// printTree(targetNode);
}
private int countNodes(AbstractUserManagerNode current) {
int i = 1;
for(AbstractUserManagerNode n : current.getChildren()){
i += countNodes(n);
}
return i;
}
}
The only problem is now the performance with huge number of users, it is difficult to search and edit as it takes a lot of time to load specific user info in “User management”