osuser
  1. osuser
  2. OSUSER-7

LDAP credential provider - solution

    Details

    • Type: Task Task
    • Status: Open
    • Priority: Major Major
    • Resolution: Unresolved
    • Affects Version/s: current
    • Fix Version/s: milestone 1
    • Component/s: www
    • Labels:
      None
    • Environment:

      Operating System: All
      Platform: All

    • Issuezilla Id:
      7

      Description

      Damn this issue tracker (or its config). I cannot neither add attachment nor
      edit/comment... Let any admin cleans it up if necessary.

      Here is a solution for https://osuser.dev.java.net/issues/show_bug.cgi?id=6 in a
      form of listing.

      andrew michalec.

      =========================

      /*

      • Copyright (c) 2002-2003 by OpenSymphony
      • All rights reserved.
        */
        package com.opensymphony.user.provider.ldap;

      import com.opensymphony.user.Entity;
      import com.opensymphony.user.UserManager;
      import com.opensymphony.user.provider.CredentialsProvider;

      import org.apache.commons.logging.Log;
      import org.apache.commons.logging.LogFactory;

      import java.util.*;

      import javax.naming.AuthenticationException;
      import javax.naming.CommunicationException;
      import javax.naming.Context;
      import javax.naming.NamingEnumeration;
      import javax.naming.NamingException;
      import javax.naming.directory.*;

      /**

      • Provider for checking credentials against a LDAP directory.
      • <p>
      • Tries to connect to an LDAP directory with the specified username/password.
        Succeeds or fails depending on whether the
      • LDAP authentication succeeds/fails.
      • <p>
      • The authentication algorithm is as follows:
      • <ul>
      • <li>Establish an anonymous or authenticated connection with LDAP
      • <li>Search within a subtree for a node representing the user, eg. search
        below 'ou=People,dc=example,dc=com' for (uid=fred) if the username is 'fred'.
      • <li>If a user node is found (eg. 'uid=fred,ou=People,dc=example,dc=com'),
        try to connect to LDAP again, using the
      • found node as the 'bind DN', and using the user's password.
      • <li>If this second connection succeeds, return true
      • </ul>
      • <p>A sample osuser.xml configuration:
      • <pre>
      • <provider class="com.opensymphony.user.provider.ldap.LDAPCredentialsProvider">
        <property
        name="java.naming.factory.initial">com.sun.jndi.ldap.LdapCtxFactory</property>
        <property name="java.naming.provider.url">ldap://localhost:389</property>
        <property name="searchBase">ou=People,dc=example,dc=com</property>
        <property name="uidSearchName">uid</property>
        <property
        name="java.naming.security.principal">cn=admin,dc=example,dc=com</property>
        <property name="java.naming.security.credentials">secret</property>
        <property name="exclusive-access">true</property>
        </provider>
      • </pre>
      • The security principal and credentials lines are optional, depending on
        whether your initial connection need be authenticated or not.
      • <p>
      • Currently, there must be <strong>at least one other non-LDAP
        CredentialsProvider</strong> configured for this provider to work.
      • This is because there are user management features that
        LDAPCredentialsProvider does not provide on its own (create/delete user, change
        password).
      • When calls to these methods are made, LDAPCredentialsProvider delegates the
        call to the other CredentialsProvider implementation.
      • <p>
      • Notes:
      • <ul>
      • <li>Entering blank password will always fail, regardless of whether the
        underlying LDAP allows anonymous user connects.
      • <li>If the initial LDAP connection cannot be established, or there is an
        unexpected error, the authentication attempt
      • is passed on to other non-LDAP CredentialsProvider.
      • <li>If the user exists in LDAP but the password was incorrect, the module
        fails without consulting other CredentialsProviders.
      • <li>Turning logging up to DEBUG will reveal details on authentication attempts.
      • </ul>
        *
      • @author <a href="mailto:jeff@atlassian.com">Jeff Turner</a>
        */
        public class LDAPCredentialsProvider implements CredentialsProvider {
        //~ Static fields/initializers /////////////////////////////////////////////

      private static final Log log = LogFactory.getLog(LDAPCredentialsProvider.class);
      static private HashMap cache = new HashMap();

      //~ Instance fields ////////////////////////////////////////////////////////

      private Hashtable env;
      private String searchBase;
      private String uidSearchName;
      private long timeout;

      //~ Methods ////////////////////////////////////////////////////////////////

      public boolean authenticate(String name, String password) {
      // Do NOT allow null or empty passwords
      // This is required as LDAP (by default) allows an empty password for an
      existing user
      // (if the password is not specified LDAP allows the user to connect,
      and treats the user as
      // an unauthenticated - anonymous user)
      if ((password == null) || "".equals(password))

      { return false; }

      // check cache
      TimeAndPassword tp = (TimeAndPassword) cache.get(name);

      if ((tp != null) && tp.password.equals(password) && (tp.time >
      System.currentTimeMillis())) {
      if (log.isDebugEnabled())

      { log.debug("Successful authentication for " + name + " from cached LDAP lookup"); }

      return true;
      }

      DirContext ctx = null;

      try

      { ctx = new InitialDirContext(env); }

      catch (NamingException e)

      { log.error("Could not connect to LDAP. Please check your " + "host ('" + env.get(Context.PROVIDER_URL) + "'), " + "bind DN ('" + env.get(Context.SECURITY_PRINCIPAL) + "') and bind password.", e); return tryOtherCredentialsProviders(name, password); }

      StringBuffer filterBuffer = new
      StringBuffer(uidSearchName).append("=").append(name);

      String[] attrIDs =

      {uidSearchName}

      ;
      SearchControls ctls = new SearchControls();
      ctls.setReturningAttributes(attrIDs);
      ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);

      if (log.isDebugEnabled())

      { log.debug("Doing initial search: " + "username='" + env.get(Context.SECURITY_PRINCIPAL) + "', password='" + env.get(Context.SECURITY_CREDENTIALS) + "', base='" + searchBase + "', filter='" + filterBuffer + "'"); }

      NamingEnumeration results = null;

      try

      { results = ctx.search(searchBase, filterBuffer.toString(), ctls); }

      catch (NamingException e)

      { log.error("Connected to LDAP, but could not perform " + (env.containsKey(Context.SECURITY_PRINCIPAL) ? "authenticated" : "anonymous") + " search from base '" + searchBase + "'"); return tryOtherCredentialsProviders(name, password); }

      StringBuffer dnBuffer = new StringBuffer();

      try {
      if (log.isDebugEnabled()) {
      if ((results != null) && results.hasMore())

      { log.debug("Found users"); }

      else

      { log.debug("No users found"); }

      }

      // Lotus Domino R5 LDAP server drops socket connection when
      // switching credentials! First context (ctx) is used to iterate
      // through results and only second context (ctx2) is used
      // to authorize. Context ctx will survive whether ctx2 is dropped
      // by Domino or not.
      DirContext ctx2 = new InitialDirContext(ctx.getEnvironment());

      while ((results != null) && results.hasMore()) {
      SearchResult sr = (SearchResult) results.next();
      dnBuffer = new StringBuffer();
      dnBuffer.append(sr.getName());
      dnBuffer.append(",");
      dnBuffer.append(searchBase);

      try

      { ctx2.removeFromEnvironment(Context.SECURITY_PRINCIPAL); ctx2.removeFromEnvironment(Context.SECURITY_CREDENTIALS); ctx2.addToEnvironment(Context.SECURITY_PRINCIPAL, dnBuffer.toString()); ctx2.addToEnvironment(Context.SECURITY_CREDENTIALS, password); }

      catch (NamingException e)

      { log.error("Connected and searched LDAP, but encountered unexpected error when switching authentication details.", e); continue; }

      ctls = new SearchControls();
      ctls.setReturningAttributes(new String[0]);
      ctls.setSearchScope(SearchControls.OBJECT_SCOPE);

      if (log.isDebugEnabled())

      { log.debug("Searching below '" + dnBuffer + "' for '" + filterBuffer + "'"); }

      try {
      try

      { ctx2.search(dnBuffer.toString(), filterBuffer.toString(), ctls); }

      catch (CommunicationException ex)

      { log.info("Second phase connection failed. Trying to reconnect to bypass sort of Lotus Domino R5 problems."); // Second context (ctx2) lost; we recreate this context and try once more. ctx2 = new InitialDirContext(ctx2.getEnvironment()); ctx2.search(dnBuffer.toString(), filterBuffer.toString(), ctls); }

      } catch (AuthenticationException ae) {
      if (log.isDebugEnabled())

      { log.debug("User with dn '" + dnBuffer + "' found, but authentication failed."); }

      continue;
      } catch (NamingException e)

      { log.error("Initial connect and search successful, but second phase connection to LDAP as '" + dnBuffer + "' failed.", e); continue; }

      if (log.isDebugEnabled())

      { log.debug("User '" + name + "' successfully authenticated; caching for " + timeout + " ms"); }

      cache.put(name, new TimeAndPassword(System.currentTimeMillis() +
      timeout, password));

      return true;
      }
      } catch (NamingException e)

      { log.error("Connected but encountered error checking if LDAP had more results.", e); }

      return tryOtherCredentialsProviders(name, password);
      }

      public boolean changePassword(String name, String password) {
      // LDAP cannot change passwords, so look for the first
      CredentialsProvider that can and wrap the call
      Collection credentialsProviders =
      UserManager.getInstance().getCredentialsProviders();

      for (Iterator iterator = credentialsProviders.iterator();
      iterator.hasNext() {
      CredentialsProvider provider = (CredentialsProvider) iterator.next();
      boolean isLDAP = provider instanceof LDAPCredentialsProvider;

      if (!isLDAP) {
      if (provider.handles(name))

      { return provider.changePassword(name, password); }

      }
      }

      return false;
      }

      public boolean create(String name)

      { // LDAP doesn't support this feature, so instead call the first provider that does return false; }

      public void flushCaches()

      { // flush the TimeAndPassword cache cache = new HashMap(); }

      public boolean handles(String name) {
      // check cache
      TimeAndPassword tp = (TimeAndPassword) cache.get(name);

      if ((tp != null) && (tp.time > System.currentTimeMillis())) {
      if (log.isDebugEnabled())

      { log.debug("Cached lookup: Credentials for '" + name + "' will be handled by LDAP provider"); }

      return true;
      }

      // check other credentials providers, don't handle this name if at least
      one other one doesn't
      Collection credentialsProviders =
      UserManager.getInstance().getCredentialsProviders();
      boolean handles = false;

      for (Iterator iterator = credentialsProviders.iterator();
      iterator.hasNext() {
      CredentialsProvider provider = (CredentialsProvider) iterator.next();
      boolean isLDAP = provider instanceof LDAPCredentialsProvider;

      if (!isLDAP) {
      if (provider.handles(name)) {
      if (log.isDebugEnabled())

      { log.debug("'" + name + "' will be handled by LDAP"); }

      handles = true;

      break;
      }
      }
      }

      if (log.isDebugEnabled()) {
      if (handles == false)

      { log.debug("Credentials for '" + name + "' NOT handled by LDAP, because '" + name + "' not handled by any other credentials provider. Check you have at least one other" + " credentials provider, and that they contain this user."); }

      }

      return handles;
      }

      public boolean init(Properties properties) {
      if (log.isDebugEnabled())

      { log.debug("LDAPCredentialsProvider $Revision: 1.3 $ initializing"); }

      env = new Hashtable(properties);
      env.put(javax.naming.Context.SECURITY_AUTHENTICATION, "simple");
      searchBase = properties.getProperty("searchBase");
      uidSearchName = properties.getProperty("uidSearchName");

      try

      { timeout = Long.parseLong(properties.getProperty("cacheTimeout")); }

      catch (NumberFormatException e)

      { timeout = 1000 * 60 * 30; // 30 minutes default time }

      return true;
      }

      public List list()

      { // LDAP cannot list users return Collections.EMPTY_LIST; }

      public boolean load(String name, Entity.Accessor accessor) {
      // LDAP doesn't support this feature, so instead call the first provider
      that does
      Collection credentialsProviders =
      UserManager.getInstance().getCredentialsProviders();

      for (Iterator iterator = credentialsProviders.iterator();
      iterator.hasNext() {
      CredentialsProvider provider = (CredentialsProvider) iterator.next();
      boolean isLDAP = provider instanceof LDAPCredentialsProvider;

      if (!isLDAP) {
      if (provider.handles(name))

      { return provider.load(name, accessor); }

      }
      }

      return true;
      }

      public boolean remove(String name) {
      // LDAP cannot support removing users
      Collection credentialsProviders =
      UserManager.getInstance().getCredentialsProviders();

      for (Iterator iterator = credentialsProviders.iterator();
      iterator.hasNext() {
      CredentialsProvider provider = (CredentialsProvider) iterator.next();
      boolean isLDAP = provider instanceof LDAPCredentialsProvider;

      if (!isLDAP) {
      if (provider.handles(name))

      { return provider.remove(name); }

      }
      }

      return false;
      }

      public boolean store(String name, Entity.Accessor accessor) {
      // LDAP doesn't support this feature, so instead call the first provider
      that does
      Collection credentialsProviders =
      UserManager.getInstance().getCredentialsProviders();

      for (Iterator iterator = credentialsProviders.iterator();
      iterator.hasNext() {
      CredentialsProvider provider = (CredentialsProvider) iterator.next();
      boolean isLDAP = provider instanceof LDAPCredentialsProvider;

      if (!isLDAP) {
      if (provider.handles(name))

      { return provider.store(name, accessor); }

      }
      }

      return true;
      }

      /** Loop through non-LDAP credentials providers and tries to authenticate
      against them. */
      private boolean tryOtherCredentialsProviders(String name, String password) {
      if (log.isDebugEnabled())

      { log.debug("Couldn't authenticate against LDAP server, trying other CredentialsProviders"); }

      Collection credentialsProviders =
      UserManager.getInstance().getCredentialsProviders();

      for (Iterator iterator = credentialsProviders.iterator();
      iterator.hasNext() {
      CredentialsProvider provider = (CredentialsProvider) iterator.next();
      boolean isLDAP = provider instanceof LDAPCredentialsProvider;

      if (!isLDAP) {
      if (provider.handles(name)) {
      if (log.isDebugEnabled())

      { log.debug("Provider '" + provider.getClass().getName() + "' handles user; checking authentication..."); }

      boolean result = provider.authenticate(name, password);

      if (result) {
      if (log.isDebugEnabled())

      { log.debug("User authenticated by '" + provider.getClass().getName() + "'"); }

      cache.put(name, new
      TimeAndPassword(System.currentTimeMillis() + timeout, password));

      return true;
      } else {
      if (log.isDebugEnabled())

      { log.debug("Provider '" + provider.getClass().getName() + "' failed to authenticate user."); }

      return false;
      }
      }
      }
      }

      if (log.isDebugEnabled())

      { log.debug("No non-LDAP authenticators could authenticate this user"); }

      return false;
      }

      //~ Inner Classes //////////////////////////////////////////////////////////

      private class TimeAndPassword {
      public String password;
      public long time;

      public TimeAndPassword(long time, String password)

      { this.time = time; this.password = password; }

      }
      }

        Activity

        amichalec created issue -
        kenaiadmin made changes -
        Field Original Value New Value
        issue.field.bugzillaimportkey 7 92157

          People

          • Assignee:
            osuser-issues
            Reporter:
            amichalec
          • Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

            Dates

            • Created:
              Updated: