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:

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();
}