CAS4: LDAP Password Policy Enforcement (LPPE)

Work in progress

This document is a work-in-progress and may not accurately reflect the full set of changes that implemented in the CAS 4 release. Tread lightly! Upon the CAS 4 release, this page should be updated to reflect the correct LPPE settings

 

The purpose of the LPPE module is to detect a number scenarios that would otherwise prevent user authentication, specifically using an Ldap instance as the primary source of user accounts. To understand the general overview and intent of the LPPE functionality, please review this page. 

Ldaptive & CAS 4

Coming up in CAS 4, the ldap integration in CAS that is provided by Spring is being reworked and replaced by that of ldaptive. Some of the design ideas and efforts throughout this document will be provided by the new framework and as such, may not be need explicit implementation and support directly in CAS.

 

This document serves to highlight and explain the architectural changes that are proposed for upcoming CAS 4 release. In additional to various bug and security fixes, the following goals and improvements are planned: 

Change in Package Structure

All LPPE functionally is proposed to be contained inside the new package: org/jasig/cas/adaptors/ldap/lppe as much as possible. There may be overlap in some areas, but the effort is concentrated on keeping the changeset in one subpack of the ldap integration module.

Support for Non-Expiring ActiveDirectory Accounts

Current version of LPPE in CAS 3.5.x contains a bug in calculating the expiration date for AD accounts that are flagged to never expire. The correct approach would be to look up the bit-wise value in the userAccountControl attribute of the AD container to detect account state. This fix can be accommodated through support for retrieval of custom attributes as described above.

The bitwise flags are defined as such:

private enum ActiveDirectoryUserAccountControlFlags {
        UAC_FLAG_ACCOUNT_DISABLED(2),
        UAC_FLAG_LOCKOUT(16),
        UAC_FLAG_PASSWD_NOTREQD(32),
        UAC_FLAG_DONT_EXPIRE_PASSWD(65536),
        UAC_FLAG_PASSWORD_EXPIRED(8388608);
        
        private int value;
        
        ActiveDirectoryUserAccountControlFlags(final int id) { 
            this.value = id; 
        }
        
        public final int getValue() { 
            return this.value; 
        }
}

Internalization of Pre-Authentication Ldap Error Codes 

LPPE attempts to intercept authentication errors by detecting a set of ldap error codes. By translating the error codes into a webflow state, LPPE is then able to redirect the user the page appropriate and relevant for the issue experienced. Currently, these error codes are visibly defined in the configuration and are somewhat easily accessible by the deployer:

<property name="ldapErrorDefinitions">
         <list>
            <bean class="org.jasig.cas.adaptors.ldap.LdapErrorDefinition"
                 p:ldapPattern="data 530"
                 p:type="badHours" />
             <bean class="org.jasig.cas.adaptors.ldap.LdapErrorDefinition"
                 p:ldapPattern="data 533"
                 p:type="accountDisabled" />
             <bean class="org.jasig.cas.adaptors.ldap.LdapErrorDefinition"
                 p:ldapPattern="data 773"
                 p:type="mustChangePassword" />
             <bean class="org.jasig.cas.adaptors.ldap.LdapErrorDefinition"
                 p:ldapPattern="data 775"
                 p:type="accountLocked" />
             <bean class="org.jasig.cas.adaptors.ldap.LdapErrorDefinition"
                 p:ldapPattern="data 531"
                 p:type="badWorkstation" />
             <bean class="org.jasig.cas.adaptors.ldap.LdapErrorDefinition"
                 p:ldapPattern="data (701|532)"
                 p:type="passwordExpired" />
         </list>
</property>

A better way perhaps to handle the abstraction of ldap error codes, which was also suggested previously by developers, would be to internalize the above errors whose type is indicated by the name of the object. The following is proposed in place of the configuration:

<property name="ldapErrorDefinitions">
    <list>
       <bean class="org.jasig.cas.adaptors.ldap.lppe.ActiveDirectoryAccountDisabledLdapErrorDefinition" />
       <bean class="org.jasig.cas.adaptors.ldap.lppe.ActiveDirectoryAccountLockedLdapErrorDefinition" />
       <bean class="org.jasig.cas.adaptors.ldap.lppe.ActiveDirectoryInvalidLoginHoursLdapErrorDefinition" />
       <bean class="org.jasig.cas.adaptors.ldap.lppe.ActiveDirectoryInvalidLoginWorkstationLdapErrorDefinition" />
       <bean class="org.jasig.cas.adaptors.ldap.lppe.ActiveDirectoryAccountMustChangePasswordLdapErrorDefinition" />
       <bean class="org.jasig.cas.adaptors.ldap.lppe.ActiveDirectoryAccountPasswordExpiredLdapErrorDefinition" />
    </list>
</property>

Ldap error definitions are optional. If none is found, authentication is prevented and the appropriate exception is thrown back. As such, deployers may choose to only pick definitions that are relevant to their environment and configuration. 

It's important to note that these error codes may prevent authentication. Examination of a successfully-authenticated ldap account for state, password expiration and other conditions needs to occur AFTER the authentication once the credentials are authenticated.

The above approach is completely extensible and exposes hooks for other implementations of the LdapErrorDefinition, should a use case arise. The changeset is provided and facilitated by having support for retrieval of custom attributes. 

Support for Post-Authentication Account Examiners

To detect and calculate the account expiration warning, the account state needs to be examined after a successful authentication. Based on LPPE configuration, the account state through retrieval of custom attributes is examined and appropriate prompts and alterations in the flow may occur. 

This changeset proposes an extensible API for examining account state post authentication. By default the implementation exposes an instance of LdapPasswordExpirationPolicyExaminer that calculates password expiration warning.  Other implementation that need to evaluate the account from other aspects may also be provided.

<property name="ldapPasswordPolicyExaminers">
	<list>
		<bean class="org.jasig.cas.adaptors.ldap.lppe.LdapPasswordExpirationPolicyExaminer">
			<property name="ignorePasswordExpirationWarningFlags" value="${ldap.authentication.lppe.noWarnValues}" />
			<property name="alwaysDisplayPasswordExpirationWarning" value="${ldap.authentication.lppe.warnAll}" />
			<property name="ldapDateConverter">
				<bean class="org.jasig.cas.adaptors.ldap.lppe.ActiveDirectoryLdapDateConverter" />
			</property> 
		</bean>
	</list>
</property>

The above approach is completely extensible and exposes hooks for other implementations of the LdapPasswordPolicyExaminer, should a use case arise. The changeset is provided and facilitated by having support for retrieval of custom attributes. Examiners are also optional and may or may not be used in conjunction with detection of ldap error codes during authentication. 

Support for Custom Date Formatters

To calculate the password expiration warning based on a given LDAP attribute date (specified in the LPPE configuration), this changeset proposes various implementations of the LdapDateConverter interface. This interface provides for not only configuring the given timezone used in date calculation, but defines the general contract between LPPE and the following implementations: 

  • ActiveDirectoryLdapDateConverter used specifically with ActiveDirectory ldap instances, feature the ability to convert a special AD date value into its equivalent java.util.Date object.

  • SimpleDateFormatLdapDateConverter expects the received date value form LDAP to be consistent with a given DateTimeFormatter's pattern.
  • TimeUnitLdapDateConverter expects the received ldap date value to be defined in milliseconds. It will convert the value to the time unit specified. The final date is initialized from a given date (or epoch).

Reduce Ldap Query Overhead with LdapPasswordPolicyAwareAuthenticationHandler

The current implementation of LPPE is forced to execute a second ldap query to retrieve account state post authentication. In addition to the query overhead, duplicate configuration may need to be applied for both the ldap authN handler and LPPE as well. 

This changeset proposes a specific LdapPasswordPolicyAwareAuthenticationHandler that wraps itself around a given implementation of the AbstractLdapUsernamePasswordAuthenticationHandler class.

The wrap allows LPPE to:

  • Let the inner ldap authN handler do its job which is to execute the authentication query. This will execute one query only as part of which the LPPE authN handler should be able to instruct the inner ldap handler to retrieve additional (custom) attributes necessary for LPPE processing.
  • Allow for a semi-comprehensive construction of a given PasswordPolicyConfiguration which is constructed based on account state and retrieved attributes. 
  • Take advantage of the machinery provided by the AbstractLdapUsernamePasswordAuthenticationHandler class, such as principal transformers as opposed to duplicating the same elements over. 
  • Exposes better control for detecting ldap error definitions before/during authentication as well as examining the account state post authentication, using the constructed PasswordPolicyConfiguration.
<bean id="lppeEnabledLdapAuthenticationHandler" class="org.jasig.cas.adaptors.ldap.lppe.LdapPasswordPolicyAwareAuthenticationHandler"> 
		<property name="ldapAuthenticationHandler" ref="bindLdapAuthenticationHandler" />
		
		...
</bean>

The LdapPasswordPolicyAwareAuthenticationHandler is able to work with other authentication handlers as well, should they grant user authentication. For instance, along side with LdapPasswordPolicyAwareAuthenticationHandler, there might be a JDBC authentication handler defined apriori that allows the user to login. In this use case, LPPE will still issue an ldap query after authentication to retrieve account state and to construct the password policy configuratio. 

Support for Retrieval of Custom Ldap Authentication Attributes

The LPPE authentication handler relies on the inner ldap authN handler for retrieval of attributes that are needed for LPPE. Support and hooks must be exposed so that LPPE instructs the inner handler, which attributes to retrieve. Such attributes may be:

<bean id="lppeEnabledLdapAuthenticationHandler" class="org.jasig.cas.adaptors.ldap.lppe.LdapPasswordPolicyAwareAuthenticationHandler"> 
		...

    	<property name="accountDisabledAttributeName" value="${ldap.authentication.lppe.accountDisabledAttribute}" />
    	<property name="accountLockedAttributeName" value="${ldap.authentication.lppe.accountLockedAttribute}" />
    	<property name="accountPasswordMustChangeAttributeName" value="${ldap.authentication.lppe.accountPasswordMustChangeAttribute}" />
    	
		<property name="passwordExpirationDateAttributeName" value="${ldap.authentication.lppe.dateAttribute}" />
		<property name="passwordWarningNumberOfDaysAttributeName" value="${ldap.authentication.lppe.warningDaysAttribute}" />
		<property name="validPasswordNumberOfDaysAttributeName" value="${ldap.authentication.lppe.validDaysAttribute}" />
		
		<property name="defaultValidPasswordNumberOfDays" value="${ldap.authentication.lppe.validDays}" />
		<property name="defaultPasswordWarningNumberOfDays" value="${ldap.authentication.lppe.warningDays}" />
		<property name="ignorePasswordExpirationWarningAttributeName" value="${ldap.authentication.lppe.noWarnAttribute}" />
		<property name="passwordPolicyUrl" value="${ldap.authentication.lppe.password.url}" />   
</bean>

Retrieval of custom attributes allows LPPE to detect certain ldap error codes and conditions that do not prevent ldap authentication. For instance, in working with OpenLdap a given account may be able to successfully log in even through a flag is set to indicate the account is locked. Defining custom attributes and their evaluation prior to authentication can support this use case. 

Support for retrieval of ldap authentication attributes is not available in CAS 3.5.x, which is partially the reason LPPE in CAS 3.5.x has to execute a second ldap query to retrieve the needed elements from ldap. 

Support for Custom WebFlow States

The Current LPPE implementation exposes a custom webflow action that execute LPPE functions after the authentication has taken place. Its job is to examine the account state for password expiration warnings and redirect the flow as is appropriate. The authentication process, for ldap error definitions, also relies on the error "type" of a given error definition to redirect the flow, which was/is initiated by AuthenticationViaFormAction. This method involves two webflow actions and unnecessarily augments the AuthentivationViaFormAction logic and  intent even if LPPE is not implemented. 

To separate the concerns, an extension of AuthentivationViaFormAction is developed. This new component,  LdapPasswordPolicyAwareAuthenticationViaFormAction, is responsible for invoking account examiners and to handle the redirects in the webflow that are specific to LPPE. Each examine, such as LdapPasswordExpirationPolicyExaminer, upon a successful authentication is invoked to evaluate the account state. 

LdapPasswordPolicyAwareAuthenticationViaFormAction will also refactor and extract out the detection of ldap error codes and their appropriate custom webflow state from the previous webflow action.Of course, this method relies on the fact that AuthenticationViaFormAction becomes extensible and allows subclasses to pass on a given state id to which it should redirect on success/error/etc. 

Full Spring Configuration

	<bean id="lppeEnabledLdapAuthenticationHandler" class="org.jasig.cas.adaptors.ldap.lppe.LdapPasswordPolicyAwareAuthenticationHandler"> 
		<property name="ldapAuthenticationHandler" ref="bindLdapAuthenticationHandler" />
		
		<property name="ldapErrorDefinitions">
    		<list>
     	  	  	<bean class="org.jasig.cas.adaptors.ldap.lppe.ActiveDirectoryAccountDisabledLdapErrorDefinition" />
	 	     	<bean class="org.jasig.cas.adaptors.ldap.lppe.ActiveDirectoryAccountLockedLdapErrorDefinition" />
    		   	<bean class="org.jasig.cas.adaptors.ldap.lppe.ActiveDirectoryInvalidLoginHoursLdapErrorDefinition" />
     	  		<bean class="org.jasig.cas.adaptors.ldap.lppe.ActiveDirectoryInvalidLoginWorkstationLdapErrorDefinition" />
      			<bean class="org.jasig.cas.adaptors.ldap.lppe.ActiveDirectoryAccountMustChangePasswordLdapErrorDefinition" />
      	 		<bean class="org.jasig.cas.adaptors.ldap.lppe.ActiveDirectoryAccountPasswordExpiredLdapErrorDefinition" />
		 	  </list>
		</property>
		
		<property name="ldapPasswordPolicyExaminers">
			<list>
				<bean class="org.jasig.cas.adaptors.ldap.lppe.LdapPasswordExpirationPolicyExaminer">
					<property name="ignorePasswordExpirationWarningFlags" value="${ldap.authentication.lppe.noWarnValues}" />
					<property name="alwaysDisplayPasswordExpirationWarning" value="${ldap.authentication.lppe.warnAll}" />
					<property name="ldapDateConverter">
						<bean class="org.jasig.cas.adaptors.ldap.lppe.ActiveDirectoryLdapDateConverter" />
					</property> 
				</bean>
			</list>
		</property>
    	<property name="accountDisabledAttributeName" value="${ldap.authentication.lppe.accountDisabledAttribute}" />
    	<property name="accountLockedAttributeName" value="${ldap.authentication.lppe.accountLockedAttribute}" />
    	<property name="accountPasswordMustChangeAttributeName" value="${ldap.authentication.lppe.accountPasswordMustChangeAttribute}" />
    	
		<property name="passwordExpirationDateAttributeName" value="${ldap.authentication.lppe.dateAttribute}" />
		<property name="passwordWarningNumberOfDaysAttributeName" value="${ldap.authentication.lppe.warningDaysAttribute}" />
		<property name="validPasswordNumberOfDaysAttributeName" value="${ldap.authentication.lppe.validDaysAttribute}" />
		
		<property name="defaultValidPasswordNumberOfDays" value="${ldap.authentication.lppe.validDays}" />
		<property name="defaultPasswordWarningNumberOfDays" value="${ldap.authentication.lppe.warningDays}" />
		<property name="ignorePasswordExpirationWarningAttributeName" value="${ldap.authentication.lppe.noWarnAttribute}" />
		<property name="passwordPolicyUrl" value="${ldap.authentication.lppe.password.url}" />   
</bean>
 
<bean id="authenticationViaFormAction" class="org.jasig.cas.adaptors.ldap.lppe.web.flow.LdapPasswordPolicyAwareAuthenticationViaFormAction"
        p:centralAuthenticationService-ref="centralAuthenticationService"
        p:warnCookieGenerator-ref="warnCookieGenerator"
        p:ldapPasswordPolicyAuthenticationHandler-ref="lppeEnabledLdapAuthenticationHandler" />

    

 

Component Diagram

Attachments

LPPE.png