From 5e250705708f15320c44e81fb159ffb279b06972 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 16 Jun 2011 12:59:28 +0200 Subject: [PATCH] added support for trusted domains, see #28 --- .../ActiveDirectoryAuthenticationHandler.java | 199 +++++++++++++++++- .../auth/ActiveDirectoryDomain.java | 166 +++++++++++++++ scm-webapp/pom.xml | 15 ++ 3 files changed, 373 insertions(+), 7 deletions(-) create mode 100644 plugins/scm-activedirectory-auth-plugin/src/main/java/sonia/scm/activedirectory/auth/ActiveDirectoryDomain.java diff --git a/plugins/scm-activedirectory-auth-plugin/src/main/java/sonia/scm/activedirectory/auth/ActiveDirectoryAuthenticationHandler.java b/plugins/scm-activedirectory-auth-plugin/src/main/java/sonia/scm/activedirectory/auth/ActiveDirectoryAuthenticationHandler.java index b655e35614..bc4cadf48e 100644 --- a/plugins/scm-activedirectory-auth-plugin/src/main/java/sonia/scm/activedirectory/auth/ActiveDirectoryAuthenticationHandler.java +++ b/plugins/scm-activedirectory-auth-plugin/src/main/java/sonia/scm/activedirectory/auth/ActiveDirectoryAuthenticationHandler.java @@ -48,6 +48,8 @@ import com4j.typelibs.activeDirectory.IADsGroup; 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; @@ -60,6 +62,7 @@ import sonia.scm.plugin.ext.Extension; 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; @@ -68,6 +71,8 @@ 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; @@ -177,6 +182,7 @@ public class ActiveDirectoryAuthenticationHandler con.provider("ADsDSOObject"); con.open("Active Directory Provider", "" /* default */, "" /* default */, -1 /* default */); + readDomains(rootDSE); logger.debug("Connected to Active Directory"); } catch (ExecutionException ex) @@ -203,24 +209,27 @@ public class ActiveDirectoryAuthenticationHandler * 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(";(sAMAccountName=" + cmd.commandText(";(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 { @@ -254,7 +263,43 @@ public class ActiveDirectoryAuthenticationHandler } 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:", @@ -262,14 +307,21 @@ public class ActiveDirectoryAuthenticationHandler 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)); @@ -292,10 +344,90 @@ public class ActiveDirectoryAuthenticationHandler 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(";(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 @@ -319,6 +451,55 @@ public class ActiveDirectoryAuthenticationHandler 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 */ @@ -326,4 +507,8 @@ public class ActiveDirectoryAuthenticationHandler /** Field description */ private String defaultNamingContext; + + /** Field description */ + private Map domainMap = + new HashMap(); } diff --git a/plugins/scm-activedirectory-auth-plugin/src/main/java/sonia/scm/activedirectory/auth/ActiveDirectoryDomain.java b/plugins/scm-activedirectory-auth-plugin/src/main/java/sonia/scm/activedirectory/auth/ActiveDirectoryDomain.java new file mode 100644 index 0000000000..66f3b1ee4c --- /dev/null +++ b/plugins/scm-activedirectory-auth-plugin/src/main/java/sonia/scm/activedirectory/auth/ActiveDirectoryDomain.java @@ -0,0 +1,166 @@ +/** + * 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; +} diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index e88ad2a3f3..5ecc202472 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -434,6 +434,21 @@ + + + active-directory + + + + + sonia.scm.plugins + scm-activedirectory-auth-plugin + 1.5-SNAPSHOT + + + + + it