Commits

Sebastian Sdorra committed 5c56a1a

added support for trusted domains, see #28

  • Participants
  • Parent commits ad6e280

Comments (0)

Files changed (3)

File plugins/scm-activedirectory-auth-plugin/src/main/java/sonia/scm/activedirectory/auth/ActiveDirectoryAuthenticationHandler.java

 import com4j.typelibs.activeDirectory.IADsOpenDSObject;
 import com4j.typelibs.activeDirectory.IADsUser;
 import com4j.typelibs.ado20.ClassFactory;
+import com4j.typelibs.ado20.Field;
+import com4j.typelibs.ado20.Fields;
 import com4j.typelibs.ado20._Command;
 import com4j.typelibs.ado20._Connection;
 import com4j.typelibs.ado20._Recordset;
 import sonia.scm.user.User;
 import sonia.scm.util.AssertUtil;
 import sonia.scm.util.SystemUtil;
+import sonia.scm.util.Util;
 import sonia.scm.web.security.AuthenticationHandler;
 import sonia.scm.web.security.AuthenticationResult;
 
 import java.io.IOException;
 
 import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
 
       con.provider("ADsDSOObject");
       con.open("Active Directory Provider", "" /* default */, "" /* default */,
                -1 /* default */);
+      readDomains(rootDSE);
       logger.debug("Connected to Active Directory");
     }
     catch (ExecutionException ex)
    * Method description
    *
    *
+   *
+   * @param domainContext
    * @param userOrGroupname
    *
    * @return
    */
-  protected String getDnOfUserOrGroup(String userOrGroupname)
+  protected String getDnOfUserOrGroup(String domainContext,
+          String userOrGroupname)
   {
     String dn;
     _Command cmd = ClassFactory.createCommand();
 
     cmd.activeConnection(con);
-    cmd.commandText("<LDAP://" + defaultNamingContext + ">;(sAMAccountName="
+    cmd.commandText("<LDAP://" + domainContext + ">;(sAMAccountName="
                     + userOrGroupname + ");distinguishedName;subTree");
 
     _Recordset rs = cmd.execute(null, Variant.MISSING, -1 /* default */);
 
     if (!rs.eof())
     {
-      dn = rs.fields().item("distinguishedName").value().toString();
+      dn = getString(rs, "distinguishedName");
     }
     else
     {
     }
 
     AuthenticationResult result;
-    String dn = getDnOfUserOrGroup(username);
+    String host = "";
+    String internalName = username;
+    String domainContext = defaultNamingContext;
+    int index = username.indexOf("\\");
+
+    if (index > 0)
+    {
+      String domain = username.substring(0, index);
+
+      username = username.substring(index + 1);
+      internalName = domain.toLowerCase().concat("/").concat(username);
+
+      ActiveDirectoryDomain d = domainMap.get(domain.toUpperCase());
+
+      if (d != null)
+      {
+        domainContext = d.getDomainContext();
+        host = Util.nonNull(d.getHost());
+      }
+      else if (logger.isWarnEnabled())
+      {
+        logger.warn("could not find domain {}", domain);
+      }
+    }
+
+    if (logger.isDebugEnabled())
+    {
+      logger.debug("try to autenticate user {} in context {}", username,
+                   domainContext);
+    }
+
+    String dn = getDnOfUserOrGroup(domainContext, username);
+
+    if (logger.isDebugEnabled())
+    {
+      logger.debug("found user at {}", dn);
+    }
 
     // now we got the DN of the user
     IADsOpenDSObject dso = COM4J.getObject(IADsOpenDSObject.class, "LDAP:",
 
     try
     {
-      IADsUser usr = dso.openDSObject("LDAP://" + dn, dn, password,
+      if (Util.isNotEmpty(host))
+      {
+        host = host.concat("/");
+      }
+
+      IADsUser usr = dso.openDSObject("LDAP://".concat(host).concat(dn), dn,
+                                      password,
                                       0).queryInterface(IADsUser.class);
 
       if (usr != null)
       {
         if (!usr.accountDisabled())
         {
-          User user = new User(username, usr.fullName(), usr.emailAddress());
+          User user = new User(internalName, usr.fullName(),
+                               usr.emailAddress());
 
           user.setType(TYPE);
           result = new AuthenticationResult(user, getGroups(usr));
     return result;
   }
 
+  /**
+   * Method description
+   *
+   *
+   * @param rootDSE
+   */
+  private void readDomains(IADs rootDSE)
+  {
+    try
+    {
+      String configNC = (String) rootDSE.get("configurationNamingContext");
+
+      if (Util.isNotEmpty(configNC))
+      {
+        _Command cmd = ClassFactory.createCommand();
+
+        cmd.activeConnection(con);
+        cmd.commandText("<LDAP://" + configNC
+                        + ">;(NETBIOSName=*);cn,dnsRoot,ncname;subTree");
+
+        _Recordset rs = cmd.execute(null, Variant.MISSING, -1 /* default */);
+
+        while (!rs.eof())
+        {
+          String cn = getString(rs, "cn");
+          String dn = getString(rs, "ncname");
+          String host = getFirstString(rs, "dnsRoot");
+
+          if (Util.isNotEmpty(cn) && Util.isNotEmpty(dn))
+          {
+            if (logger.isInfoEnabled())
+            {
+              logger.info("found domain: {}, {}, {}", new Object[] { cn, dn,
+                      host });
+            }
+
+            domainMap.put(cn, new ActiveDirectoryDomain(cn, dn, host));
+          }
+
+          rs.moveNext();
+        }
+      }
+      else if (logger.isWarnEnabled())
+      {
+        logger.warn("could not find a valid configurationNamingContext");
+      }
+    }
+    catch (Exception ex)
+    {
+      logger.error("could not read domains", ex);
+    }
+  }
+
   //~--- get methods ----------------------------------------------------------
 
   /**
-   * Method description
+   * Get the first String of a multivalue recordset item
+   *
+   *
+   * @param rs
+   * @param item
+   *
+   * @return the first item of a recordset
+   */
+  private String getFirstString(_Recordset rs, String item)
+  {
+    String result = null;
+    Object[] values = (Object[]) getObject(rs, item);
+
+    if (Util.isNotEmpty(values))
+    {
+      Object value = values[0];
+
+      if (value != null)
+      {
+        result = value.toString();
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Fetch all groupnames of a user
    *
    *
    * @param usr
     return groups;
   }
 
+  /**
+   * Method description
+   *
+   *
+   * @param rs
+   * @param item
+   *
+   * @return
+   */
+  private Object getObject(_Recordset rs, String item)
+  {
+    Object value = null;
+    Fields fields = rs.fields();
+
+    if (fields != null)
+    {
+      Field field = fields.item(item);
+
+      if (field != null)
+      {
+        value = field.value();
+      }
+    }
+
+    return value;
+  }
+
+  /**
+   * Method description
+   *
+   *
+   * @param rs
+   * @param item
+   *
+   * @return
+   */
+  private String getString(_Recordset rs, String item)
+  {
+    String result = null;
+    Object value = getObject(rs, item);
+
+    if (value != null)
+    {
+      result = value.toString();
+    }
+
+    return result;
+  }
+
   //~--- fields ---------------------------------------------------------------
 
   /** Field description */
 
   /** Field description */
   private String defaultNamingContext;
+
+  /** Field description */
+  private Map<String, ActiveDirectoryDomain> domainMap =
+    new HashMap<String, ActiveDirectoryDomain>();
 }

File plugins/scm-activedirectory-auth-plugin/src/main/java/sonia/scm/activedirectory/auth/ActiveDirectoryDomain.java

+/**
+ * Copyright (c) 2010, Sebastian Sdorra
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. Neither the name of SCM-Manager; nor the names of its
+ *    contributors may be used to endorse or promote products derived from this
+ *    software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * http://bitbucket.org/sdorra/scm-manager
+ *
+ */
+
+
+
+package sonia.scm.activedirectory.auth;
+
+//~--- non-JDK imports --------------------------------------------------------
+
+import sonia.scm.util.Util;
+
+/**
+ *
+ * @author sdorra
+ */
+public class ActiveDirectoryDomain
+{
+
+  /**
+   * Constructs ...
+   *
+   */
+  public ActiveDirectoryDomain() {}
+
+  /**
+   * Constructs ...
+   *
+   *
+   * @param domain
+   * @param dn
+   * @param host
+   */
+  public ActiveDirectoryDomain(String domain, String dn, String host)
+  {
+    this.host = host;
+    this.domain = domain;
+    this.dn = dn;
+  }
+
+  //~--- get methods ----------------------------------------------------------
+
+  /**
+   * Method description
+   *
+   *
+   * @return
+   */
+  public String getDn()
+  {
+    return dn;
+  }
+
+  /**
+   * Method description
+   *
+   *
+   * @return
+   */
+  public String getDomain()
+  {
+    return domain;
+  }
+
+  /**
+   * Method description
+   *
+   *
+   * @return
+   */
+  public String getDomainContext()
+  {
+    String dc = dn;
+
+    if (Util.isNotEmpty(host))
+    {
+      dc = host.concat("/").concat(dn);
+    }
+
+    return dc;
+  }
+
+  /**
+   * Method description
+   *
+   *
+   * @return
+   */
+  public String getHost()
+  {
+    return host;
+  }
+
+  //~--- set methods ----------------------------------------------------------
+
+  /**
+   * Method description
+   *
+   *
+   * @param dn
+   */
+  public void setDn(String dn)
+  {
+    this.dn = dn;
+  }
+
+  /**
+   * Method description
+   *
+   *
+   * @param domain
+   */
+  public void setDomain(String domain)
+  {
+    this.domain = domain;
+  }
+
+  /**
+   * Method description
+   *
+   *
+   * @param host
+   */
+  public void setHost(String host)
+  {
+    this.host = host;
+  }
+
+  //~--- fields ---------------------------------------------------------------
+
+  /** Field description */
+  private String dn;
+
+  /** Field description */
+  private String domain;
+
+  /** Field description */
+  private String host;
+}

File scm-webapp/pom.xml

       </dependencies>
       
     </profile>
+    
+    <profile>
+      <id>active-directory</id>
+    
+      <dependencies>
+          
+        <dependency>
+          <groupId>sonia.scm.plugins</groupId>
+          <artifactId>scm-activedirectory-auth-plugin</artifactId>
+          <version>1.5-SNAPSHOT</version>
+        </dependency>
+      
+      </dependencies>
+      
+    </profile>
 
     <profile>
       <id>it</id>