Account State
No standard for exposing account state data has been universally adopted by LDAP vendors. This leaves clients with vendor specific solutions that typically fall into the following categories:
- use a request/response control
- read directory attributes
- parse custom error messages/exceptions
The AuthenticationResponseHandler can be leveraged to solve this type of problem by populating the AccountState object of the AuthenticationResponse. AccountState contains Warning and Error types that are common to the most popular policy implementations. AccountState contains the following properties:
Warnings
expiration | date this account will expire |
loginsRemaining | number of logins allowed until the account will start failing |
Errors
code | integer code for this error |
message | text for this error |
Ldaptive provides several implementations for well-known directories:
Password Policy
This request/response control is defined in the following draft: http://tools.ietf.org/html/draft-behera-ldap-password-policy-10 and is most commonly used with OpenLDAP.
ConnectionConfig connConfig = ConnectionConfig.builder()
.url("ldap://directory.ldaptive.org")
.useStartTLS(true)
.build();
SearchDnResolver dnResolver = SearchDnResolver.builder()
.factory(new DefaultConnectionFactory(connConfig))
.dn("ou=people,dc=ldaptive,dc=org")
.filter("uid={user}")
.build();
SimpleBindAuthenticationHandler authHandler = new SimpleBindAuthenticationHandler(new DefaultConnectionFactory(connConfig));
Authenticator auth = new Authenticator(dnResolver, authHandler);
auth.setRequestHandlers(new PasswordPolicyAuthenticationRequestHandler());
auth.setResponseHandlers(new PasswordPolicyAuthenticationResponseHandler());
AuthenticationResponse response = auth.authenticate(new AuthenticationRequest("dfisher", new Credential("password")));
if (response.isSuccess()) {
// authentication succeeded, check account state
AccountState state = response.getAccountState();
// authentication succeeded, only a warning should exist
AccountState.Warning warning = state.getWarning();
} else {
// authentication failed, check account state
AccountState state = response.getAccountState();
// authentication failed, only an error should exist
AccountState.Error error = state.getError();
}
Active Directory
Active Directory returns account state as part of the ldap result message when a bind fails (error 49). Warnings are supported by leveraging either the ‘msDS-UserPasswordExpiryTimeComputed’ or ‘pwdLastSet’ attributes. A list of common bind errors can be found at http://ldapwiki.willeke.com/wiki/Common%20Active%20Directory%20Bind%20Errors
ConnectionConfig connConfig = ConnectionConfig.builder()
.url("ldap://directory.ldaptive.org")
.useStartTLS(true)
.build();
SearchDnResolver dnResolver = SearchDnResolver.builder()
.factory(new DefaultConnectionFactory(connConfig))
.dn("ou=people,dc=ldaptive,dc=org")
.filter("uid={user}")
.build();
SimpleBindAuthenticationHandler authHandler = new SimpleBindAuthenticationHandler(new DefaultConnectionFactory(connConfig));
Authenticator auth = Authenticator.builder()
.dnResolver(dnResolver)
.authenticationHandler(authHandler)
.responseHandlers(new ActiveDirectoryAuthenticationResponseHandler())
.returnAttributes(ActiveDirectoryAuthenticationResponseHandler.ATTRIBUTES)
.build();
AuthenticationResponse response = auth.authenticate(new AuthenticationRequest("dfisher", new Credential("password")));
if (response.isSuccess()) {
// authentication succeeded, check account state
AccountState state = response.getAccountState();
// authentication succeeded, only a warning should exist
AccountState.Warning warning = state.getWarning();
} else {
// authentication failed, check account state
AccountState state = response.getAccountState();
// authentication failed, only an error should exist
AccountState.Error error = state.getError();
}
If this handler is assigned an expirationPeriod, then the ‘pwdLastSet’ attribute will cause the handler to emit a warning for the pwdLastSet value plus the expiration amount. The scope of that warning can be further narrowed by providing a warningPeriod. By default if the ‘msDS-UserPasswordExpiryTimeComputed’ attribute is found, expirationPeriod is ignored.
See Reading User Account Password Attributes.
eDirectory
eDirectory uses a combination of result messages and attributes to convey account state. In order to parse warnings the required attributes must be requested from the Authenticator. See http://support.novell.com/docs/Tids/Solutions/10067240.html for more discussion and an explanation of error codes.
ConnectionConfig connConfig = ConnectionConfig.builder()
.url("ldap://directory.ldaptive.org")
.useStartTLS(true)
.build();
SearchDnResolver dnResolver = SearchDnResolver.builder()
.factory(new DefaultConnectionFactory(connConfig))
.dn("ou=people,dc=ldaptive,dc=org")
.filter("uid={user}")
.build();
SimpleBindAuthenticationHandler authHandler = new SimpleBindAuthenticationHandler(new DefaultConnectionFactory(connConfig));
Authenticator auth = Authenticator.builder()
.dnResolver(dnResolver)
.authenticationHandler(authHandler)
.responseHandlers(new EDirectoryAuthenticationResponseHandler())
.returnAttributes(EDirectoryAuthenticationResponseHandler.ATTRIBUTES)
.build();
AuthenticationResponse response = auth.authenticate(new AuthenticationRequest("dfisher", new Credential("password")));
if (response.isSuccess()) {
// authentication succeeded, check account state
AccountState state = response.getAccountState();
// authentication succeeded, only a warning should exist
AccountState.Warning warning = state.getWarning();
} else {
// authentication failed, check account state
AccountState state = response.getAccountState();
// authentication failed, only an error should exist
AccountState.Error error = state.getError();
}
If this handler is assigned a warningPeriod, this handler will only emit warnings during that window before password expiration. Otherwise, a warning is always emitted if the ‘passwordExpirationTime’ attribute is found.
FreeIPA
FreeIPA also uses a combination of result messages and attributes to convey account state.
ConnectionConfig connConfig = ConnectionConfig.builder()
.url("ldap://directory.ldaptive.org")
.useStartTLS(true)
.build();
Authenticator auth = Authenticator.builder()
.dnResolver(SearchDnResolver.builder()
.factory(new DefaultConnectionFactory(connConfig))
.dn("ou=people,dc=ldaptive,dc=org")
.filter("uid={user}")
.build())
.authenticationHandler(new SimpleBindAuthenticationHandler(new DefaultConnectionFactory(connConfig)))
.responseHandlers(new FreeIPAAuthenticationResponseHandler())
.returnAttributes(FreeIPAAuthenticationResponseHandler.ATTRIBUTES)
.build();
AuthenticationResponse response = auth.authenticate(new AuthenticationRequest("dfisher", new Credential("password")));
if (response.isSuccess()) {
// authentication succeeded, check account state
AccountState state = response.getAccountState();
// authentication succeeded, only a warning should exist
AccountState.Warning warning = state.getWarning();
} else {
// authentication failed, check account state
AccountState state = response.getAccountState();
// authentication failed, only an error should exist
AccountState.Error error = state.getError();
}