Emmanuel,
I tried this but it doesn't seem to work. I added the code you recommended,
when I login the first time I see the log statements showing the attempt to
invalidate the cache but when I try to login a second time my custom
authenticator is never invoked. I also tried adding the modify event in an
attempt to invalidate the cache when the account is modified, I again see the
log entries showing the attempt to invalidate the cache but again when I try to
login I do not see my code getting called at all. Any other suggestions. Log
entries below, updated code attached.
First Login
INFO | jvm 1 | 2017/12/08 13:18:06 | [13:18:05] INFO
[com.cga.aaims.ldap.apacheds.interceptor.AAIMSAuthenticationInterceptor] -
Intercepting bind operation
INFO | jvm 1 | 2017/12/08 13:18:06 | [13:18:05] INFO
[com.cga.aaims.ldap.apacheds.interceptor.AAIMSAuthenticationInterceptor] -
Executing parent level bind events first
INFO | jvm 1 | 2017/12/08 13:18:06 | [13:18:05] INFO
[com.cga.aaims.ldap.apacheds.interceptor.AAIMSAuthenticationInterceptor] -
Executing custom bind events
INFO | jvm 1 | 2017/12/08 13:18:06 | [13:18:05] INFO
[com.cga.aaims.ldap.apacheds.interceptor.AAIMSAuthenticationInterceptor] -
Attempting to validate status attribute for uId [email protected]
INFO | jvm 1 | 2017/12/08 13:18:06 | [13:18:05] INFO
[com.cga.aaims.ldap.apacheds.interceptor.AAIMSAuthenticationInterceptor] -
Status for [email protected] is active
INFO | jvm 1 | 2017/12/08 13:18:06 | [13:18:05] INFO
[com.cga.aaims.ldap.apacheds.interceptor.AAIMSAuthenticationInterceptor] -
Attempting to validate pwdReset attribute for uId
[email protected]
INFO | jvm 1 | 2017/12/08 13:18:06 | [13:18:05] INFO
[com.cga.aaims.ldap.apacheds.interceptor.AAIMSAuthenticationInterceptor] -
pwdReset for [email protected] is FALSE
INFO | jvm 1 | 2017/12/08 13:18:06 | [13:18:05] INFO
[com.cga.aaims.ldap.apacheds.interceptor.AAIMSAuthenticationInterceptor] -
Attempting to invalidate the cache for
[email protected],ou=CommittedMembers,ou=people,dc=test,dc=com
INFO | jvm 1 | 2017/12/08 13:18:06 | [13:18:05] INFO
[com.cga.aaims.ldap.apacheds.interceptor.AAIMSAuthenticationInterceptor] -
Attempting to set lastLogon attribute for uId [email protected]
INFO | jvm 1 | 2017/12/08 13:18:06 | [13:18:05] INFO
[com.cga.aaims.ldap.apacheds.interceptor.AAIMSAuthenticationInterceptor] -
lastLogon should be set now
INFO | jvm 1 | 2017/12/08 13:18:06 | [13:18:05] INFO
[com.cga.aaims.ldap.apacheds.interceptor.AAIMSAuthenticationInterceptor] - Done
with custom bind action, calling next operation
INFO | jvm 1 | 2017/12/08 13:18:06 | [13:18:06] INFO
[com.cga.aaims.ldap.apacheds.interceptor.AAIMSAuthenticationInterceptor] -
Intercepting bind operation
INFO | jvm 1 | 2017/12/08 13:18:06 | [13:18:06] INFO
[com.cga.aaims.ldap.apacheds.interceptor.AAIMSAuthenticationInterceptor] -
Executing parent level bind events first
INFO | jvm 1 | 2017/12/08 13:18:06 | [13:18:06] INFO
[com.cga.aaims.ldap.apacheds.interceptor.AAIMSAuthenticationInterceptor] -
Executing custom bind events
INFO | jvm 1 | 2017/12/08 13:18:06 | [13:18:06] INFO
[com.cga.aaims.ldap.apacheds.interceptor.AAIMSAuthenticationInterceptor] - Done
with custom bind action, calling next operation
INFO | jvm 1 | 2017/12/08 13:18:06 | [13:18:06] WARN
[org.apache.directory.server.core.api.interceptor.context.FilteringOperationContext]
- Requested attribute pwdLastSet does not exist in the schema, it will be
ignored
INFO | jvm 1 | 2017/12/08 13:18:06 | [13:18:06] WARN
[org.apache.directory.server.core.api.interceptor.context.FilteringOperationContext]
- Requested attribute pwdLastSet does not exist in the schema, it will be
ignored
Second Login
INFO | jvm 1 | 2017/12/08 13:19:00 | [13:19:00] WARN
[org.apache.directory.server.core.api.interceptor.context.FilteringOperationContext]
- Requested attribute pwdLastSet does not exist in the schema, it will be
ignored
INFO | jvm 1 | 2017/12/08 13:19:00 | [13:19:00] WARN
[org.apache.directory.server.core.api.interceptor.context.FilteringOperationContext]
- Requested attribute pwdLastSet does not exist in the schema, it will be
ignored
Thanks,
Justin Isenhour | Lead Developer, Systems and Technology Group | Compass Group
USA | 2400 Yorkmont Road | Charlotte, NC 28217 | 704.328.5804 |
[email protected]
-----Original Message-----
From: Emmanuel Lécharny [mailto:[email protected]]
Sent: Thursday, December 7, 2017 5:35 PM
To: [email protected]
Subject: Re: [Ext] Re: [ApacheDS] How to clear cached authentication on change
of custom attribute
Le 07/12/2017 à 22:34, Isenhour, Justin a écrit :
> A couple of things I have noticed. I am not able to access the
> authenticator, my custom interceptor extends AuthenticationInterceptor which
> has a collection of authenticators but that collection is private. The other
> thing I have noticed is that if the user account in question is already
> cached then my custom bind event is never called, so changes made here would
> have no impact. Thoughts?
Ah, right, my proposal was not crrect because you would have to have access to
an authenticator beforehand.
But you can somehow fetch one using the getAuthenticators() method, which is
public in the AuthenticationInterceptor parent class, iterate on each
authenticator and call the invalidateCache() method on each one.
That should work (yeah, I know, kind of a hack...)
--
Emmanuel Lecharny
Symas.com
directory.apache.org
package com.cga.aaims.ldap.apacheds.interceptor;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.SimpleTimeZone;
import org.apache.directory.api.ldap.model.constants.SchemaConstants;
import org.apache.directory.api.ldap.model.entry.Attribute;
import org.apache.directory.api.ldap.model.entry.DefaultAttribute;
import org.apache.directory.api.ldap.model.entry.DefaultModification;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.entry.Modification;
import org.apache.directory.api.ldap.model.entry.ModificationOperation;
import
org.apache.directory.api.ldap.model.exception.LdapAuthenticationException;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.ldap.model.schema.AttributeType;
import org.apache.directory.server.core.api.CoreSession;
import org.apache.directory.server.core.api.DirectoryService;
import org.apache.directory.server.core.api.entry.ClonedServerEntry;
import
org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
import
org.apache.directory.server.core.api.interceptor.context.BindOperationContext;
import
org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
import org.apache.directory.server.core.authn.AuthenticationInterceptor;
import org.apache.directory.server.core.authn.Authenticator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.cga.aaims.ldap.apacheds.AAIMSSchemaConstants;
import com.cga.aaims.ldap.apacheds.Status;
/**
* Custom ApacheDS interceptor designed to bridge the gaps between the
out-of-the-box
* features/functionality of ApacheDS and the business requirements of AAIMS.
*
* <br><br>
*
* <b>Add Operation:</b><br>
* On add we will check to see if the user object already has the status
attribute,
* if not then we will add it with the default value of active.
* If it does exist then we will leave it as is and move on.
*
* <br><br>
*
* <b>Bind Operation:</b><br>
* On bind we will perform 3 different customs actions:<br>
* <ol>
* <li>
* <b>Check User Status:</b><br>
* Look for the status attribute and check to see if it is active or not.
* If active the user will be allowed, if not active then we will throw an
LdapAuthenticationException.
* </li>
* <li>
* <b>Check Must Change Password Flag:</b><br>
* Check the pwdReset attribute to see if the user is required to reset their
password or not.
* A value of true will force the user to go through the password reset process
before they can
* successfully authenticate again.
* </li>
* <li>
* <b>Update Last Login Date:</b><br>
* Set the lastLogonDate attribute for the user to the current time. This
attribute can then be used in
* audit process to disable user accounts that are not used within a certain
time frame.
* </li>
* </ol>
*
*
* @author Justin Isenhour
*
*/
public class AAIMSAuthenticationInterceptor extends AuthenticationInterceptor {
private static final Logger LOGGER =
LoggerFactory.getLogger(AAIMSAuthenticationInterceptor.class);
/** Admin session used for making modifications to the entry during
authentication **/
private CoreSession adminSession;
/**
* Initialize the interceptor and sets up an admin session that can be used
in other
* event handlers to make modification to the entry.
*/
@Override
public void init( DirectoryService directoryService ) throws LdapException {
adminSession = directoryService.getAdminSession();
super.init( directoryService );
}
/**
* Intercepts the add operation in order to add the status attribute if it
doesn't already exist.
*/
@Override
public void add(AddOperationContext addContext) throws LdapException {
LOGGER.debug("Intercepting add operation");
ClonedServerEntry entry = (ClonedServerEntry)
addContext.getEntry();
Attribute uIdAT = entry.get(SchemaConstants.UID_AT);
boolean isStgBasicAccountObject =
entry.hasObjectClass(AAIMSSchemaConstants.STG_BASIC_ACCOUNT_OBJECT_CLASS);
if (isStgBasicAccountObject && null != uIdAT) {
String uId = uIdAT.getString();
//set the status attribute value if needed
setStatusAttribute(uId, entry);
} else {
LOGGER.debug("This is not a user object or not one that
extends stgBasicAccount, ignoring");
}
super.add(addContext);
}
/**
*
*
* @param uId
* @param entry
* @throws LdapException
*/
private void setStatusAttribute(String uId, ClonedServerEntry entry)
throws LdapException {
LOGGER.debug("Attempting to add status attribute to uId {}",
uId);
AttributeType statusAT;
Attribute statusAttribute;
statusAT =
schemaManager.lookupAttributeTypeRegistry(AAIMSSchemaConstants.STATUS_AT);
if (entry.get(statusAT) == null) {
LOGGER.debug("Status was null, defaulting to active");
statusAttribute = new DefaultAttribute(statusAT);
statusAttribute.add(Status.ACTIVE.status());
entry.add(statusAttribute);
} else {
statusAttribute = entry.get(statusAT);
String status = statusAttribute.getString();
LOGGER.debug("Status attribute for {} has already been
set to {}, leaving it alone", uId, status);
}
}
/**
* Intercepts the bind operation to check to see if the users account
status it active or not.
*/
@Override
public void bind(BindOperationContext bindContext) throws LdapException
{
LOGGER.info("Intercepting bind operation");
LOGGER.info("Executing parent level bind events first");
super.bind(bindContext);
LOGGER.info("Executing custom bind events");
Entry entry = bindContext.getEntry();
Attribute uIdAT = entry.get(SchemaConstants.UID_AT);
String uId = uIdAT.getString();
boolean isStgBasicAccountObject =
entry.hasObjectClass(AAIMSSchemaConstants.STG_BASIC_ACCOUNT_OBJECT_CLASS);
if (isStgBasicAccountObject && null != uIdAT) {
checkUserStatus(uId, entry);
checkMustChangePasswordFlag(uId, entry);
invalidateCache(bindContext.getDn());
try {
setLastLogonAttribute(bindContext, uId, entry);
} catch (Exception e) {
LOGGER.error("Error setting last logon time for
{}", uId, e);
}
}
LOGGER.info("Done with custom bind action, calling next
operation");
next(bindContext);
}
/**
* Will attempt to get the status attribute for the LDAP object.
* If the attribute is not present then this logic will be ignored.
* If it is present and the value of it is anything other than active
* then we will throw and LdapExecption.
*
* @param uId - user id of the LDAP account
* @param entry - LDAP entry object being evaluated
* @throws LdapException Account is not active
*/
private void checkUserStatus(String uId, Entry entry) throws
LdapException {
LOGGER.info("Attempting to validate status attribute for uId
{}", uId);
AttributeType statusAT;
Attribute statusAttribute;
statusAT =
schemaManager.lookupAttributeTypeRegistry(AAIMSSchemaConstants.STATUS_AT);
if (entry.get(statusAT) != null) {
statusAttribute = entry.get(statusAT);
String status = statusAttribute.getString();
LOGGER.info("Status for {} is {}", uId, status);
if (!Status.ACTIVE.status().equalsIgnoreCase(status)) {
throw new LdapAuthenticationException("Account
is not active");
}
} else {
LOGGER.info("No status attribute was found for {},
continuing", uId);
}
}
/**
* Will attempt to get the pwdReset attribute for the LDAP object.
* If the attribute is not present then this logic will be ignored.
* If it is present and the value of it is true then we will throw
* an LdapExecption.
*
* @param uId - user id of the LDAP account
* @param entry - LDAP entry object being evaluated
* @throws LdapException User must change password
*/
private void checkMustChangePasswordFlag(String uId, Entry entry)
throws LdapException {
LOGGER.info("Attempting to validate pwdReset attribute for uId
{}", uId);
AttributeType pwdResetAT;
Attribute pwdResetAttribute;
pwdResetAT =
schemaManager.lookupAttributeTypeRegistry(SchemaConstants.PWD_RESET_AT);
if (entry.get(pwdResetAT) != null) {
pwdResetAttribute = entry.get(pwdResetAT);
String pwdReset = pwdResetAttribute.getString();
LOGGER.info("pwdReset for {} is {}", uId, pwdReset);
if (Boolean.valueOf(pwdReset)) {
throw new LdapAuthenticationException("User
must change password");
}
} else {
LOGGER.info("No pwdReset attribute was found for {},
continuing", uId);
}
}
/**
* Will attempt to set the lastLogon attribute for the user to the
current time
*
* @param uId
* @param entry
* @throws Exception
*/
private void setLastLogonAttribute(BindOperationContext bindContext,
String uId, Entry entry) throws Exception {
LOGGER.info("Attempting to set lastLogon attribute for uId {}",
uId);
Dn bindDn = bindContext.getDn();
List<Modification> mods = new ArrayList<Modification>();
AttributeType lastLogonAT;
Attribute lastLogonAttribute;
SimpleDateFormat dateFormat = new
SimpleDateFormat(AAIMSSchemaConstants.DATE_FORMAT);
dateFormat.setTimeZone(new
SimpleTimeZone(SimpleTimeZone.UTC_TIME, "UTC"));
String currentTime = dateFormat.format(new Date());
lastLogonAT =
schemaManager.lookupAttributeTypeRegistry(AAIMSSchemaConstants.LAST_LOGON_AT);
lastLogonAttribute = new DefaultAttribute(lastLogonAT);
lastLogonAttribute.add(currentTime);
Modification lastLogonTimeMod = new
DefaultModification(ModificationOperation.REPLACE_ATTRIBUTE, lastLogonAT,
currentTime);
mods.add(lastLogonTimeMod);
Attribute attrMods = lastLogonTimeMod.getAttribute();
attrMods.getAttributeType();
ModifyOperationContext bindModCtx = new
ModifyOperationContext(adminSession);
bindModCtx.setDn(bindDn);
bindModCtx.setEntry(entry);
bindModCtx.setModItems(mods);
bindModCtx.setPushToEvtInterceptor(true);
directoryService.getPartitionNexus().modify(bindModCtx);
LOGGER.info("lastLogon should be set now");
}
public void modify( ModifyOperationContext modifyContext ) throws
LdapException {
LOGGER.info("Intercepting modify operation");
LOGGER.info("Executing parent level modify events first");
super.modify(modifyContext);
LOGGER.info("Executing custom modify events");
invalidateCache(modifyContext.getDn());
}
private void invalidateCache(Dn dn) {
LOGGER.info("Attempting to invalidate the cache for {}",
dn.getName());
for ( Authenticator authenticator : this.getAuthenticators() ) {
authenticator.invalidateCache(dn);
}
}
}