Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #57 from tduehr/freeipa
add FreeIPA support
- Loading branch information
Showing
3 changed files
with
412 additions
and
0 deletions.
There are no files selected for viewing
210 changes: 210 additions & 0 deletions
210
core/src/main/java/org/ldaptive/auth/ext/FreeIPAAccountState.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
105 changes: 105 additions & 0 deletions
105
core/src/main/java/org/ldaptive/auth/ext/FreeIPAAuthenticationResponseHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)); | ||
} | ||
} | ||
} |
Oops, something went wrong.