/*
* Adito
*
* Copyright (C) 2003-2006 3SP LTD. All Rights Reserved
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package com.adito.activedirectory;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import javax.management.relation.RoleNotFoundException;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.adito.boot.ContextHolder;
import com.adito.core.CoreEvent;
import com.adito.core.CoreJAASConfiguration;
import com.adito.core.CoreListener;
import com.adito.core.CoreServlet;
import com.adito.properties.Property;
import com.adito.properties.PropertyChangeEvent;
import com.adito.properties.impl.userattributes.UserAttributeKey;
import com.adito.realms.Realm;
import com.adito.security.DefaultUserDatabase;
import com.adito.security.InvalidLoginCredentialsException;
import com.adito.security.Role;
import com.adito.security.User;
import com.adito.security.UserDatabaseException;
import com.adito.security.UserNotFoundException;
import com.adito.util.ThreadRunner;
/**
* <p>
* Microsoft Active Directory implementation of
* {@link com.adito.security.UserDatabase}.
*
* @author Lee Painter <a href="mailto:lee@localhost"><lee@localhost></a>
* @author Brett Smith <a href="mailto:brett@localhost"><brett@localhost></a>
*/
public class ActiveDirectoryUserDatabase extends DefaultUserDatabase implements CoreListener {
private static final String OBJECT_SID_ATTRIBUTE = "objectSID";
private static final String HOME_DIRECTORY_ATTRIBUTE = "homeDirectory";
private static final String HOME_DRIVE_ATTRIBUTE = "homeDrive";
private static final String MEMBER_OF_ATTIBUTE = "memberOf";
private static final String GROUPNAME_FILTER_ATTRIBUTE = "%GROUPNAME%";
private static final String USERNAME_FILTER_PARAMETER = "%USERNAME%";
private static final String USER_PRINCIPAL_NAME_PARAMETER = "%PRINCIPAL_NAME%";
public static final String SAM_ACCOUNT_NAME_ATTRIBUTE = "sAMAccountName";
public static final String COMMON_NAME_ATTRIBUTE = "cn";
public static final String DISPLAY_NAME_ATTRIBUTE = "displayName";
public static final String USER_PRINCIPAL_NAME_ATTRIBUTE = "userPrincipalName";
public static final String MAIL_ATTRIBUTE = "mail";
public static final String USER_ACCOUNT_CONTROL_ATTRIBUTE = "userAccountControl";
public static final String PWD_LAST_SET_ATTRIBUTE = "pwdLastSet";
public static final String GROUP_TYPE_ATTRIBUTE = "groupType";
public static final String PRIMARY_GROUP_ID_ATTRIBUTE = "primaryGroupId";
public static final String OBJECT_CLASS_ATTRIBUTE = "objectClass";
private static final String USER_FILTER = "(&(!(" + OBJECT_CLASS_ATTRIBUTE + "=computer))(" + OBJECT_CLASS_ATTRIBUTE
+ "=user)(|(" + SAM_ACCOUNT_NAME_ATTRIBUTE + "=" + USERNAME_FILTER_PARAMETER + ")("
+ USER_PRINCIPAL_NAME_ATTRIBUTE + "=" + USER_PRINCIPAL_NAME_PARAMETER + ")))";
private static final String USER_GROUPS_FILTER = "(&(" + OBJECT_CLASS_ATTRIBUTE + "=group)(member=" + GROUPNAME_FILTER_ATTRIBUTE + "))";
private static final String GROUP_FILTER = "&(" + OBJECT_CLASS_ATTRIBUTE + "=group)(" + COMMON_NAME_ATTRIBUTE + "="
+ GROUPNAME_FILTER_ATTRIBUTE + ")";
private static final String USER_ATTRS[] = { SAM_ACCOUNT_NAME_ATTRIBUTE, USER_PRINCIPAL_NAME_ATTRIBUTE, DISPLAY_NAME_ATTRIBUTE,
MAIL_ATTRIBUTE, HOME_DIRECTORY_ATTRIBUTE, HOME_DRIVE_ATTRIBUTE, MEMBER_OF_ATTIBUTE, PRIMARY_GROUP_ID_ATTRIBUTE,
PWD_LAST_SET_ATTRIBUTE, USER_ACCOUNT_CONTROL_ATTRIBUTE };
private static final String GROUP_ATTRS[] = { COMMON_NAME_ATTRIBUTE, OBJECT_SID_ATTRIBUTE, MEMBER_OF_ATTIBUTE };
private static final String WILDCARD_SEARCH = "*";
private static final Log logger = LogFactory.getLog(ActiveDirectoryUserDatabase.class);
private ActiveDirectoryUserDatabaseConfiguration configuration;
private UserContainer userContainer = UserContainer.EMPTY_CACHE;
private GroupContainer groupContainer = GroupContainer.EMPTY_CACHE;
private ThreadRunner threadRunner;
/** Constructor */
public ActiveDirectoryUserDatabase() {
this(false, false);
}
/** Constructor */
public ActiveDirectoryUserDatabase(boolean supportsAccountCreation, boolean supportsPasswordChange) {
super("Active Directory", supportsAccountCreation, supportsPasswordChange);
addJAASConfiguration();
}
private void addJAASConfiguration() {
Map<String, String> parameters = new HashMap<String, String>();
parameters.put("client", "TRUE");
parameters.put("debug", String.valueOf(logger.isDebugEnabled()).toUpperCase());
parameters.put("useSubjectCredsOnly", "FALSE");
parameters.put("useTicketCache", "FALSE");
parameters.put("refreshKrb5Config", "TRUE");
AppConfigurationEntry entry = new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule",
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, parameters);
CoreJAASConfiguration config = (CoreJAASConfiguration) Configuration.getConfiguration();
config.addAppConfigurationEntry(ActiveDirectoryUserDatabase.class.getName(), entry);
}
protected ActiveDirectoryUserDatabaseConfiguration getConfiguration() {
return configuration;
}
protected UserContainer getUserContainer() {
return userContainer;
}
protected GroupContainer getGroupContainer() {
return groupContainer;
}
/*
* (non-Javadoc)
*
* @see com.adito.security.UserDatabase#logon(java.lang.String,
* java.lang.String)
*/
public User logon(String username, String password) throws UserDatabaseException, InvalidLoginCredentialsException {
ActiveDirectoryUser user = null;
try {
user = (ActiveDirectoryUser) getAccount(username);
} catch (Exception e) {
logger.error("Failed to logon", e);
throw new UserDatabaseException("Failed to logon", e);
}
// this needs to be outside the try/catch, otherwise the specific
// exception is turned into a general one
assertValidCredentials(user, password);
return user;
}
private void assertValidCredentials(ActiveDirectoryUser user, String password) throws InvalidLoginCredentialsException {
if (!areCredentialsValid(user, password)) {
throw new InvalidLoginCredentialsException("Invalid username or password.");
}
}
@SuppressWarnings("unchecked")
public Iterable<User> allUsers() {
Iterator<? extends User> retrievePrincipals = userContainer.retrievePrincipals();
return (Iterable<User>) toIterable(retrievePrincipals);
}
/*
* (non-Javadoc)
*
* @see com.adito.security.UserDatabase#getAccount(java.lang.String)
*/
public ActiveDirectoryUser getAccount(String username) throws UserNotFoundException, Exception {
if (logger.isDebugEnabled()) {
logger.debug("Getting account " + username);
}
if (!userContainer.containsPrincipal(username)) {
loadUsers(username, false);
}
if (!userContainer.containsPrincipal(username)) {
throw new UserNotFoundException(username + " is not a valid user!");
}
return userContainer.retrievePrincipal(username);
}
/*
* (non-Javadoc)
*
* @see com.adito.security.UserDatabase#getRole(java.lang.String)
*/
public Role getRole(String groupName) throws UserDatabaseException, RoleNotFoundException {
if (logger.isDebugEnabled()) {
logger.debug("Getting group " + groupName);
}
if (!groupContainer.containsPrincipal(groupName)) {
loadRoles(groupName, false);
}
if (!groupContainer.containsPrincipal(groupName)) {
throw new RoleNotFoundException(groupName + " is not a valid group!");
}
ActiveDirectoryGroup group = groupContainer.retrievePrincipal(groupName);
groupContainer.buildHierarchy(group.getOriginalDn());
return group;
}
@SuppressWarnings("unchecked")
public Iterable<Role> allRoles() {
Iterator<? extends Role> retrievePrincipals = groupContainer.retrievePrincipals();
return (Iterable<Role>) toIterable(retrievePrincipals);
}
/*
* (non-Javadoc)
*
* @see com.adito.security.UserDatabase#checkPassword(java.lang.String,java.lang.String)
*/
public boolean checkPassword(String username, String password) throws UserDatabaseException, InvalidLoginCredentialsException {
try {
ActiveDirectoryUser user = (ActiveDirectoryUser) getAccount(username);
return areCredentialsValid(user, password);
} catch (UserNotFoundException e) {
throw new UserDatabaseException("Failed to check password", e);
} catch (Exception e) {
throw new UserDatabaseException("Failed to check password", e);
}
}
private void loadUsers(final String filter, final boolean removeMissingEntries) throws UserDatabaseException {
configuration.doAs(new RetryPrivilegedAction() {
protected Object doIt(InitialLdapContext context) throws Exception {
loadUsers(filter, context, removeMissingEntries);
return null;
}
});
}
private void loadUsers(final String filter, InitialLdapContext context, final boolean removeMissingEntries)
throws NamingException {
final Collection<String> usernames = userContainer.retrievePrincipalNames();
PagedResultMapper mapper = new AbstractPagedResultMapper() {
public void mapSearchResult(SearchResult searchResult) throws NamingException, UserDatabaseException {
String dn = searchResult.getNameInNamespace();
ActiveDirectoryUser user = populateActiveDirectoryUser(dn, searchResult.getAttributes());
String key = userContainer.storePrincipal(user);
usernames.remove(key);
if (logger.isDebugEnabled()) {
logger.debug("Found user " + user);
}
}
};
try {
String replacedFilter = buildUserFilter(filter);
PagedResultTemplate pagedResultTemplate = configuration.getPagedResultTemplate();
pagedResultTemplate.search(context, replacedFilter, USER_ATTRS, mapper);
} finally {
if (removeMissingEntries) {
userContainer.updateRemovedPrincipals(usernames);
}
}
}
private String buildUserFilter(String filter) {
String escapedFilter = ActiveDirectoryUserDatabaseConfiguration.getEscapedDn(filter, true);
String usernameReplacedFilter = USER_FILTER.replaceAll(USERNAME_FILTER_PARAMETER, escapedFilter);
String principalFilter = escapedFilter.equals("*") ? "*" : escapedFilter + "*";
return usernameReplacedFilter.replaceAll(USER_PRINCIPAL_NAME_PARAMETER, principalFilter);
}
private void loadRoles(final String filter, final boolean removeMissingEntries) throws UserDatabaseException {
configuration.doAs(new RetryPrivilegedAction() {
@Override
protected Object doIt(InitialLdapContext context) throws Exception {
loadRoles(filter, context, removeMissingEntries);
return null;
}
@Override
protected InitialLdapContext getContext(String url) throws NamingException {
Map<String, String> extraProperties = Collections.<String, String> singletonMap(
"java.naming.ldap.attributes.binary", OBJECT_SID_ATTRIBUTE);
return configuration.getAuthenticatedContext(url, extraProperties);
}
});
groupContainer.buildHierarchy();
}
private void loadRoles(String filter, InitialLdapContext context, boolean removeMissingEntries) throws Exception {
final Collection<String> groupNames = groupContainer.retrievePrincipalNames();
PagedResultMapper mapper = new AbstractPagedResultMapper() {
public void mapSearchResult(SearchResult searchResult) throws NamingException {
String dn = searchResult.getNameInNamespace();
Attributes attributes = searchResult.getAttributes();
String commonName = getAttributeValue(attributes, COMMON_NAME_ATTRIBUTE);
if (commonName.length() != 0) {
Long rid = ActiveDirectoryGroup.getRIDFromSID((byte[]) attributes.get(OBJECT_SID_ATTRIBUTE).get());
ActiveDirectoryGroup group = new ActiveDirectoryGroup(commonName, dn, getEscapedDn(dn), rid, getRealm());
String[] parents = getParents(attributes);
String key = groupContainer.storeGroup(group, parents);
groupNames.remove(key);
}
}
};
try {
String replacedFilter = buildGroupFilter(filter);
PagedResultTemplate pagedResultTemplate = configuration.getPagedResultTemplate();
pagedResultTemplate.search(context, replacedFilter, GROUP_ATTRS, mapper);
} finally {
if (removeMissingEntries) {
groupContainer.updateRemovedGroups(groupNames);
}
}
}
private String[] getParents(Attributes attributes) throws NamingException {
List<String> parents = new ArrayList<String>();
Attribute memberOfAttribute = attributes.get(MEMBER_OF_ATTIBUTE);
if (memberOfAttribute != null) {
final PagedResultTemplate pagedResultTemplate = configuration.getPagedResultTemplate();
for (int index = 0; index < memberOfAttribute.size(); index++) {
String parentDn = (String) memberOfAttribute.get(index);
if (pagedResultTemplate.isDnValid(parentDn)) {
parents.add(parentDn); // valid parent so record
}
}
}
return parents.toArray(new String[parents.size()]);
}
private String buildGroupFilter(String filter) {
String escapedFilter = ActiveDirectoryUserDatabaseConfiguration.getEscapedDn(filter, true);
String replacedFilter = GROUP_FILTER.replaceAll(GROUPNAME_FILTER_ATTRIBUTE, escapedFilter);
StringBuilder filterBuilder = new StringBuilder("(");
filterBuilder.append(replacedFilter);
if (!configuration.isIncludeDistributionGroups()) {
/**
* this seems a little random, basically there is no way of saying a
* group IS a distribution group. you can OR settings together and
* work out what they are but the resulting query would be huge.
*/
int minimumSecurityId = ActiveDirectoryGroupTypes.SECURITY_GROUP | ActiveDirectoryGroupTypes.APP_QUERY_GROUP;
filterBuilder.append("(" + GROUP_TYPE_ATTRIBUTE + "<=" + String.valueOf(minimumSecurityId) + ")");
}
filterBuilder.append(")");
return filterBuilder.toString();
}
protected final boolean areCredentialsValid(ActiveDirectoryUser user, String password) {
try {
assertCredentials(user, password);
return true;
} catch (Throwable e) {
logger.error("Failure to authenticate user", e);
return false;
}
}
protected void assertCredentials(ActiveDirectoryUser user, String password) throws Throwable {
if (configuration.isUserAuthenticationGssApi()) {
ActiveDirectoryUser activeDirectoryUser = (ActiveDirectoryUser) user;
LoginContext context = configuration.createLoginContext(activeDirectoryUser.getUserPrincipalName(), password);
ActiveDirectoryUserDatabaseConfiguration.logoutContext(context);
} else {
assertSimpleCredentialsValid(user.getPrincipalName(), password);
}
}
private void assertSimpleCredentialsValid(final String username, final String password) throws Throwable {
RetryPrivilegedAction action = new RetryPrivilegedAction() {
protected Object doIt(InitialLdapContext context) {
return null;
}
protected InitialLdapContext getContext(String url) throws Exception {
String userDn = ((ActiveDirectoryUser) getAccount(username)).getOriginalDn();
Map<String, String> variables = new HashMap<String, String>(3);
variables.put(Context.SECURITY_AUTHENTICATION, configuration.getUserAuthenticationType());
variables.put(Context.SECURITY_PRINCIPAL, userDn);
variables.put(Context.SECURITY_CREDENTIALS, password);
return configuration.getInitialContext(url, variables);
}
};
Object result = action.run();
if (result instanceof Throwable) {
throw (Throwable) result;
}
}
/**
* Get a user account given a DN. <code>null</code> will be returned if no
* such account can be found.
*
* @param dn dn
* @return user account
* @throws UserDatabaseException on any error
*/
public User getAccountFromDN(final String dn) throws UserDatabaseException {
if (dn.indexOf("CN") > -1 && dn.indexOf("DC") > -1) {
// This looks like a DN so do a lookup
if (logger.isDebugEnabled()) {
logger.debug("Looking up account using DN: " + dn);
}
return (User) configuration.doAs(new RetryPrivilegedAction(false) {
protected Object doIt(InitialLdapContext context) throws NamingException {
return getAccountFromDN(dn, context);
}
});
}
throw new UserDatabaseException("Certificate requires subject to be DN of Active Directory user");
}
private User getAccountFromDN(String dn, InitialLdapContext context) throws NamingException {
String actualDN = null;
for (StringTokenizer tokens = new StringTokenizer(dn, ","); tokens.hasMoreTokens();) {
String elm = tokens.nextToken().trim();
if (elm.toUpperCase().startsWith("CN") || elm.toUpperCase().startsWith("OU") || elm.toUpperCase().startsWith("DC")) {
actualDN = (actualDN == null ? "" : actualDN + ",") + elm;
}
}
try {
Attributes attributes = context.getAttributes(actualDN, USER_ATTRS);
return populateActiveDirectoryUser(dn, attributes);
} catch (Exception e) {
logger.error("Cannot locate user for DN " + dn, e);
throw new NamingException("User not found for DN " + dn);
}
}
private ActiveDirectoryUser populateActiveDirectoryUser(String dn, Attributes attributes) throws NamingException, UserDatabaseException {
if (attributes == null) {
throw new NamingException("No attributes for " + dn);
}
String username = getAttributeValue(attributes, SAM_ACCOUNT_NAME_ATTRIBUTE);
String userPrincipalName = getAttributeValue(attributes, USER_PRINCIPAL_NAME_ATTRIBUTE);
String defaultDomain = getConfiguration().getDomain();
String email = getAttributeValue(attributes, MAIL_ATTRIBUTE);
String fullName = getAttributeValue(attributes, DISPLAY_NAME_ATTRIBUTE);
Date lastPasswordChange = isPasswordChangeAllowed(attributes) ? getPasswordLastSetDate(attributes) : new Date();
ActiveDirectoryUser user = new ActiveDirectoryUser(username, userPrincipalName, defaultDomain, email, fullName, dn, getEscapedDn(dn), lastPasswordChange, getRealm());
String homeDirectory = getAttributeValue(attributes, User.USER_ATTR_HOME_DIRECTORY);
if (homeDirectory.length() != 0) {
Property.setProperty(new UserAttributeKey(user, User.USER_ATTR_HOME_DIRECTORY), homeDirectory, null);
}
String homeDrive = getAttributeValue(attributes, User.USER_ATTR_HOME_DRIVE);
if (homeDrive.length() != 0) {
Property.setProperty(new UserAttributeKey(user, User.USER_ATTR_HOME_DRIVE), homeDrive, null);
}
ActiveDirectoryGroup[] groups = getGroups(user, attributes);
if (logger.isDebugEnabled()) {
logger.debug("User belongs to " + groups.length + " groups");
}
user.setRoles(groups);
return user;
}
public static Date getPasswordLastSetDate(Attributes attributes) throws NamingException {
try {
String value = getAttributeValue(attributes, PWD_LAST_SET_ATTRIBUTE);
return ActiveDirectoryUserDatabaseConfiguration.adTimeToJavaDate(Long.parseLong(value));
} catch (NumberFormatException e) {
return new Date();
}
}
/**
* @param attributes
* @return true if the last password change date should be required
* @throws NamingException
*/
protected boolean isPasswordChangeAllowed(Attributes attributes) throws NamingException {
return false;
}
private ActiveDirectoryGroup[] getGroups(ActiveDirectoryUser user, Attributes attributes) throws NamingException, UserDatabaseException {
Collection<ActiveDirectoryGroup> groups = new ArrayList<ActiveDirectoryGroup>();
String primaryGroupId = getAttributeValue(attributes, PRIMARY_GROUP_ID_ATTRIBUTE);
if (primaryGroupId.length() != 0) {
Long rid = new Long(Long.parseLong(primaryGroupId));
if (logger.isDebugEnabled()) {
logger.debug("Users primaryGroupId is " + rid.toString());
}
ActiveDirectoryGroup group = groupContainer.getByRid(rid);
if (group != null) {
if (logger.isDebugEnabled()) {
logger.debug("Users primary group is " + group.getOriginalDn());
}
groups.add(group);
} else {
if (logger.isDebugEnabled()) {
logger.debug("Could not find primary group " + rid.toString());
}
}
}
if (configuration.isMemberOfSupported()) {
groups.addAll(getUsersGroups(attributes));
} else {
groups.addAll(getGroupsForUser(user));
}
return groups.toArray(new ActiveDirectoryGroup[groups.size()]);
}
private Collection<ActiveDirectoryGroup> getUsersGroups(Attributes attributes) throws NamingException {
Attribute memberOfAttribute = attributes.get(MEMBER_OF_ATTIBUTE);
if (memberOfAttribute == null) {
return Collections.<ActiveDirectoryGroup>emptyList();
}
Collection<ActiveDirectoryGroup> groups = new ArrayList<ActiveDirectoryGroup>();
for (int index = 0; index < memberOfAttribute.size(); index++) {
String groupDn = (String) memberOfAttribute.get(index);
groups.addAll(getGroupsByDn(groupDn));
}
return groups;
}
private Collection<ActiveDirectoryGroup> getGroupsByDn(String groupDn) {
if (logger.isDebugEnabled()) {
logger.debug("Checking if user is a member of " + groupDn + " a valid group");
}
Collection<ActiveDirectoryGroup> groups = new ArrayList<ActiveDirectoryGroup>();
if (groupContainer.containsDn(groupDn)) {
ActiveDirectoryGroup group = (ActiveDirectoryGroup) groupContainer.getGroupByDn(groupDn);
if (group != null && !groups.contains(group)) {
groups.add(group);
if (logger.isDebugEnabled()) {
logger.debug("Member of " + groupDn + " [" + group.getPrincipalName() + "]");
}
/**
* Add the parent groups for each group since the user
* effectively belongs to those groups too.
*/
if (group.getParents() != null) {
for (int parentIndex = 0; parentIndex < group.getParents().length; parentIndex++) {
ActiveDirectoryGroup parentGroup = group.getParents()[parentIndex];
if (parentGroup == null) {
if (logger.isDebugEnabled()) {
logger.debug("Found NULL parent group");
}
} else if (!groups.contains(parentGroup)) {
groups.add(parentGroup);
}
}
}
} else {
if (logger.isInfoEnabled()) {
logger.info("Could not find group " + groupDn);
}
}
}
return groups;
}
private Collection<ActiveDirectoryGroup> getGroupsForUser(final ActiveDirectoryUser user) throws UserDatabaseException {
final Collection<String> groupDns = new HashSet<String>();
configuration.doAs(new RetryPrivilegedAction() {
@Override
protected Object doIt(InitialLdapContext context) throws Exception {
PagedResultMapper mapper = new AbstractPagedResultMapper() {
public void mapSearchResult(SearchResult searchResult) throws NamingException {
groupDns.add(searchResult.getNameInNamespace());
}
};
String replacedFilter = USER_GROUPS_FILTER.replaceAll(GROUPNAME_FILTER_ATTRIBUTE, user.getDn());
PagedResultTemplate pagedResultTemplate = configuration.getPagedResultTemplate();
pagedResultTemplate.search(context, replacedFilter, GROUP_ATTRS, mapper);
return null;
}
});
Collection<ActiveDirectoryGroup> groups = new ArrayList<ActiveDirectoryGroup>();
for (String groupDn : groupDns) {
groups.addAll(getGroupsByDn(groupDn));
}
return groups;
}
/*
* (non-Javadoc)
*
* @see com.adito.core.CoreListener#coreEvent(com.adito.core.CoreEvent)
*/
public void coreEvent(CoreEvent event) {
// When in install mode, the wizard looks after re-initialising the user
// database
if (!ContextHolder.getContext().isSetupMode() && event instanceof PropertyChangeEvent) {
PropertyChangeEvent changeEvent = (PropertyChangeEvent) event;
if (changeEvent.getDefinition().getName().startsWith("activeDirectory.")) {
if (logger.isInfoEnabled()) {
logger.info("Active Directory configuration changed. Re-initialising");
}
try {
configuration.refresh();
initialise();
} catch (Exception e) {
logger.error("Failed to re-initialise Active Directory.", e);
}
}
}
}
public void open(CoreServlet controllingServlet, Realm realm) throws Exception {
try {
super.open(controllingServlet, realm);
initConfiguration();
initialise();
CoreServlet.getServlet().addCoreListener(this);
threadRunner = new ThreadRunner("CacheUpdater", getCacheUpdaterJob(), configuration.getTimeToLive());
threadRunner.start();
} catch (Exception e) {
close();
throw e;
}
}
private void initConfiguration() throws Exception {
configuration = buildConfiguration();
configuration.postInitialize();
userContainer = configuration.createUserContainer();
groupContainer = configuration.createRoleContainer();
if (logger.isInfoEnabled()) {
logger.info("Running with configuration = " + configuration.toString());
}
}
protected ActiveDirectoryUserDatabaseConfiguration buildConfiguration() throws IllegalArgumentException, Exception {
ActiveDirectoryUserDatabaseConfiguration configuration = new ActiveDirectoryUserDatabaseConfiguration(getRealm(), new Properties());
configuration.setProtocolType(ActiveDirectoryUserDatabaseConfiguration.PLAIN_PROTOCOL);
return configuration;
}
private Runnable getCacheUpdaterJob() {
return new Runnable() {
public void run() {
if (logger.isInfoEnabled()) {
logger.info("Caching items");
logger.info("Caching roles");
}
try {
loadRoles(WILDCARD_SEARCH, true);
} catch (UserDatabaseException e) {
logger.error("Error updating cached roles", e);
}
if (logger.isInfoEnabled()) {
logger.info("Finished caching roles");
logger.info("Caching users");
}
// once we have the roles, we'll be able to find the users role
// assignments
try {
loadUsers(WILDCARD_SEARCH, true);
} catch (UserDatabaseException e) {
logger.error("Error updating cached users", e);
}
if (logger.isInfoEnabled()) {
logger.info("Finished caching users");
logger.info("Finished caching items");
}
}
};
}
/**
* I have changed this to obtain the service account user account details.
* The previous code was not fully contacting the LDAP server, it actually
* only performed a kerberos login so some errors were not being detected.
*
* @throws NamingException
*/
private void initialise() throws Exception {
try {
String serviceAccountName = configuration.getServiceAccountName();
if (configuration.isServiceAuthenticationGssApi()) {
assertServiceAccountValid(serviceAccountName);
} else {
getAttributeValue(serviceAccountName, SAM_ACCOUNT_NAME_ATTRIBUTE);
}
} catch (Exception e) {
logger.error("Could not get the service account login context. All Active Directory features will be unavailable. You should check your Service Account Username and Password settings.", e);
close(); // if we can't talk to the server we shouldn't be classed as open
throw e;
}
}
private void assertServiceAccountValid(final String serviceAccountName) throws UserNotFoundException, UserDatabaseException {
Boolean isAccountFound = (Boolean) configuration.doAs(new RetryPrivilegedAction() {
protected Object doIt(InitialLdapContext context) throws NamingException {
String replacedFilter = buildUserFilter(serviceAccountName);
PagedResultTemplate pagedResultTemplate = configuration.getPagedResultTemplate();
return pagedResultTemplate.searchForResult(context, configuration.getBaseDn(), replacedFilter);
}
});
if (!isAccountFound) {
throw new UserNotFoundException(serviceAccountName + " is not a valid user!");
}
}
/*
* (non-Javadoc)
*
* @see com.adito.boot.Database#close()
*/
public void close() throws Exception {
super.close();
CoreServlet.getServlet().removeCoreListener(this);
if (threadRunner != null) {
threadRunner.stop();
}
userContainer.close();
groupContainer.close();
}
/*
* (non-Javadoc)
*
* @see com.adito.security.UserDatabase#logout(com.adito.security.User)
*/
public void logout(User user) {
}
/**
* The application must supply a PrivilegedAction that is to be run inside a
* Subject.doAs() or Subject.doAsPrivileged().
*/
protected abstract class RetryPrivilegedAction implements PrivilegedAction<Object> {
private final boolean returnExceptionNotNull;
private final String hosts;
protected RetryPrivilegedAction() {
this(true);
}
protected RetryPrivilegedAction(boolean returnExceptionNotNull) {
this.returnExceptionNotNull = returnExceptionNotNull;
hosts = configuration.getContactableActiveDirectories();
}
public final Object run() {
long startTime = System.currentTimeMillis();
if (logger.isDebugEnabled()) {
logger.debug("Starting Timed Operation");
}
InitialLdapContext context = null;
try {
context = getContext(hosts);
Object doIt = doIt(context);
return doIt;
} catch (Exception e) {
logger.error("Failed to run action.", e);
return returnExceptionNotNull ? e : null;
} finally {
close(context);
long finishTime = System.currentTimeMillis();
if (logger.isDebugEnabled()) {
logger.debug("Finished Timed Operation in " + ((finishTime - startTime) / 1000) + " seconds");
}
}
}
protected InitialLdapContext getContext(String url) throws Exception {
return configuration.getAuthenticatedContext(url);
}
protected abstract Object doIt(InitialLdapContext context) throws Exception;
}
protected String getAttributeValue(String dn, String attributeName) throws NamingException, UserDatabaseException {
Attributes attributes = getAttributes(dn, new String[] { attributeName });
return getAttributeValue(attributes, attributeName);
}
protected Attributes getAttributes(final String dn, final String... attributes) throws UserDatabaseException {
return (Attributes) configuration.doAs(new RetryPrivilegedAction() {
protected Object doIt(InitialLdapContext context) throws NamingException {
return context.getAttributes(dn, attributes);
}
});
}
protected static String getAttributeValue(Attributes attributes, String attributeName) throws NamingException {
if (attributes == null) {
return null;
}
Attribute attribute = attributes.get(attributeName);
return attribute == null ? "" : (String) attribute.get();
}
/**
* As some characters are escaped via a backslash, we need to add some more
* slashes to get this to work.
*
* @param dn
* @return String
*/
private static String getEscapedDn(String dn) {
String escapeDn = dn.replaceAll("\\\\", "\\\\\\\\");
String escapeForwardSlash = escapeDn.replaceAll("/", "\\\\/");
return escapeForwardSlash;
}
protected static void close(InitialLdapContext context) {
if (context != null) {
try {
context.close();
} catch (NamingException e) {
// ignore
}
}
}
}