/* * Copyright 2007 * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * * Notes: * 1. Implementation dependency: Assuming manipulations to the list returned from listGroupsForUser updates the real list * 2. Using a simple Cache implementation which expires entries in the cache after a specified amount of time - maybe there is a better implementation for that available * * @author gleeb */ package com.xpn.xwiki.ldap.authentication; import com.novell.ldap.*; import java.util.ArrayList; import java.util.Enumeration; import java.util.Iterator; import java.io.UnsupportedEncodingException; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.securityfilter.realm.SimplePrincipal; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.objects.BaseObject; import com.xpn.xwiki.objects.classes.BaseClass; import com.xpn.xwiki.user.api.XWikiGroupService; import com.xpn.xwiki.user.impl.xwiki.XWikiAuthServiceImpl; import java.security.Principal; import java.security.Security; import java.text.MessageFormat; import java.util.HashMap; /* * This class provides an authentication method that validates a user trough LDAP against a directory. * It just gives LDAP users access if they belong to a particular group and creates XWiki users if they have never logged in before * Furthermore, it synchronizes membership to XWiki groups based on membership to LDAP groups. */ public class LDAPAuthenticater extends XWikiAuthServiceImpl { private static final Log log = LogFactory.getLog(LDAPAuthenticater.class); // simple cache for LDAP groups and their members - cache entries are to expire after 6h and there is no size limit protected static Cache> cache = new Cache>(21800, 0); // 1. check Superadmin // 2. check if ldap authentication is off => authenticate against db // 3. bind to LDAP => if failed try db // 4. if group param, verify group membership (& get DN) // 4. if no group locate dn_ldap attribute on user // 5. if no dn search for user // 6. compare pwd // 7. if flag check if !user exists => create user // 8. if flag update user attributes // 9. if flag update Xwiki groupmembership // on Error try local DB login public Principal authenticate(String user, String pwd, XWikiContext context) throws XWikiException { Principal result = null; // message to the user: nousername, nopassword, wrongpassword, wronguser, loginfailed // context.put("message", "nopassword"); if ((user==null)||(user.length()==0)) return null; user=user.replaceAll(" ", ""); if ((pwd == null) || (pwd.trim().length()==0)) return null; pwd = pwd.trim(); // strip possible "XWiki." // ATTENTION: Possible incompatibility to before now user is NEVER located with "XWiki.username" in LDAP // Also Usernames must NOT have a . in the name // as a consequence of LDAP, Usernames must NOT contain , as well int i = user.indexOf("."); if (i != -1) { user = user.substring(i + 1); } // If we have the context then we are using direct mode // then we should specify the database // This is needed for virtual mode to work // NO IDEA WHAT NEEDS TO BE DONE if context is null if (context == null) { log.debug("LDAP authentication failed: no context"); return null; } LDAPConnection lc = null; try { result = checkSuperadmin(user, pwd, context); if(result != null) { log.debug("Superadmin logged in."); return result; } // check if LDAP is activated in xwiki.cfg String ldap_activ = getParam("ldap", context); if(ldap_activ.length()==0 || ! ldap_activ.equalsIgnoreCase("1")) { log.debug("LDAP authentication failed: LDAP not activ"); return null; } // open LDAP int ldapPort = getLDAPPort(context); String ldapHost = getParam("ldap_server", context); String bindDNFormat = getParam("ldap_bind_DN", context); String bindPasswordFormat = getParam("ldap_bind_pass", context); String uidattr = getParam("ldap_UID_attr", context); if(uidattr == null || uidattr.length() == 0) uidattr = "cn"; Object[] arguments = { user, pwd }; // allow to use the given user and password also as the LDAP bind user and password String bindDN = MessageFormat.format(bindDNFormat, arguments); String bindPassword = MessageFormat.format(bindPasswordFormat, arguments); if ("1".equals(getParam("ldap_ssl", context))) { String keyStore = getParam("ldap_ssl.keystore", context); log.debug("Connecting to LDAP using SSL"); lc = openLDAPSSL(ldapHost, ldapPort, bindDN, bindPassword, keyStore); } else { lc = openLDAP(ldapHost, ldapPort, bindDN, bindPassword); } if(lc == null) { log.error("Bind to LDAP server failed."); return null; } // update cache parameter int expire = getCacheExpiration(context); // verify group String user_dn = null; String usergroup = null; usergroup = getParam("ldap_user_group", context); log.debug("Checking if the user belongs to the user group: " + usergroup); if(usergroup.length()>0) { // get members HashMap list = null; synchronized(cache) { cache.setTTL(expire); list = cache.getItem(usergroup); } if(list == null) { list = new HashMap(); log.debug("Retrieving Members of the group: " + usergroup); getGroupMembers(lc, usergroup, uidattr, list, new ArrayList()); synchronized(cache) { cache.putItem(usergroup, list); } } // check if user is in the list user_dn = findInGroup(user, list, "cn"); log.debug("Found user dn in user group:" + user_dn); // if a usergroup is specified THEN the user MUST be in the group to validate in LDAP if(user_dn == null) { log.debug("LDAP authentication failed: user not in LDAP user group"); return null; // no valid LDAP user from the group } } if(user_dn == null) { // get DN from exisiting XWiki user user_dn = getLDAP_DN(user, context); log.debug("Found user dn with the user object: " + user_dn); } // parse field mappings ArrayList attributes = null; HashMap mappings = new HashMap(); ArrayList attrList = new ArrayList(); String ldapFieldMapping = getParam("ldap_fields_mapping",context); if (log.isDebugEnabled()) log.debug("Ready to create user from LDAP with fields " + ldapFieldMapping); if (ldapFieldMapping != null && ldapFieldMapping.length() > 0) { String[] fields = ldapFieldMapping.split(","); for (int j = 0; j < fields.length; j++) { String[] field = fields[j].split("="); if (2 == field.length) { String xwikiattr = field[0].replace(" ", ""); String ldapattr = field[1].replace(" ", ""); mappings.put(ldapattr, xwikiattr); attrList.add(ldapattr); log.debug("Found field mapping: " + xwikiattr + "=" + ldapattr); } else { log.error("Error parsing ldap_fields_mapping attribute in xwiki.cfg: " + fields[j]); } } } // if we still don't have a dn, search for it. Also get the attributes, we might need them if(user_dn == null) { // search for the user in LDAP String query = "(" + uidattr + "=" + user + ")"; String baseDN = getParam("ldap_base_DN", context); if (log.isDebugEnabled()) log.debug("Searching for the user in LDAP: user:" + user + " base:" + baseDN + " query:"+ query + " uid:" + uidattr); String[] attrl = null; int lsize = attrList.size(); if(lsize > 0) { attrl = attrList.toArray(new String[lsize]); } ArrayList> list = searchLDAP(lc, baseDN, query, attrl, LDAPConnection.SCOPE_SUB); if(list.size() == 0) { log.debug("User not found in ldap after search: " + query); return null; } // locate the best matching attribute set, if there are more than one result if(list.size() > 1) { log.error("More than one user found in LDAP for the query " + query); ArrayList foundset = null; // match the uidattr for(ArrayList attr : list) { for(SearchAttributes sa : attr) { if(log.isErrorEnabled() && "dn".equals(sa.attrName)) { log.error(" User found: " + sa.attrValue); } if(uidattr.equals(sa.attrName)) { if(user.equalsIgnoreCase(sa.attrValue)) { if(foundset == null) { foundset = attr; log.debug("Candidate found: " + sa.attrName); } else { // found already one>> user name must be unique log.error(" Two users have the same ids in LDAP:" + query); return null; } } } } } if(foundset == null) return null; attributes = foundset; } else { attributes = list.get(0); } for(SearchAttributes sa : attributes) { if("dn".equals(sa.attrName)) { user_dn = sa.attrValue; break; } } if(log.isDebugEnabled()) { log.debug("Found Attributes in LDAP " + attributes.size()); log.debug("Found user dn=" + user_dn); } } // NOW verify password if ("1".equals(getParam("ldap_validate_password", context))) { if(! verifyPWD(lc, user_dn, pwd)) { log.debug("LDAP authentication failed: could not validate the password: wrong password for " + user_dn); return null; } } else { log.debug("Password is already supposed to be verified when bound to LDAP"); } boolean updateUser = false; //default, don't update the user with attributes // check if we have to create the user String xwikiuser = findUser(user, context); String upduser = getParam("ldap_update_user", context); if(upduser.equals("1")) updateUser = true; boolean createuser = false; if(xwikiuser == null || updateUser) { log.debug("LDAP attributes will be used to update XWiki attributes."); // get attributes from LDAP if we don't already have them if(attributes == null) { String[] attrl = null; int lsize = attrList.size(); if(lsize > 0) { attrl = attrList.toArray(new String[lsize]); } // didn't get attributes before, so do it now ArrayList> list = searchLDAP(lc, user_dn, null, attrl, LDAPConnection.SCOPE_BASE); if(list.size() == 0 || list.size() > 1) { log.error("User DN not found in LDAP or more than one found: " + user_dn); } if(list.size() != 0) attributes = list.get(0); if(log.isDebugEnabled()) log.debug("Found Attributes in LDAP " + attributes.size()); } // create user if(xwikiuser == null) { log.debug("Creating new XWiki user based on LDAP attribues located at " + user_dn); createUserFromLDAP(user, mappings, attributes, context); log.debug("New XWiki user created: " + user); createuser = true; } else { log.debug("Updating existing user with LDAP attribues located at " + user_dn); updateUserFromLDAP(user, mappings, attributes, context); } } // from now on we can enter the application result = GetUserPrincipal(user, context); if(result == null) { log.debug("Could not create authenticated principal."); return null; } HashMap groupmappings = new HashMap(); int pos = 1; String grouplistmapping = getParam("ldap_group_mapping_" + pos, context); while(grouplistmapping != null && grouplistmapping.length() > 0) { int splitt = grouplistmapping.indexOf('='); // first = if(splitt < 1) { log.error("Error parsing ldap_group_mapping attribute in xwiki.cfg: " + grouplistmapping); } else { String xwikigroup = grouplistmapping.substring(0, splitt); String ldapgroup = grouplistmapping.substring(splitt+1); groupmappings.put(ldapgroup, xwikigroup); log.debug("Groupmapping found: " + xwikigroup + "=" + ldapgroup); } pos++; grouplistmapping = getParam("ldap_group_mapping_" + pos, context); } if(groupmappings.size()>0) { // got valid group mappings // update group membership, join and remove from given groups // sync group membership for this user // flag if always sync or just on create of the user String syncmode = getParam("ldap_mode_group_sync", context); if( (syncmode.equalsIgnoreCase("create") && createuser) || syncmode.equalsIgnoreCase("always") ) { log.debug("Updating group membership for the user: " + user ); // ASSUMING the implementation still returns the actual list. In this case manipulations to the list are for real. // get the list of groups the user already belongs to java.util.Collection c = context.getWiki().getGroupService(context).listGroupsForUser(user, context); if(log.isDebugEnabled()) { log.debug("The user belongs to following XWiki groups: "); for(Object x : c) { log.debug(x.toString()); } } // retrieve list of all groups java.util.List allxwikigroups = context.getWiki().getGroupService(context).listAllGroups(context); if(log.isDebugEnabled()) { log.debug("All defined XWiki groups: "); for(Object x : allxwikigroups) { log.debug(x.toString()); } } // go through mapped groups to locate the user for(String groupname : groupmappings.keySet()) { String xwikigroupname = groupmappings.get(groupname); // check if group is in list of all groups if(! allxwikigroups.contains(xwikigroupname)) { log.error("XWiki group not found:" + xwikigroupname); continue; } HashMap list = null; synchronized(cache) { cache.setTTL(expire); list = cache.getItem(groupname); } if(list == null) { list = new HashMap(); if(getGroupMembers(lc, groupname, uidattr, list, new ArrayList()) ) { synchronized(cache) { cache.putItem(groupname, list); } } } if(list.containsKey(user_dn)) { // add to group if not there if(!c.contains(xwikigroupname)) { log.debug("Adding user " + user + " to xwiki group " + xwikigroupname); addUserToGroup(user, xwikigroupname, context); //c.add(xwikigroupname); log.debug("Finished adding user " + user + " to xwiki group " + xwikigroupname); // is supposed to equivalent to // context.getWiki().getGroupService(context).addUserToGroup(user, context.getDatabase(), xwikigroupname, context); } } else { // remove from group if there if(c.contains(xwikigroupname)) { log.debug("Removing user " + user + " from xwiki group " + xwikigroupname); removeUserFromGroup(user, xwikigroupname, context); //c.remove(xwikigroupname); log.debug("Finished removing xwiki group " + xwikigroupname + " from user " + user); } } } /* * This may not be necessary, but the group service does have * a cache, and I've found that adding or removing entries * sometimes don't take effect if I don't do this. */ context.getWiki().getGroupService(context).flushCache(); } } } catch(Exception e) { log.error("LDAP authentication failed.", e); }finally { if(lc != null) { closeLDAP(lc); } // one last option if(result == null && context != null) { String trylocal = getParam("ldap_trylocal", context); if(trylocal.length() > 0 && "1".equals(trylocal)) { log.debug("Trying authentication against XWiki DB"); // try local user Principal principal = null; // Use XWiki password if(checkPassword(findUser(user, context), pwd, context)) { principal = GetUserPrincipal(user, context); log.debug("XWiki DB login succeeded"); } else { context.put("message", "loginfailed"); log.debug("XWiki DB login failed"); } // overwrite the result! return principal; } else { context.put("message", "loginfailed"); } } } return result; } /** * Old * @param user * @param groupName * @param context */ protected void addUserToGroup2(String user, String groupName, XWikiContext context ) { try { String wikiName = "XWiki." + user; String groupClassName = context.getWiki().getGroupClass(context).getName(); log.debug("Group object name: "+ groupClassName); // Get the XWiki document holding the objects comprising the group membership list XWikiDocument groupDoc = context.getWiki().getDocument(groupName, context); if(log.isDebugEnabled() || groupDoc != null) { log.debug("Doc: " + groupDoc.getContent()); } // Get the specific group membership object for the user (classname, key, value) BaseObject groupObj = groupDoc.getObject(groupClassName, "member", wikiName); if(groupObj == null) { log.debug("GroupObj is null, creating new."); groupObj = new BaseObject(); groupObj.setClassName(groupClassName); groupObj.setStringValue("member", wikiName); } groupDoc.addObject(groupClassName, groupObj); context.getWiki().saveDocument(groupDoc, context); } catch(Exception e) { log.error("Failed to add a user to a group " + user + " group: " + groupName, e); } } protected void addUserToGroup(String user, String groupname, XWikiContext context) { try { String fullwikiname = "XWiki." + user; BaseClass gclass = context.getWiki().getGroupClass(context); XWikiDocument allgroupdoc = context.getWiki().getDocument(groupname, context); //"XWiki.XWikiAllGroup" BaseObject memberobj = (BaseObject) gclass.newObject(context); memberobj.setClassName(gclass.getName()); memberobj.setName(allgroupdoc.getFullName()); memberobj.setStringValue("member", fullwikiname); allgroupdoc.addObject(gclass.getName(), memberobj); context.setDoc(allgroupdoc); if (allgroupdoc.isNew()) { context.getWiki().saveDocument(allgroupdoc, context); } else { context.getWiki().getHibernateStore().saveXWikiObject(memberobj, context, true); } XWikiGroupService gservice = context.getWiki().getGroupService(context); gservice.addUserToGroup(fullwikiname, context.getDatabase(), groupname, context); } catch(Exception e) { log.error("Failed to add a user to a group " + user + " group: " + groupname, e); } } protected void removeUserFromGroup(String user, String groupName, XWikiContext context ) { try { String wikiName = "XWiki." + user; String groupClassName = context.getWiki().getGroupClass(context).getName(); // Get the XWiki document holding the objects comprising the group membership list XWikiDocument groupDoc = context.getWiki().getDocument(groupName, context); // Get the specific group membership object for the user BaseObject groupObj = groupDoc.getObject(groupClassName, "member", wikiName); /* * This code imitates something I found in the XWiki code that seemed * to be necessary, but appears superfluous: get all the group membership * objects as a Vector and null the entry containing this one */ java.util.Vector objects = groupDoc.getObjects(groupClassName); objects.set(groupObj.getIntValue("id"), null); // This removes the object from the group document. groupDoc.addObjectsToRemove(groupObj); if (groupDoc.isNew()) { context.getWiki().saveDocument(groupDoc, context); } else { context.getWiki().getHibernateStore().saveXWikiObject(groupObj, context, true); } // XWikiGroupService gservice = context.getWiki().getGroupService(context); // gservice.addUserToGroup(fullwikiname, context.getDatabase(), groupname, context); } catch(Exception e) { log.error("Failed to remove a user from a group " + user + " group: " + groupName, e); } } protected SimplePrincipal checkSuperadmin(String username, String password, XWikiContext context ) { String superadmin = "superadmin"; if (username.equals(superadmin)) { String superadminpassword = context.getWiki().Param("xwiki.superadminpassword"); if ((superadminpassword != null) && (superadminpassword.equals(password))) { SimplePrincipal principal = new SimplePrincipal("XWiki.superadmin"); return principal; } else { return null; } } return null; } /* * First try to retrieve value from XWiki Preferences and then from xwiki.cfg * Syntax ldap_ (for XWiki Preferences) will be changed to ldap. for xwiki.cfg */ protected String getParam(String name, XWikiContext context) { String param = ""; try { param = context.getWiki().getXWikiPreference(name, context); } catch (Exception e) { // ignore } if (param == null || "".equals(param)) { try { param = context.getWiki().Param("xwiki.authentication." + StringUtils.replace(name, "ldap_", "ldap.")); } catch (Exception e) { // ignore } } if (param == null) param = ""; return param; } /** * Retrieve the LDAP port - helper * @param context * @return */ protected int getLDAPPort(XWikiContext context) { try { return context.getWiki().getXWikiPreferenceAsInt("ldap_port", context); } catch (Exception e) { return (int) context.getWiki().ParamAsLong("xwiki.authentication.ldap.port", 0); } } /** * Retrieve the time in s until a entry in the cache is to expire - helper * @param context * @return */ protected int getCacheExpiration(XWikiContext context) { try { return context.getWiki().getXWikiPreferenceAsInt("ldap_groupcache_expiration", context); } catch (Exception e) { return (int) context.getWiki().ParamAsLong("xwiki.authentication.ldap.groupcache_expiration", 21800); } } /** * Create a Principal for a * @param susername * @param context * @return */ protected Principal GetUserPrincipal(String susername, XWikiContext context) { Principal principal = null; // First we check in the local database String user = null; try { if (log.isDebugEnabled()) log.debug("Finding user " + susername); user = findUser(susername, context); if (user != null) { if (log.isDebugEnabled()) log.debug("Found user " + susername); principal = new SimplePrincipal(user); } } catch (Exception e) { log.debug("Failed creating a Principal for user " + user, e); } if (context.isVirtual()) { if (principal == null) { // Then we check in the main database String db = context.getDatabase(); try { context.setDatabase(context.getWiki().getDatabase()); try { user = findUser(susername, context); if (user != null) principal = new SimplePrincipal(context.getDatabase() + ":" + user); } catch (Exception e) { log.debug("Failed creating a Principal for user " + user, e); } } finally { context.setDatabase(db); } } } return principal; } /** * Sets attributes on the user object based on attribute values provided by the LDAP * @param user * @param mappings * @param attributes * @param context * @throws XWikiException */ protected void updateUserFromLDAP(String user, HashMap mappings, ArrayList attributes, XWikiContext context) throws XWikiException { BaseClass bclass = context.getWiki().getUserClass(context); String fullwikiname = "XWiki." + user; XWikiDocument doc = context.getWiki().getDocument(fullwikiname, context); BaseObject bobj = doc.getObject(bclass.getName()); for(SearchAttributes lattr : attributes){ String lval = lattr.attrValue; String xattr = mappings.get(lattr.attrName); if(xattr == null) continue; if (xattr.equals("name")) { // ignore log.debug("Ignoring a mapping for name since the name is already set to " + user); } else { if(log.isDebugEnabled()) log.debug("Setting XWiki field " + xattr + " (from LDAP attribute: " + lattr.attrName + ") Value:" + lval); bobj.setStringValue(xattr, lval); } } context.setDoc(doc); if (doc.isNew()) { context.getWiki().saveDocument(doc, context); } else { context.getWiki().getHibernateStore().saveXWikiObject(bobj, context, true); } } /** * Create an XWiki user and set all mapped attributes from LDAP to XWiki attributes * @param mappings * @param attributes * @param context * @throws XWikiException */ protected void createUserFromLDAP(String user, HashMap mappings, ArrayList attributes, XWikiContext context) throws XWikiException { HashMap map = new HashMap(); for(SearchAttributes lattr : attributes){ String lval = lattr.attrValue; String xattr = mappings.get(lattr.attrName); if(xattr == null) continue; map.put(xattr, lval); log.debug("Mapping " + xattr + " to " + lval); } // Mark user active map.put("active", "1"); log.debug("Creating User: " + user); context.getWiki().createUser(user, map, "XWiki.XWikiUsers", "#includeForm(\"XWiki.XWikiUserTemplate\")", "edit", context); /* BaseClass bclass = context.getWiki().getUserClass(context); BaseObject bobj = new BaseObject(); log.debug("Userclass objekt name: " + bclass.getName()); bobj.setClassName(bclass.getName()); String fullwikiname = "XWiki." + user; bobj.setName(fullwikiname); for(SearchAttributes lattr : attributes){ String lval = lattr.attrValue; String xattr = mappings.get(lattr.attrName); if(xattr == null) continue; if (xattr.equals("name")) { // ignore log.debug("Ignoring a mapping for name since the name is already set to " + user); } else { if(log.isDebugEnabled()) log.debug("Setting XWiki field " + xattr + " (from LDAP attribute: " + lattr.attrName + ") Value:" + lval); bobj.setStringValue(xattr, lval); } } XWikiDocument doc = context.getWiki().getDocument(fullwikiname, context); doc.setParent(""); doc.addObject(bclass.getName(), bobj); doc.setContent("#includeForm(\"XWiki.XWikiUserTemplate\")"); context.getWiki().protectUserPage(fullwikiname, "edit", doc, context); context.setDoc(doc); context.getWiki().saveDocument(doc, context); context.getWiki().setUserDefaultGroup(fullwikiname, context); log.debug("XWiki User created:" + fullwikiname); */ } /** * Locates the user in the HashMap: either the user is a value or the key starts with the LDAP syntax * @param user * @param list * @param uid * @return */ protected String findInGroup(String user, HashMap list, String uid) { String result = null; String ldapuser = null; if(uid == null || uid.length() ==0) uid = "cn"; ldapuser = uid + "=" + user.toLowerCase() + ","; for(String u : list.keySet()) { // implementing it case-insensitiv for now if(user.equalsIgnoreCase(list.get(u)) || u.toLowerCase().startsWith(ldapuser)) return u; } return result; } /** * Tries to retrieve the DN from the users object * @param susername * @param context * @return */ protected String getLDAP_DN(String susername, XWikiContext context) { String DN = null; if (context != null) { // First we check in the local database try { String user = findUser(susername, context); if (user != null && user.length() != 0) { DN = readLDAP_DN(user, context); } } catch (Exception e) { log.debug("Faild finding LDAP DN stored in the user object.",e); // ignore } if (context.isVirtual()) { if (DN == null || DN.length() == 0) { // Then we check in the main database String db = context.getDatabase(); try { context.setDatabase(context.getWiki().getDatabase()); try { String user = findUser(susername, context); if (user != null && user.length() != 0) DN = readLDAP_DN(user, context); } catch (Exception e) { log.debug("Faild finding LDAP DN stored in the user object (virtual).",e); // ignore } } finally { context.setDatabase(db); } } } } return DN; } protected String readLDAP_DN(String username, XWikiContext context) { String DN = null; try { XWikiDocument doc = context.getWiki().getDocument(username, context); // We only allow empty password from users having a XWikiUsers object. if (doc.getObject("XWiki.XWikiUsers") != null) { DN = doc.getStringValue("XWiki.XWikiUsers", "ldap_dn"); } } catch (Throwable e) { log.debug("Retrieving the ldap_dn attribute from the XWikiUsers object failed.", e); } return DN; } /* ============== LDAP LIBRARY FUNCTIONS =============== */ public LDAPConnection openLDAP(String ldapHost, int ldapPort, String loginDN, String password) { LDAPConnection lc = new LDAPConnection(); if(ldapPort==0) ldapPort = LDAPConnection.DEFAULT_PORT; int ldapVersion = LDAPConnection.LDAP_V3; try { // connect to the server lc.connect( ldapHost, ldapPort ); // authenticate to the server lc.bind( ldapVersion, loginDN, password.getBytes("UTF8") ); if(!lc.isConnected() || ! lc.isConnectionAlive() ) { log.error("Connection to LDAP failed."); return null; } if(! lc.isBound()) { log.debug("Binding to LDAP failed, - ok if Annonymous Access"); } return lc; } catch( UnsupportedEncodingException e ) { log.error( "LDAP bind failed.", e); } catch(LDAPException e) { log.error( "LDAP bind failed.", e); } return null; } public LDAPConnection openLDAPSSL(String ldapHost, int ldapPort, String loginDN, String password, String pathToKeys) { LDAPConnection lc = new LDAPConnection(); if(ldapPort==0) ldapPort = LDAPConnection.DEFAULT_SSL_PORT; int ldapVersion = LDAPConnection.LDAP_V3; try { // Dynamically set JSSE as a security provider Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider()); if(pathToKeys != null && pathToKeys.length()>0) { // Dynamically set the property that JSSE uses to identify // the keystore that holds trusted root certificates System.setProperty("javax.net.ssl.trustStore", pathToKeys); // obviously unnecessary: sun default pwd = "changeit" // System.setProperty("javax.net.ssl.trustStorePassword", sslpwd); } LDAPSocketFactory ssf = new LDAPJSSESecureSocketFactory(); // Set the socket factory as the default for all future connections // LDAPConnection.setSocketFactory(ssf); // Note: the socket factory can also be passed in as a parameter // to the constructor to set it for this connection only. lc = new LDAPConnection(ssf); // connect to the server lc.connect( ldapHost, ldapPort ); // authenticate to the server lc.bind( ldapVersion, loginDN, password.getBytes("UTF8") ); if(!lc.isConnected() || !lc.isConnectionAlive()) { log.error("Connection to LDAP through SSL failed. Beyond other causes, verify that the SSL certificate is found in the keystore."); return null; } if(! lc.isBound()) { log.debug("Binding to LDAP failed, - ok if Annonymous Access"); } return lc; } catch( UnsupportedEncodingException e ) { log.error( "LDAP bind failed.", e); } catch(LDAPException e) { log.error( "LDAP bind failed.", e); } return null; } public void closeLDAP(LDAPConnection lc) { try { lc.disconnect(); } catch(LDAPException e) { log.debug("LDAP close failed.", e); } } public boolean verifyPWD( LDAPConnection lc, String user_dn, String pwd ) { try{ LDAPAttribute attr = new LDAPAttribute("userPassword", pwd ); return lc.compare( user_dn, attr ); } catch( LDAPException e ) { if ( e.getResultCode() == LDAPException.NO_SUCH_OBJECT ) { log.error("Unable to locate user_dn:" + user_dn, e); } else if ( e.getResultCode() == LDAPException.NO_SUCH_ATTRIBUTE ) { log.error("Unable to verify password because userPassword attribute not found.", e); } else { log.error("Unable to verify password", e); } } return false; } protected class SearchAttributes { public String attrName; public String attrValue; public SearchAttributes(String name, String value) { attrName=name; attrValue=value; } } // LDAPConnection.SCOPE_SUB oder LDAPConnection.SCOPE_BASE protected ArrayList> searchLDAP(LDAPConnection lc, String baseDN, String query, String[] attr, int ldap_scope) { ArrayList> result = new ArrayList>(); try { LDAPSearchConstraints cons = new LDAPSearchConstraints(); cons.setTimeLimit( 1000 ) ; LDAPSearchResults searchResults = lc.search( baseDN, ldap_scope, query, // filter attr, // return all attributes false, // return attrs and values cons ); // time out value while(searchResults.hasMore()) { ArrayList attributes = new ArrayList(); result.add(attributes); LDAPEntry nextEntry = searchResults.next(); String foundDN = nextEntry.getDN(); attributes.add(new SearchAttributes("dn", foundDN)); LDAPAttributeSet attributeSet = nextEntry.getAttributeSet(); Iterator allAttributes = attributeSet.iterator(); while (allAttributes.hasNext()) { LDAPAttribute attribute = (LDAPAttribute) allAttributes.next(); String attributeName = attribute.getName(); Enumeration allValues = attribute.getStringValues(); if (allValues != null) { while (allValues.hasMoreElements()) { String Value = (String) allValues.nextElement(); attributes.add(new SearchAttributes(attributeName, Value)); } } } } } catch(LDAPException e) { log.debug("LDAP Search failed", e); return null; } return result; } /* * Get all members of a given group based on the groupDN. If the group contains subgroups get these members as well. * Retrive an identifier for each member * @param lc The initialized LDAP Connection * @param groupDN The group to retrieve the members of and scan for subgroups * @param uidattr The attribute containing the identifier for a user (default=cn) * @param members is the result: maps dn to member id * @param subgroups returns all the subgroubs identified * @return whether the groupDN is actually a group */ public boolean getGroupMembers( LDAPConnection lc, String groupDN, String uidattr, HashMap members, ArrayList subgroups) { // break out if there is a look of groups if(subgroups!=null && subgroups.contains(groupDN)) return true; if(uidattr == null || uidattr.length() == 0) uidattr = "cn"; String[] attrs = new String[] { "objectClass", uidattr, "member"}; ArrayList> l = searchLDAP(lc, groupDN, null, attrs, LDAPConnection.SCOPE_BASE); // Only one record should be returned if(l.size() == 0) { log.debug("Failed locating group " + groupDN); return false; } if(l.size() > 1) { log.error("More than one group found with the DN: " + groupDN); } ArrayList list = l.get(0); boolean isGroup=false; String id = null; for(SearchAttributes sa: list) { String key= sa.attrName; if (key.equalsIgnoreCase( "objectClass" ) ) { String objectName = sa.attrValue; if ( objectName.equalsIgnoreCase( "group" ) || objectName.equalsIgnoreCase( "groupOfNames" ) || objectName.equalsIgnoreCase( "groupOfUniqueNames") || objectName.equalsIgnoreCase( "dynamicGroup" ) || objectName.equalsIgnoreCase( "dynamicGroupAux" ) || objectName.equalsIgnoreCase( "groupWiseDistributionList" ) ) { isGroup = true; } } if ( key.equalsIgnoreCase( uidattr )) { id = sa.attrValue; } } if (!isGroup) { // not a group but actually a member log.debug("This object is NOT a group object: " + groupDN); // add all new members if(id==null) { log.error("Could not find attribute " + uidattr + " for LDAP dn " + groupDN); id = ""; } if(!members.containsKey(groupDN)) members.put(groupDN, id ); return false; } // remember this group if(subgroups != null) subgroups.add(groupDN); for(SearchAttributes sa: list) { String key= sa.attrName; if ( key.equalsIgnoreCase ( "member" ) || key.equalsIgnoreCase ( "uniqueMember" ) ) { String member = sa.attrValue; // or subgroup // we check for subgroups // recursiv call to scan all subgroups and identify members and their uid getGroupMembers(lc, member, uidattr, members, subgroups); } } return true; } /* =================== TEST CODE ================== */ /** * Testcode * @param args */ public static void main( String[] args ) { LDAPAuthenticater t = new LDAPAuthenticater(); LDAPConnection lc = t.openLDAP("vpc-jag-cv", 0, "", ""); boolean login = t.verifyPWD(lc, "cn=AAmes,ou=People,o=MegaNova,c=US", "pass"); login = t.verifyPWD(lc, "cn=AAmes,ou=People,o=MegaNova,c=US", "fail"); t.closeLDAP(lc); lc = t.openLDAP("dsmaster", 0, "", ""); login = t.verifyPWD(lc, "cn=EAI,department=USER,department=INFORMATIK,department=1230,o=MP", "eaieai"); HashMap members = new HashMap(); ArrayList subgroups = new ArrayList(); boolean isGroup = t.getGroupMembers(lc, "cn=IT-SWL,department=Adm,department=INFORMATIK,department=1230,o=MP", "cn", members, subgroups); t.closeLDAP(lc); } public void runtimeTest(XWikiContext context) { HashMap map = new HashMap(); // Mark user active try { map.put("Active", "1"); log.debug("create user 1"); int x = context.getWiki().createUser("asdf", map, "XWiki.XWikiUsers", "#includeForm(\"XWiki.XWikiUserTemplate\")", "edit", context); /* map.put("ldap_dn", "cn=GLEEB,department=USER,department=INFORMATIK,department=1230,o=MP"); map.put("last_name", "Leeb"); map.put("email", "Gunter.Leeb@mediaprint.at"); map.put("first_name", "Gunter"); map.put("fullname", "Gunter Leeb"); log.debug("create user 2 " + x); x = context.getWiki().createUser("asdf1", map, "XWiki.XWikiUsers", "#includeForm(\"XWiki.XWikiUserTemplate\")", "edit", context); log.debug("create user 3 " + x); x = context.getWiki().createUser("gleeb", map, "XWiki.XWikiUsers", "#includeForm(\"XWiki.XWikiUserTemplate\")", "edit", context); log.debug("create user done " + x); */ log.debug("Testcode succeeded - remove rc3 flag in xwiki.cfg"); } catch(Exception e) { log.debug("Testcode failed - ignore exception", e); } } }