Skip to content

Instantly share code, notes, and snippets.

@dblock
Created June 27, 2012 13:31
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save dblock/3004083 to your computer and use it in GitHub Desktop.
Save dblock/3004083 to your computer and use it in GitHub Desktop.
Obtain additional user information from ActiveDirectory using Com4J
//
// from http://waffle.codeplex.com/workitem/10034
//
package waffle.servlet.spi;
import java.io.IOException;
import java.security.Principal;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com4j.COM4J;
import com4j.Com4jObject;
import com4j.Variant;
import com4j.typelibs.activeDirectory.IADs;
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;
/*
* Decorates a Request with Active Directory data corresponding to the currently authenticated user.
* Requires COM4J.
*
* Developpers info: http://waffle.codeplex.com/workitem/10034
*
* by Christophe Dupriez, DESTIN inc. SSEB. Contributed in 2010/12 to Waffle Project along its OpenSource licence.
*/
public class ActiveDirectoryDecoratorFilter implements Filter {
protected Log _log = LogFactory.getLog(ActiveDirectoryDecoratorFilter.class);
/*
<filter>
<filter-name>ActiveDirectoryDecoratorFilter</filter-name>
<filter-class>
waffle.servlet.spi.ActiveDirectoryDecoratorFilter
</filter-class>
<init-param>
<param-name>peremption</param-name>
<param-value>1800</param-value> (number of seconds before user data is considered invalid)
</init-param>
<init-param>
<param-name>query</param-name>
<param-value>sAMAccountName</param-value> (fields where authentication principal name is searched in AD)
</init-param>
<init-param>
<param-name>query2</param-name>
<param-value>userPrincipalName</param-value> (secondary field to search name in AD)
</init-param>
<init-param>
<param-name>request-attribute-where-you-like-to-receive-userPrincipalName</param-name>
<param-value>userPrincipalName</param-value>
</init-param>
<init-param>
<param-name>request-attribute-where-you-like-to-receive-someOtherField</param-name>
<param-value>someOtherField</param-value> (typical AD fields are: sn,givenName,telephoneNumber...)
</init-param>
</filter>
<filter-mapping>
<filter-name>ActiveDirectoryDecoratorFilter</filter-name>
<url-pattern>/*</url-pattern> OR LIMITED TO URLs WHERE AD ADDITIONAL INFO IS USEFUL!
</filter-mapping>
*/
private String defaultNamingContext = null;
private final String defaultQueryField = "sAMAccountName";
private final String defaultQueryField2 = "userPrincipalName";
private String queryField;
private String queryField2;
private final String defaultFields = "distinguishedName,userPrincipalName,sAMAccountName,sn,givenName,telephoneNumber";
private String adFields;
private HashMap<String,String> AD2attribute;
private HashMap<String, HashMap<String,String>> knownUsersData;
private HashMap<String, Date> knownUsersCreated;
private long maxAge = 30 * 60 * 1000; // Default age for cached AD information entries is 30 minutes
public synchronized void init(FilterConfig filterConfig) throws ServletException {
IADs rootDSE = COM4J.getObject(IADs.class, "LDAP://RootDSE", null);
defaultNamingContext = (String)rootDSE.get("defaultNamingContext");
knownUsersData = new HashMap<String, HashMap<String,String>>();
knownUsersCreated = new HashMap<String, Date>();
//Get init parameters
Enumeration<String> paramNames = filterConfig.getInitParameterNames();
adFields = "";
AD2attribute = new HashMap<String,String>();
queryField = "";
queryField2 = "";
while (paramNames.hasMoreElements()) {
String aName = paramNames.nextElement();
if ("peremption".equalsIgnoreCase(aName)) {
String value = filterConfig.getInitParameter(aName); // Caution: aName must be used as it may be not in full lowercase...
if (value != null && !value.isEmpty()) {
long peremption = Long.valueOf(value);
if (peremption > 0) {
maxAge = peremption*1000; // Cache time to live is in seconds...
}
}
} else if ("query".equalsIgnoreCase(aName)) {
String value = filterConfig.getInitParameter(aName); // Caution: aName must be used as it may be not in full lowercase...
if (value != null && !value.isEmpty()) {
queryField = value;
}
} else if ("query2".equalsIgnoreCase(aName)) {
String value = filterConfig.getInitParameter(aName);
if (value != null && !value.isEmpty()) {
queryField2 = value;
}
} else {
String value = filterConfig.getInitParameter(aName);
if (value != null && !value.isEmpty()) {
adFields = (adFields.isEmpty() ? "" : ',')+value;
AD2attribute.put(value,aName);
}
}
}
if (adFields.isEmpty()) {
adFields = defaultFields;
StringTokenizer st = new StringTokenizer (adFields,",");
while (st.hasMoreTokens()) {
String token = st.nextToken();
AD2attribute.put(token, token);
}
}
if (queryField.isEmpty()) {
queryField = defaultQueryField;
queryField2 = defaultQueryField2;
}
_log.info("LDAP root="+defaultNamingContext+", query="+queryField+", query2="+queryField2+", AD fields="+adFields);
}
public synchronized void destroy() {
defaultNamingContext = null;
knownUsersData = null;
knownUsersCreated = null;
adFields = null;
_log.info("destroyed");
}
private static void store (ServletRequest request, HashMap<String,String> attributes) {
for (Map.Entry<String,String> anAttr : attributes.entrySet()) {
request.setAttribute(anAttr.getKey(),anAttr.getValue());
}
}
synchronized private void gatherUserData (ServletRequest req, String name) {
Date nameCreated = knownUsersCreated.get(name);
Date now = new Date();
if (nameCreated != null && nameCreated.getTime() > (now.getTime() - maxAge) ) {
store (req,knownUsersData.get(name));
} else {
knownUsersData.remove(name);
knownUsersCreated.remove(name);
_Connection con = ClassFactory.createConnection();
con.provider("ADsDSOObject");
con.open("Active Directory Provider",""/*default*/,""/*default*/,-1/*default*/);
// query LDAP to find out the LDAP DN and other info for the given user from the login ID
_Command cmd = ClassFactory.createCommand();
cmd.activeConnection(con);
String command = "<LDAP://"+defaultNamingContext+">;("+queryField+"="+name+");"+adFields+";subTree";
_log.debug("Command="+command);
cmd.commandText(command);
_Recordset rs = cmd.execute(null, Variant.MISSING, -1/*default*/);
if(rs.eof()) { // User not found!
command = "<LDAP://"+defaultNamingContext+">;("+queryField2+"="+name+");"+adFields+";subTree";
_log.debug("Command="+command);
cmd.commandText(command);
rs = cmd.execute(null, Variant.MISSING, -1/*default*/);
}
if(rs.eof()) { // User not found!
_log.error(name+" not found.");
}
else {
Fields userData = rs.fields();
if (userData != null) {
Iterator<Com4jObject> itCom = userData.iterator();
int i=0;
HashMap<String,String> userDataAttributes = new HashMap<String,String>();
while (itCom.hasNext()) {
Field comObj = (Field)itCom.next();
String attribute = AD2attribute.get(comObj.name());
if (attribute != null && !attribute.isEmpty()) {
_log.debug(i++ +") " +attribute+":"+comObj.name()+"="+comObj.value().toString());
userDataAttributes.put(attribute, comObj.value().toString());
}
}
store (req,userDataAttributes);
knownUsersData.put(name, userDataAttributes);
knownUsersCreated.put(name, now);
}
else {
_log.error("User "+name+" AD information is empty?");
}
}
rs.close();
con.close();
}
}
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
if (req instanceof HttpServletRequest) {
HttpServletRequest request = (HttpServletRequest) req;
// initNamingContext(true); This could be added in case Filter init is not ALWAYS called: let's trust the spec for now!
if (defaultNamingContext != null) {
Principal currPrincipal = request.getUserPrincipal();
if (currPrincipal != null) {
String name = currPrincipal.getName();
if (name != null && !name.isEmpty()) {
gatherUserData(req,name);
}
}
}
}
chain.doFilter(req, res);
}
}
//
// from http://waffle.codeplex.com/workitem/10034
//
import java.util.HashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com4j.COM4J;
import com4j.ComException;
import com4j.Variant;
import com4j.typelibs.activeDirectory.IADs;
import com4j.typelibs.ado20.ClassFactory;
import com4j.typelibs.ado20.Fields;
import com4j.typelibs.ado20._Command;
import com4j.typelibs.ado20._Connection;
import com4j.typelibs.ado20._Recordset;
public class UserInfo {
protected Log _log = LogFactory.getLog(UserInfo.class);
static String defaultNamingContext = null;
static final String usefulFields = "distinguishedName,userPrincipalName,sAMAccountName,sn,givenName,telephoneNumber";
String dn; // distinguishedName
String upn; // userPrincipalName
String fqn; // sAMAccountName;
String sn; // surname
String givenName;
String telephoneNumber;
static HashMap<String, UserInfo> knownUsers = new HashMap<String, UserInfo>();
synchronized void initNamingContext() {
if (defaultNamingContext == null) {
IADs rootDSE = COM4J.getObject(IADs.class, "LDAP://RootDSE", null);
defaultNamingContext = (String)rootDSE.get("defaultNamingContext");
_log.error("defaultNamingContext="+defaultNamingContext);
}
}
synchronized public static UserInfo getInstance(String username) {
UserInfo found = knownUsers.get(username);
if (found != null) return found;
return getInstanceNoCache(username);
}
synchronized public static UserInfo getInstanceNoCache(String username) {
UserInfo found = new UserInfo(username);
if (found.dn == null) {
return null;
}
knownUsers.put(username, found);
return found;
}
private UserInfo (String username) {
initNamingContext();
if (defaultNamingContext == null) {
return;
}
// Searching LDAP requires ADO [8], so it's good to create a connection upfront for reuse.
_Connection con = ClassFactory.createConnection();
con.provider("ADsDSOObject");
con.open("Active Directory Provider",""/*default*/,""/*default*/,-1/*default*/);
// query LDAP to find out the LDAP DN and other info for the given user from the login ID
_Command cmd = ClassFactory.createCommand();
cmd.activeConnection(con);
String searchField = "userPrincipalName";
int pSlash = username.indexOf('\\');
if (pSlash > 0) {
searchField = "sAMAccountName";
username = username.substring(pSlash+1);
}
_log.error("Command="+"<LDAP://"+defaultNamingContext+">;("+searchField+"="+username+");"+usefulFields+";subTree");
cmd.commandText("<LDAP://"+defaultNamingContext+">;("+searchField+"="+username+");"+usefulFields+";subTree");
_Recordset rs = cmd.execute(null, Variant.MISSING, -1/*default*/);
if(rs.eof()) { // User not found!
_log.error(username+" not found.");
}
else {
Fields userData = rs.fields();
if (userData != null) {
/* Iterator<Com4jObject> itCom = userData.iterator();
int i=0;
while (itCom.hasNext()) {
Field comObj = (Field)itCom.next();
_log.error(i++ +":"+comObj.name()+"="+comObj.value().toString());
} */
Object o;
try {
o = userData.item("distinguishedName").value();
if (o != null) dn = o.toString();
} catch (ComException ecom ) {
_log.error("distinguishedName not returned:"+ecom.getMessage());
}
try {
o = userData.item("userPrincipalName").value();
if (o != null) upn = o.toString();
} catch (ComException ecom ) {
_log.error("userPrincipalName not returned:"+ecom.getMessage());
}
try {
o = userData.item("sAMAccountName").value();
if (o != null) fqn = o.toString();
} catch (ComException ecom ) {
_log.error("sAMAccountName not returned:"+ecom.getMessage());
}
try {
o = userData.item("sn").value();
if (o != null) sn = o.toString();
} catch (ComException ecom ) {
_log.error("sn not returned:"+ecom.getMessage());
}
try {
o = userData.item("givenName").value();
if (o != null) givenName = o.toString();
} catch (ComException ecom ) {
_log.error("givenName not returned:"+ecom.getMessage());
}
try {
o = userData.item("telephoneNumber").value();
if (o != null) telephoneNumber = o.toString();
} catch (ComException ecom ) {
_log.error("telephoneNumber not returned:"+ecom.getMessage());
}
}
else {
_log.error("User "+username+" information is empty?");
}
}
rs.close();
con.close();
}
public static String getDefaultNamingContext() {
return defaultNamingContext;
}
public String getDn() {
return dn;
}
public String getUpn() {
return upn;
}
public String getFqn() {
return fqn;
}
public String getSn() {
return sn;
}
public String getGivenName() {
return givenName;
}
public String getTelephoneNumber() {
return telephoneNumber;
}
}
@Samoth
Copy link

Samoth commented Jun 28, 2012

Use Variant.getMissing() instead Variant.MISSING
userinfo.java, line 85

Otherwise, you will receive exception:
com4j.ComException: 800a0e7c (Unknown error) : Parameter object is improperly defined. Inconsistent or incomplete information was provided.

@christophe-dupriez
Copy link

Thanks a lot Samoth for the advice, it really helped me!

@TimmyStorms
Copy link

Thanks for this solution! I'm still having one problem though. This is the exception that I'm getting: "java.lang.ClassCastException: $Proxy101 cannot be cast to com4j.typelibs.ado20.Field" at line 203 of the ActiveDirectoryDecoratorFilter. Any idea of what is happening here? I'm using the "20120426-2" version of com4j (because I'm on a 64 bit OS)

Thanks

update: the objects seem to be of the Wrapper instead of Field

@TimmyStorms
Copy link

I had to adapt the code of the filter in order to make it work on my end.

 Fields userData = rs.fields();
                if (userData == null || userData.count() <= 0) {
                    _log.error("User " + name + " AD information is empty?");
                } else {
                    final Map<String, String> userDataAttributes = new HashMap<String, String>();
                    for (int i = 0; i < userData.count(); i++) {
                        final Field field = userData.item(i);
                        String attribute = AD2attribute.get(field.name());
                        if (attribute != null && !attribute.isEmpty()) {
                            _log.debug(i + ") " + field.name() + "=" + field.value().toString());
                            userDataAttributes.put(attribute, field.value().toString());
                        }
                        store(req, userDataAttributes);
                        knownUsersData.put(name, userDataAttributes);
                        knownUsersCreated.put(name, now);
                    }
                }

@derjust
Copy link

derjust commented Apr 17, 2019

@Samoth and @TimmyStorms so much thanks for this!

for those coming along here in 2024, it even works on x64 with those artifacts:

<dependency>
    <groupId>org.jvnet.com4j</groupId>
    <artifactId>com4j</artifactId>
    <version>2.1</version>
</dependency>
<dependency>
    <groupId>org.jvnet.com4j.typelibs</groupId>
    <artifactId>active-directory</artifactId>
    <version>1.0</version>
</dependency>
<dependency>
    <groupId>org.jvnet.com4j.typelibs</groupId>
    <artifactId>ado20</artifactId>
    <version>1.0</version>
</dependency>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment