LDAP servers such as OpenLDAP, beyond their fundamental role as directory services, boast robust password policy handling features that are pivotal in fortifying one’s organization’s cybersecurity defenses. LDAP servers are renowned for their ability to centralize user authentication and authorization across an organization’s network and enforce advanced password policy requirements, such as stringent password complexity, the ability to lock user accounts temporarily, etc.
In this post, we will take a look at how Apereo CAS may be tuned to allow for advanced password policy handling for LDAP accounts, with a particular focus on Groovy. Our starting position is:
7.0.x
21
To manage password policy enforcement with LDAP, Apereo CAS must first be configured to authenticate user accounts with an LDAP backend, the simplest form of which would be as follows:
cas.authn.ldap[0].type=DIRECT
cas.authn.ldap[0].dn-format=uid=%s,ou=people,dc=example,dc=org
cas.authn.ldap[0].ldap-url=ldaps://ldap.example.org
cas.authn.ldap[0].additional-attributes=*,+
cas.authn.ldap[0].enhance-with-entry-resolver=false
This configuration block instructs CAS to look for accounts in the LDAP server that match the specified DN pattern and establish an LDAP BIND
operation using the supplied user password. Once the bind is successful, CAS will additionally fetch all user and operational attributes attached to the account and will make those available to downstream systems and components for further processing.
Once we have an authenticated account, it’s time to evaluate the password policy rules.
Let us pretend that our LDAP server has marked the user account as locked with a somewhat non-conventional boolean attribute that is called accountIsLocked
, which is set to true
. As our imaginary LDAP server is unable to prevent the user account from successfully authenticating, the expectation falls onto us as the consumer to fetch this attribute, detect its value, and then instruct CAS to block the authentication flow accordingly. This can be done via the following block:
cas.authn.ldap[0].password-policy.enabled=true
cas.authn.ldap[0].password-policy.policy-attributes.accountIsLocked=\
javax.security.auth.login.AccountLockedException
The authentication flow will now halt with an AccountLockedException
signal if the accountIsLocked
attribute is found to be true
. The signal is then caught and processed by the webflow layer in CAS to present the appropriate messaging and inform the user appropriately.
A simple by attribute type of policy may not be enough in more advanced cases and you may have more sophisticated rules and conditions that need to be calculated on the fly when a decision is made about the authentication flow. One way to handle this type of use case would be with Groovy scripts:
cas.authn.ldap[0].password-policy.enabled=true
cas.authn.ldap[0].password-policy.strategy=GROOVY
cas.authn.ldap[0].password-policy.groovy.location=file:/path/to/my/PasswordPolicy.groovy
The Groovy script would be something similar to the following:
def run(final Object... args) {
def response = args[0]
def configuration = args[1]
def logger = args[2]
def applicationContext = args[3]
logger.debug("Handling password policy for [{}]", response)
// Decide what to do with the LDAP response...
if (accountIsNotHavingAGoodDay(response)) {
return [new DefaultMessageDescriptor("lang.account.bad.day")]
}
return []
}
The above script is mainly tasked to produce a list of warning messages conditionally. Such messages are communicated back to the CAS webflow in the form of language codes that may be found in the CAS language bundles. For example, if the Groovy script decides that the account is not having a good day, it will then pass back a warning to CAS that aims to explain this scenario via the CAS user interface under the language code lang.account.bad.day
. The expectation is that this code is defined in CAS language bundles and is specifically designed to explain this situation:
lang.account.bad.day=Sorry, you are having a bad day!
Remember that the script is producing warnings. Warnings warn and they do not block the flow. Blocking operations would require the script to send a signal to the webflow to halt typically via throwing specific exceptions such as AccountLockedException
, etc.
As ever, if you are unhappy with Groovy or find that it proves limiting in some cases, you can always do the same sort of thing in Java:
cas.authn.ldap[0].password-policy.enabled=true
cas.authn.ldap[0].password-policy.custom-policy-class=org.example.MyPasswordPolicyHandler
The MyPasswordPolicyHandler
component would typically take on the following form:
public class MyPasswordPolicyHandler implements AuthenticationResponseHandler {
@Override
public void handle(AuthenticationResponse response) {
}
}
If you have questions about the contents and the topic of this blog post, or if you need additional guidance and support, feel free to send us a note and ask about consulting and support services.
I hope this review was of some help to you and I am sure that both this post as well as the functionality it attempts to explain can be improved in any number of ways. Please feel free to engage and contribute as best as you can.
Happy Coding,
Monday-Friday
9am-6pm, Central European Time
7am-1pm, U.S. Eastern Time
Monday-Friday
9am-6pm, Central European Time