Index: src/main/java/com/xpn/xwiki/plugin/ldap/XWikiLDAPConnection.java =================================================================== --- src/main/java/com/xpn/xwiki/plugin/ldap/XWikiLDAPConnection.java (revision 11693) +++ src/main/java/com/xpn/xwiki/plugin/ldap/XWikiLDAPConnection.java (working copy) @@ -21,28 +21,27 @@ package com.xpn.xwiki.plugin.ldap; +import java.security.Security; import java.text.MessageFormat; import java.util.ArrayList; -import java.util.Enumeration; +import java.util.Hashtable; import java.util.List; -import java.io.UnsupportedEncodingException; +import javax.naming.Context; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import com.novell.ldap.LDAPAttribute; -import com.novell.ldap.LDAPAttributeSet; import com.novell.ldap.LDAPConnection; -import com.novell.ldap.LDAPEntry; -import com.novell.ldap.LDAPException; -import com.novell.ldap.LDAPJSSESecureSocketFactory; -import com.novell.ldap.LDAPSearchConstraints; -import com.novell.ldap.LDAPSearchResults; -import com.novell.ldap.LDAPSocketFactory; import com.xpn.xwiki.XWikiContext; -import java.security.Security; - /** * LDAP communication tool. * @@ -59,26 +58,26 @@ /** * The LDAP connection. */ - private LDAPConnection connection; + private DirContext connection; /** * @return the {@link LDAPConnection}. */ - public LDAPConnection getConnection() + public DirContext getConnection() { return connection; } /** - * Open a LDAP connection. + * Open a general LDAP connection. * - * @param ldapUserName the user name to connect to LDAP server. - * @param password the password to connect to LDAP server. + * @param bindDN the dn to connect to LDAP server. + * @param bindPassword the password to connect to LDAP server. * @param context the XWiki context. * @return true if connection succeed, false otherwise. * @throws XWikiLDAPException error when trying to open connection. */ - public boolean open(String ldapUserName, String password, XWikiContext context) + public boolean open(String bindDN, String bindPassword, XWikiContext context) throws XWikiLDAPException { XWikiLDAPConfig config = XWikiLDAPConfig.getInstance(); @@ -86,15 +85,7 @@ // open LDAP int ldapPort = config.getLDAPPort(context); String ldapHost = config.getLDAPParam("ldap_server", "localhost", context); - String bindDNFormat = config.getLDAPParam("ldap_bind_DN", "{0}", context); - String bindPasswordFormat = config.getLDAPParam("ldap_bind_pass", "{1}", context); - Object[] arguments = {ldapUserName, password}; - - // 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); - boolean bind; if ("1".equals(config.getLDAPParam("ldap_ssl", "0", context))) { String keyStore = config.getLDAPParam("ldap_ssl.keystore", "", context); @@ -135,7 +126,12 @@ port = ssl ? LDAPConnection.DEFAULT_SSL_PORT : LDAPConnection.DEFAULT_PORT; } - int ldapVersion = LDAPConnection.LDAP_V3; +// int ldapVersion = LDAPConnection.LDAP_V3; + // Set up the environment for creating the initial context + Hashtable env = new Hashtable(); + env.put(Context.INITIAL_CONTEXT_FACTORY, + "com.sun.jndi.ldap.LdapCtxFactory"); + env.put(Context.PROVIDER_URL, "ldap://" + ldapHost + ":" + port + "/"); try { if (ssl) { @@ -144,6 +140,9 @@ // Dynamically set JSSE as a security provider Security.addProvider(config.getSecureProvider(context)); + // Specify SSL + env.put(Context.SECURITY_PROTOCOL, "ssl"); + if (pathToKeys != null && pathToKeys.length() > 0) { // Dynamically set the property that JSSE uses to identify // the keystore that holds trusted root certificates @@ -153,30 +152,24 @@ // 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. - this.connection = new LDAPConnection(ssf); - } else { - this.connection = new LDAPConnection(); } + if (null != loginDN && loginDN.length() > 0) { + env.put(Context.SECURITY_AUTHENTICATION, "simple"); + env.put(Context.SECURITY_PRINCIPAL, loginDN); + env.put(Context.SECURITY_CREDENTIALS, password); + } + else { + env.put(Context.SECURITY_AUTHENTICATION, "none"); + } + // connect to the server - this.connection.connect(ldapHost, port); - // authenticate to the server - this.connection.bind(ldapVersion, loginDN, password.getBytes("UTF8")); - - succeed = - this.connection.isConnected() && this.connection.isConnectionAlive() - && this.connection.isBound(); - } catch (UnsupportedEncodingException e) { - throw new XWikiLDAPException("LDAP bind failed with UnsupportedEncodingException.", e); - } catch (LDAPException e) { + this.connection = new InitialDirContext(env); + succeed = true; + } catch (NamingException e) { throw new XWikiLDAPException("LDAP bind failed with LDAPException.", e); } @@ -190,9 +183,9 @@ { try { if (this.connection != null) { - this.connection.disconnect(); + this.connection.close(); } - } catch (LDAPException e) { + } catch (NamingException e) { if (LOG.isDebugEnabled()) { LOG.debug("LDAP close failed.", e); } @@ -221,27 +214,12 @@ */ public boolean checkPassword(String userDN, String password, String passwordField) { - try { - LDAPAttribute attribute = new LDAPAttribute(passwordField, password); - return this.connection.compare(userDN, attribute); - } catch (LDAPException e) { - if (e.getResultCode() == LDAPException.NO_SUCH_OBJECT) { - if (LOG.isDebugEnabled()) { - LOG.debug("Unable to locate user_dn:" + userDN, e); - } - } else if (e.getResultCode() == LDAPException.NO_SUCH_ATTRIBUTE) { - if (LOG.isDebugEnabled()) { - LOG.debug( - "Unable to verify password because userPassword attribute not found.", e); - } - } else { - if (LOG.isDebugEnabled()) { - LOG.debug("Unable to verify password", e); - } - } - } - - return false; + String query = + MessageFormat.format("({0}={1})", + new Object[] {passwordField, password}); + + List res = searchLDAP(userDN, query, new String[]{"dn"}, SearchControls.OBJECT_SCOPE); + return !res.isEmpty(); } /** @@ -250,7 +228,8 @@ * @param baseDN the root DN where to search. * @param query the LDAP query. * @param attr the attributes names of values to return. - * @param ldapScope {@link LDAPConnection#SCOPE_SUB} oder {@link LDAPConnection#SCOPE_BASE}. + * @param ldapScope {@link SearchControls#SUBTREE_SCOPE}, {@link SearchControls#OBJECT_SCOPE} or + * {@link SearchControls#ONELEVEL_SCOPE}. * @return the found LDAP attributes. */ public List searchLDAP(String baseDN, String query, String[] attr, @@ -260,42 +239,37 @@ new ArrayList(); try { - LDAPSearchConstraints cons = new LDAPSearchConstraints(); - cons.setTimeLimit(1000); - + LOG.debug("Search for " + query + " in " + baseDN); + SearchControls scons = new SearchControls(ldapScope, 1000, 0, attr, true, true); // filter return all attributes return attrs and values time out value - LDAPSearchResults searchResults = - this.connection.search(baseDN, ldapScope, query, attr, false, cons); + NamingEnumeration searchResults = + this.connection.search(baseDN, query, scons); if (!searchResults.hasMore()) { + LOG.debug("Noting found"); return null; } - LDAPEntry nextEntry = searchResults.next(); - String foundDN = nextEntry.getDN(); + SearchResult nextEntry = searchResults.next(); + String foundDN = nextEntry.getNameInNamespace(); searchAttributeList.add(new XWikiLDAPSearchAttribute("dn", foundDN)); - LDAPAttributeSet attributeSet = nextEntry.getAttributeSet(); + NamingEnumeration attributeSet = nextEntry.getAttributes().getAll(); - for (Object attributeItem : attributeSet) { - LDAPAttribute attribute = (LDAPAttribute) attributeItem; - String attributeName = attribute.getName(); - - Enumeration allValues = attribute.getStringValues(); - - if (allValues != null) { - while (allValues.hasMoreElements()) { - - String value = (String) allValues.nextElement(); - searchAttributeList - .add(new XWikiLDAPSearchAttribute(attributeName, value)); - } + while (attributeSet.hasMoreElements()) { + Attribute attributeItem = attributeSet.nextElement(); + String attributeName = attributeItem.getID(); + LOG.debug(attributeName + ": Count " + attributeItem.size()); + NamingEnumeration allValues = attributeItem.getAll(); + while (allValues.hasMoreElements()) { + String value = (String) allValues.nextElement(); + LOG.debug(attributeName + ": " + value); + searchAttributeList + .add(new XWikiLDAPSearchAttribute(attributeName, value)); } } - } catch (LDAPException e) { - if (LOG.isDebugEnabled()) { - LOG.debug("LDAP Search failed", e); - } + } catch (NamingException e) { + LOG.warn("LDAP Search failed", e); } return searchAttributeList; Index: src/main/java/com/xpn/xwiki/plugin/ldap/XWikiLDAPUtils.java =================================================================== --- src/main/java/com/xpn/xwiki/plugin/ldap/XWikiLDAPUtils.java (revision 11693) +++ src/main/java/com/xpn/xwiki/plugin/ldap/XWikiLDAPUtils.java (working copy) @@ -21,12 +21,16 @@ package com.xpn.xwiki.plugin.ldap; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.naming.NamingException; +import javax.naming.directory.SearchControls; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.xwiki.cache.Cache; @@ -34,9 +38,10 @@ import org.xwiki.cache.config.CacheConfiguration; import org.xwiki.cache.eviction.LRUEvictionConfiguration; -import com.novell.ldap.LDAPConnection; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; +import com.xpn.xwiki.plugin.ldap.XWikiLDAPConfig; +import com.xpn.xwiki.plugin.ldap.XWikiLDAPSearchAttribute; /** * LDAP communication tool. @@ -52,6 +57,11 @@ private static final Log LOG = LogFactory.getLog(XWikiLDAPUtils.class); /** + * LDAP dn parameter. + */ + private static final String LDAP_DN = "dn"; + + /** * LDAP objectClass parameter. */ private static final String LDAP_OBJECTCLASS = "objectClass"; @@ -108,6 +118,29 @@ } /** + * Use posixGroup uid -> dn mapping + */ + private boolean usePosixGroupMapping = false; + + /** + * If posix mapping should be used, return true + * + * @return the usePosixGroupMapping + */ + public boolean isUsePosixGroupMapping() { + return usePosixGroupMapping; + } + + /** + * Set the config variable to enable uid->dn mapping + * + * @param usePosixGroupMapping the usePosixGroupMapping to set + */ + public void setUsePosixGroupMapping(boolean usePosixGroupMapping) { + this.usePosixGroupMapping = usePosixGroupMapping; + } + + /** * @param uidAttributeName the LDAP attribute containing the identifier for a user. */ public void setUidAttributeName(String uidAttributeName) @@ -167,11 +200,17 @@ throws CacheException { Cache> cache; + String cacheKey ; + + try { + cacheKey = + getUidAttributeName() + "." + connection.getConnection().getNameInNamespace(); + // getHost() + ":" + connection.getConnection().getPort();^ + } + catch(NamingException e) { + cacheKey = getUidAttributeName() + "." + connection.getConnection().toString(); + } - String cacheKey = - getUidAttributeName() + "." + connection.getConnection().getHost() + ":" - + connection.getConnection().getPort(); - Map>> cacheMap; if (cachePool.containsKey(cacheKey)) { @@ -216,7 +255,7 @@ attrs[i++] = groupMember; } - return getConnection().searchLDAP(groupDN, null, attrs, LDAPConnection.SCOPE_BASE); + return getConnection().searchLDAP(groupDN, "(objectClass=*)", attrs, SearchControls.SUBTREE_SCOPE); } /** @@ -318,6 +357,26 @@ List searchAttributeList = searchGroupsMembers(groupDN); if (searchAttributeList != null) { + if (isUsePosixGroupMapping()) { + // Convert short userId in full dn + // There should be a config option to enable this code + // It is useful when your ldap groups doesn't contain full dn + for (XWikiLDAPSearchAttribute searchAttribute : searchAttributeList) { + + String key = searchAttribute.name; + if (getGroupMemberFields().contains(key.toLowerCase())) { + + String userId = searchAttribute.value; + + // Replace the userid by the dn + searchAttribute.value = getUserDN(userId, context, "uid"); + + if (LOG.isDebugEnabled()) { + LOG.debug("Found user id : " + userId + " in dn : " + searchAttribute.value); + } + } + } + } isGroup = getGroupMembers(groupDN, memberMap, subgroups, searchAttributeList, context); } @@ -444,4 +503,56 @@ return userDN; } + + /** + * Get dn from user id. + * + * @param userId the short user id + * @param context the XWiki context. + * @return the user dn, null if not found + */ + public String getUserDN(String userId, XWikiContext context) + { + return getUserDN (userId, context, getUidAttributeName()); + } + + /** + * Get dn from user id. + * + * @param userId the short user id + * @param context the XWiki context. + * @param uidSymbol symbol + * @return the user dn, null if not found + */ + public String getUserDN(String userId, XWikiContext context, String uidSymbol) + { + XWikiLDAPConfig config = XWikiLDAPConfig.getInstance(); + + // search for the user in LDAP + String query = + MessageFormat.format("({0}={1})", + new Object[] {uidSymbol, userId}); + + String baseDN = config.getLDAPParam("ldap_base_DN", "", context); + + if (LOG.isDebugEnabled()) { + LOG.debug("Searching for the user in LDAP: user:" + userId + + " base:" + baseDN + " query:" + query + " uid:" + uidSymbol); + } + + String[] attributeNameTable = {LDAP_DN}; + + List searchAttributes = null; + + searchAttributes = getConnection().searchLDAP(baseDN, query, attributeNameTable, + SearchControls.SUBTREE_SCOPE); + + for (XWikiLDAPSearchAttribute searchAttribute : searchAttributes) { + if (LDAP_DN.equals(searchAttribute.name)) { + return searchAttribute.value; + } + } + return null; + } + } Index: src/main/java/com/xpn/xwiki/user/impl/LDAP/XWikiLDAPAuthServiceImpl.java =================================================================== --- src/main/java/com/xpn/xwiki/user/impl/LDAP/XWikiLDAPAuthServiceImpl.java (revision 11693) +++ src/main/java/com/xpn/xwiki/user/impl/LDAP/XWikiLDAPAuthServiceImpl.java (working copy) @@ -21,18 +21,22 @@ package com.xpn.xwiki.user.impl.LDAP; +import java.io.UnsupportedEncodingException; +import java.security.Principal; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import javax.naming.directory.SearchControls; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.securityfilter.realm.SimplePrincipal; -import com.novell.ldap.LDAPConnection; import com.novell.ldap.LDAPException; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; @@ -45,10 +49,6 @@ import com.xpn.xwiki.plugin.ldap.XWikiLDAPUtils; import com.xpn.xwiki.user.impl.xwiki.XWikiAuthServiceImpl; -import java.io.UnsupportedEncodingException; -import java.security.Principal; -import java.util.HashMap; - /** * This class provides an authentication method that validates a user trough LDAP against a directory. It gives LDAP * users access if they belong to a particular group, creates XWiki users if they have never logged in before and @@ -146,7 +146,8 @@ */ private String getValidXWikiUserName(String name) { - return name.replace(XWIKI_SPACE_NAME_SEP, ""); + String xwikiName = name.replace(" ", ""); + return xwikiName.replace(XWIKI_SPACE_NAME_SEP, ""); } /** @@ -243,7 +244,7 @@ * @throws LDAPException error when login. */ protected Principal ldapAuthenticateInContext(String ldapUserName, String validXWikiUserName, String password, - XWikiContext context) throws XWikiException, UnsupportedEncodingException, LDAPException + XWikiContext context) throws XWikiException, UnsupportedEncodingException { Principal principal = null; @@ -255,6 +256,8 @@ ldapUtils.setUidAttributeName(config.getLDAPParam(XWikiLDAPConfig.PREF_LDAP_UID, LDAP_DEFAULT_UID, context)); ldapUtils.setGroupClasses(config.getGroupClasses(context)); ldapUtils.setGroupMemberFields(config.getGroupMemberFields(context)); + String usePosixGroups = config.getLDAPParam("ldap_use_posix_group", "0", context); + ldapUtils.setUsePosixGroupMapping(usePosixGroups != null && usePosixGroups.equals("1")); // //////////////////////////////////////////////////////////////////// // 1. check if ldap authentication is off => authenticate against db @@ -262,7 +265,7 @@ if (!config.isLDAPEnabled(context)) { if (LOG.isDebugEnabled()) { - LOG.debug("LDAP authentication failed: LDAP not activ"); + LOG.debug("LDAP authentication failed: LDAP not active"); } return principal; @@ -272,7 +275,16 @@ // 2. bind to LDAP => if failed try db // //////////////////////////////////////////////////////////////////// - if (!connector.open(ldapUserName, password, context)) { + String bindDNFormat = config.getLDAPParam("ldap_bind_DN", "", context); + String bindPasswordFormat = config.getLDAPParam("ldap_bind_pass", "", context); + + Object[] arguments = {ldapUserName, password}; + + // 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 (!connector.open(bindDN, bindPassword, context)) { throw new XWikiException(XWikiException.MODULE_XWIKI_USER, XWikiException.ERROR_XWIKI_USER_INIT, "Bind to LDAP server failed."); } @@ -346,7 +358,7 @@ } searchAttributes = - connector.searchLDAP(baseDN, query, getAttributeNameTable(context), LDAPConnection.SCOPE_SUB); + connector.searchLDAP(baseDN, query, getAttributeNameTable(context), SearchControls.SUBTREE_SCOPE); for (XWikiLDAPSearchAttribute searchAttribute : searchAttributes) { if ("dn".equals(searchAttribute.name)) { @@ -367,18 +379,21 @@ // authenticated try to bind // //////////////////////////////////////////////////////////////////// + bindDNFormat = config.getLDAPParam("ldap_bind_DN", "{0}", context); + bindDN = MessageFormat.format(bindDNFormat, new Object[] {userDN}); + if ("1".equals(config.getLDAPParam("ldap_validate_password", "0", context))) { - String passwordField = config.getLDAPParam("ldap_password_field", "userPassword", context); - if (!connector.checkPassword(userDN, password, passwordField)) { + if (!connector.open(bindDN, password, context)) { throw new XWikiException(XWikiException.MODULE_XWIKI_USER, XWikiException.ERROR_XWIKI_USER_INIT, - "LDAP authentication failed:" + " could not validate the password: wrong password for " + userDN); + "Bind to LDAP server failed."); } } else { - String bindDNFormat = config.getLDAPParam("ldap_bind_DN", "{0}", context); - String bindDN = MessageFormat.format(bindDNFormat, new Object[] {ldapUserName}); - + // check if bindDN is equal to userDN, if not, check password as well if (!userDN.equals(bindDN)) { - connector.getConnection().bind(LDAPConnection.LDAP_V3, userDN, password.getBytes("UTF8")); + if (!connector.open(userDN, password, context)) { + throw new XWikiException(XWikiException.MODULE_XWIKI_USER, XWikiException.ERROR_XWIKI_USER_INIT, + "Bind to LDAP server failed."); + } } } @@ -461,8 +476,8 @@ if (searchAttributeList == null) { // didn't get attributes before, so do it now searchAttributeList = - ldapUtils.getConnection().searchLDAP(userDN, null, getAttributeNameTable(context), - LDAPConnection.SCOPE_BASE); + ldapUtils.getConnection().searchLDAP(userDN, "(objectClass=*)", getAttributeNameTable(context), + SearchControls.SUBTREE_SCOPE); } if (createuser) {