added support for trusted domains, see #28

This commit is contained in:
Sebastian Sdorra
2011-06-16 12:59:28 +02:00
parent 814bdf3d84
commit 5e25070570
3 changed files with 373 additions and 7 deletions

View File

@@ -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("<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
{
@@ -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("<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
@@ -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<String, ActiveDirectoryDomain> domainMap =
new HashMap<String, ActiveDirectoryDomain>();
}

View File

@@ -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;
}

View File

@@ -434,6 +434,21 @@
</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>