Forked from dblock/ActiveDirectoryDecoratorFilter.java
Created
April 17, 2019 20:29
-
-
Save derjust/1b9b2be2c9094d1cd198c4508465babd to your computer and use it in GitHub Desktop.
Obtain additional user information from ActiveDirectory using Com4J
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment