Skip to content

Commit

Permalink
Merge pull request #57 from tduehr/freeipa
Browse files Browse the repository at this point in the history
add FreeIPA support
  • Loading branch information
dfish3r committed Nov 17, 2015
2 parents 7d7749d + 09c7633 commit b5a076b
Show file tree
Hide file tree
Showing 3 changed files with 412 additions and 0 deletions.
210 changes: 210 additions & 0 deletions core/src/main/java/org/ldaptive/auth/ext/FreeIPAAccountState.java
@@ -0,0 +1,210 @@
/* See LICENSE for licensing and NOTICE for copyright. */
package org.ldaptive.auth.ext;

import java.util.Calendar;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.security.auth.login.AccountExpiredException;
import javax.security.auth.login.AccountLockedException;
import javax.security.auth.login.AccountNotFoundException;
import javax.security.auth.login.CredentialExpiredException;
import javax.security.auth.login.CredentialNotFoundException;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;
import org.ldaptive.ResultCode;
import static org.ldaptive.ResultCode.*;
import org.ldaptive.auth.AccountState;
import org.ldaptive.auth.AuthenticationResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Represents the state of an FreeIPA account.
*
* @author tduehr
*/
public class FreeIPAAccountState extends AccountState
{

public enum Error implements AccountState.Error {

UNKNOWN(-1),
FAILED_AUTHENTICATION(1),
PASSWORD_EXPIRED(2),
ACCOUNT_EXPIRED(3),
MAXIMUM_LOGINS_EXCEEDED(4),
LOGIN_TIME_LIMITED(5),
LOGIN_LOCKOUT(6),
ACCOUNT_NOT_FOUND(7),
CREDENTIAL_NOT_FOUND(8),
ACCOUNT_DISABLED(9);

/** underlying error code. */
private final int code;

/**
* Creates a new freeipa error.
*
* @param i error code
*/
Error(final int i)
{
code = i;
}


@Override
public int getCode()
{
return code;
}


@Override
public String getMessage()
{
return name();
}


@Override
public void throwSecurityException()
throws LoginException
{
switch (this) {

case ACCOUNT_NOT_FOUND:
throw new AccountNotFoundException(name());

case FAILED_AUTHENTICATION:
throw new FailedLoginException(name());

case ACCOUNT_DISABLED:
throw new FailedLoginException(name());

case PASSWORD_EXPIRED:
throw new CredentialExpiredException(name());

case CREDENTIAL_NOT_FOUND:
throw new FailedLoginException(name());

case ACCOUNT_EXPIRED:
throw new AccountExpiredException(name());

case MAXIMUM_LOGINS_EXCEEDED:
throw new AccountLockedException(name());

case LOGIN_TIME_LIMITED:
throw new AccountLockedException(name());

case LOGIN_LOCKOUT:
throw new AccountLockedException(name());

case UNKNOWN:
throw new FailedLoginException(name());

default:
throw new IllegalStateException("Unknown FreeIPA error: " + this);
}
}


/**
* Returns the error for the supplied integer constant.
*
* @param code to find error for
*
* @return error
*/
public static Error valueOf(final int code)
{
for (Error e : Error.values()) {
if (e.getCode() == code) {
return e;
}
}
if (ResultCode.valueOf(code) == ResultCode.SUCCESS)
return null;
else
return UNKNOWN;
}


/**
* Parses the supplied error messages and returns the corresponding error enum. Attempts to find {@link #PATTERN}
* and parses the second group match as a decimal integer.
*
* @param message to parse
*
* @return freeipa error
*/
public static Error parse(final ResultCode rc, String message)
{
if (rc != null && rc != SUCCESS) {
switch(rc) {

case NO_SUCH_OBJECT:
return ACCOUNT_NOT_FOUND;

case INVALID_CREDENTIALS:
return CREDENTIAL_NOT_FOUND;

case INSUFFICIENT_ACCESS_RIGHTS:
return FAILED_AUTHENTICATION;
case UNWILLING_TO_PERFORM:
if (message.equals("Entry permanently locked.\n"))
return LOGIN_LOCKOUT;
else if (message.equals("Too many failed logins.\n"))
return MAXIMUM_LOGINS_EXCEEDED;
else if (message.equals("Account (Kerberos principal) is expired"))
return ACCOUNT_EXPIRED;
else if (message.equals("Account inactivated. Contact system administrator."))
return ACCOUNT_DISABLED;

default:
return UNKNOWN;
}
}
return null;
}
}

/** freeipa specific enum. */
private final Error fError;


/**
* Creates a new edirectory account state.
*
* @param exp account expiration
* @param remaining number of logins available
*/
public FreeIPAAccountState(final Calendar exp, final int remaining)
{
super(new AccountState.DefaultWarning(exp, remaining));
fError = null;
}


/**
* Creates a new edirectory account state.
*
* @param error containing authentication failure details
*/
public FreeIPAAccountState(final FreeIPAAccountState.Error error)
{
super(error);
fError = error;
}


/**
* Returns the edirectory error for this account state.
*
* @return freeipa error
*/
public FreeIPAAccountState.Error getFreeIPAError()
{
return fError;
}
}
@@ -0,0 +1,105 @@
/* See LICENSE for licensing and NOTICE for copyright. */
package org.ldaptive.auth.ext;

import java.util.Calendar;
import org.ldaptive.LdapAttribute;
import org.ldaptive.LdapEntry;
import static org.ldaptive.ResultCode.*;
import org.ldaptive.auth.AuthenticationResponse;
import org.ldaptive.auth.AuthenticationResponseHandler;
import org.ldaptive.io.GeneralizedTimeValueTranscoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
* Attempts to parse the authentication response and set the account state using data associated with eDirectory. The
* {@link org.ldaptive.auth.Authenticator} should be configured to return 'passwordExpirationTime' and
* 'loginGraceRemaining' attributes so they can be consumed by this handler.
*
* @author tduehr
*/
public class FreeIPAAuthenticationResponseHandler implements AuthenticationResponseHandler
{

/** Maximum password age. */
private int maxPasswordAge = -1;

/** Maximum password age. */
private int maxLoginFailures = -1;

/** Number of hours before expiration to produce a warning. */
private int warningHours;

protected final Logger logger = LoggerFactory.getLogger(getClass());

/** Default constructor. */
public FreeIPAAuthenticationResponseHandler() {}

/**
* Creates a new edirectory authentication response handler.
*
* @param hours length of time before expiration that should produce a warning
*/
public FreeIPAAuthenticationResponseHandler(final int hours)
{
if (hours <= 0) {
throw new IllegalArgumentException("Hours must be > 0");
}
warningHours = hours;
}

public int getMaxPasswordAge(){
return maxPasswordAge;
}

public void setMaxPasswordAge(final int age){
if (age <= 0) {
throw new IllegalArgumentException("Age must be > 0");
}
maxPasswordAge = age;
}

@Override
public void handle(final AuthenticationResponse response)
{
if (response.getResultCode() != SUCCESS) {
final FreeIPAAccountState.Error fError = FreeIPAAccountState.Error.parse(response.getResultCode(), response.getMessage());
if (fError != null) {
response.setAccountState(new FreeIPAAccountState(fError));
}
} else if (response.getResult()) {
final LdapEntry entry = response.getLdapEntry();
final LdapAttribute expTime = entry.getAttribute("krbPasswordExpiration");
logger.info("krbPasswordExpiration: {}", expTime);
final LdapAttribute loginRemaining = entry.getAttribute("loginGraceRemaining");
logger.info("loginGraceRemaining: {}", loginRemaining);
final LdapAttribute failedLogins = entry.getAttribute("krbLoginFailedCount");
logger.info("krbLoginFailedCount: {}", failedLogins);
final LdapAttribute lastPwdChange = entry.getAttribute("krbLastPwdChange");
logger.info("krbLastPwdChange: {}", lastPwdChange);
Calendar exp = null;

int loginRemainingValue = 0;
if (loginRemaining != null)
loginRemainingValue = Integer.parseInt(loginRemaining.getStringValue());
else if (failedLogins != null && maxLoginFailures >= 0)
loginRemainingValue = maxLoginFailures - Integer.parseInt(failedLogins.getStringValue());

final Calendar now = Calendar.getInstance();
if (expTime != null) {
exp = expTime.getValue(new GeneralizedTimeValueTranscoder());
} else if (maxPasswordAge >= 0 && lastPwdChange != null) {
exp = lastPwdChange.getValue(new GeneralizedTimeValueTranscoder());
exp.setTimeInMillis(exp.getTimeInMillis() + (maxPasswordAge * 24 * 3600000));
}
if (warningHours > 0) {
final Calendar warn = (Calendar) exp.clone();
warn.add(Calendar.HOUR_OF_DAY, -warningHours);
if (now.before(warn))
exp = null;
}
response.setAccountState(new FreeIPAAccountState(exp, loginRemainingValue));
}
}
}

0 comments on commit b5a076b

Please sign in to comment.