/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
* add the following below this CDDL HEADER, with the fields enclosed
* by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2011 ForgeRock AS.
*/
package org.opends.server.api;
import static org.opends.messages.CoreMessages.*;
import static org.opends.server.config.ConfigConstants.OP_ATTR_ACCOUNT_DISABLED;
import static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.loggers.debug.DebugLogger.getTracer;
import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString;
import static org.opends.server.util.StaticUtils.toLowerCase;
import java.util.List;
import org.opends.messages.Message;
import org.opends.server.core.DirectoryServer;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.schema.GeneralizedTimeSyntax;
import org.opends.server.types.*;
/**
* The authentication policy context associated with a user's entry, which is
* responsible for managing the user's account, their password, as well as
* authenticating the user.
*/
public abstract class AuthenticationPolicyState
{
/**
* The tracer object for the debug logger.
*/
private static final DebugTracer TRACER = getTracer();
/**
* Returns the authentication policy state for the user provided user. This
* method is equivalent to the following:
*
* <pre>
* AuthenticationPolicy policy = AuthenticationPolicy.forUser(userEntry,
* useDefaultOnError);
* AuthenticationPolicyState state = policy
* .createAuthenticationPolicyState(userEntry);
* </pre>
*
* See the documentation of {@link AuthenticationPolicy#forUser} for a
* description of the algorithm used to find a user's authentication policy.
*
* @param userEntry
* The user entry.
* @param useDefaultOnError
* Indicates whether the server should fall back to using the default
* password policy if there is a problem with the configured policy
* for the user.
* @return The password policy for the user.
* @throws DirectoryException
* If a problem occurs while attempting to determine the password
* policy for the user.
* @see AuthenticationPolicy#forUser(Entry, boolean)
*/
public final static AuthenticationPolicyState forUser(final Entry userEntry,
final boolean useDefaultOnError) throws DirectoryException
{
final AuthenticationPolicy policy = AuthenticationPolicy.forUser(userEntry,
useDefaultOnError);
return policy.createAuthenticationPolicyState(userEntry);
}
/**
* A utility method which may be used by implementations in order to obtain
* the value of the specified attribute from the provided entry as a boolean.
*
* @param entry
* The entry whose attribute is to be parsed as a boolean.
* @param attributeType
* The attribute type whose value should be parsed as a boolean.
* @return The attribute's value represented as a ConditionResult value, or
* ConditionResult.UNDEFINED if the specified attribute does not exist
* in the entry.
* @throws DirectoryException
* If the value cannot be decoded as a boolean.
*/
protected static final ConditionResult getBoolean(final Entry entry,
final AttributeType attributeType) throws DirectoryException
{
final List<Attribute> attrList = entry.getAttribute(attributeType);
if (attrList != null)
{
for (final Attribute a : attrList)
{
if (a.isEmpty())
{
continue;
}
final String valueString = toLowerCase(a.iterator().next().getValue()
.toString());
if (valueString.equals("true") || valueString.equals("yes")
|| valueString.equals("on") || valueString.equals("1"))
{
if (debugEnabled())
{
TRACER.debugInfo("Attribute %s resolves to true for user entry "
+ "%s", attributeType.getNameOrOID(), entry.getDN().toString());
}
return ConditionResult.TRUE;
}
if (valueString.equals("false") || valueString.equals("no")
|| valueString.equals("off") || valueString.equals("0"))
{
if (debugEnabled())
{
TRACER.debugInfo("Attribute %s resolves to false for user "
+ "entry %s", attributeType.getNameOrOID(), entry.getDN()
.toString());
}
return ConditionResult.FALSE;
}
if (debugEnabled())
{
TRACER.debugError("Unable to resolve value %s for attribute %s "
+ "in user entry %s as a Boolean.", valueString,
attributeType.getNameOrOID(), entry.getDN().toString());
}
final Message message = ERR_PWPSTATE_CANNOT_DECODE_BOOLEAN
.get(valueString, attributeType.getNameOrOID(), entry.getDN()
.toString());
throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
message);
}
}
if (debugEnabled())
{
TRACER.debugInfo("Returning %s because attribute %s does not exist "
+ "in user entry %s", ConditionResult.UNDEFINED.toString(),
attributeType.getNameOrOID(), entry.getDN().toString());
}
return ConditionResult.UNDEFINED;
}
/**
* A utility method which may be used by implementations in order to obtain
* the value of the specified attribute from the provided entry as a time in
* generalized time format.
*
* @param entry
* The entry whose attribute is to be parsed as a boolean.
* @param attributeType
* The attribute type whose value should be parsed as a generalized
* time value.
* @return The requested time, or -1 if it could not be determined.
* @throws DirectoryException
* If a problem occurs while attempting to decode the value as a
* generalized time.
*/
protected static final long getGeneralizedTime(final Entry entry,
final AttributeType attributeType) throws DirectoryException
{
long timeValue = -1;
final List<Attribute> attrList = entry.getAttribute(attributeType);
if (attrList != null)
{
for (final Attribute a : attrList)
{
if (a.isEmpty())
{
continue;
}
final AttributeValue v = a.iterator().next();
try
{
timeValue = GeneralizedTimeSyntax.decodeGeneralizedTimeValue(v
.getNormalizedValue());
}
catch (final Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
TRACER.debugWarning("Unable to decode value %s for attribute %s "
+ "in user entry %s: %s", v.getValue().toString(),
attributeType.getNameOrOID(), entry.getDN().toString(),
stackTraceToSingleLineString(e));
}
final Message message = ERR_PWPSTATE_CANNOT_DECODE_GENERALIZED_TIME
.get(v.getValue().toString(), attributeType.getNameOrOID(), entry
.getDN().toString(), String.valueOf(e));
throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
message, e);
}
break;
}
}
if (timeValue == -1)
{
if (debugEnabled())
{
TRACER.debugInfo("Returning -1 because attribute %s does not "
+ "exist in user entry %s", attributeType.getNameOrOID(), entry
.getDN().toString());
}
}
// FIXME: else to be consistent...
return timeValue;
}
/**
* A boolean indicating whether or not the account associated with this
* authentication state has been administratively disabled.
*/
protected ConditionResult isDisabled = ConditionResult.UNDEFINED;
/**
* The user entry associated with this authentication policy state.
*/
protected final Entry userEntry;
/**
* Creates a new abstract authentication policy context.
*
* @param userEntry
* The user's entry.
*/
protected AuthenticationPolicyState(final Entry userEntry)
{
this.userEntry = userEntry;
}
/**
* Performs any finalization required after a bind operation has completed.
* Implementations may perform internal operations in order to persist
* internal state to the user's entry if needed.
*
* @throws DirectoryException
* If a problem occurs during finalization.
*/
public void finalizeStateAfterBind() throws DirectoryException
{
// Do nothing by default.
}
/**
* Returns the authentication policy associated with this state.
*
* @return The authentication policy associated with this state.
*/
public abstract AuthenticationPolicy getAuthenticationPolicy();
/**
* Returns {@code true} if this authentication policy state is associated with
* a user whose account has been administratively disabled.
* <p>
* The default implementation is use the value of the "ds-pwp-account-disable"
* attribute in the user's entry.
*
* @return {@code true} if this authentication policy state is associated with
* a user whose account has been administratively disabled.
*/
public boolean isDisabled()
{
final AttributeType type = DirectoryServer.getAttributeType(
OP_ATTR_ACCOUNT_DISABLED, true);
try
{
isDisabled = getBoolean(userEntry, type);
}
catch (final Exception e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
isDisabled = ConditionResult.TRUE;
if (debugEnabled())
{
TRACER.debugWarning("User %s is considered administratively "
+ "disabled because an error occurred while "
+ "attempting to make the determination: %s.", userEntry.getDN()
.toString(), stackTraceToSingleLineString(e));
}
return true;
}
if (isDisabled == ConditionResult.UNDEFINED)
{
isDisabled = ConditionResult.FALSE;
if (debugEnabled())
{
TRACER.debugInfo("User %s is not administratively disabled since "
+ "the attribute \"%s\" is not present in the entry.", userEntry
.getDN().toString(), OP_ATTR_ACCOUNT_DISABLED);
}
return false;
}
if (debugEnabled())
{
TRACER.debugInfo("User %s %s administratively disabled.", userEntry
.getDN().toString(), ((isDisabled == ConditionResult.TRUE) ? " is"
: " is not"));
}
return isDisabled == ConditionResult.TRUE;
}
/**
* Returns {@code true} if this authentication policy state is associated with
* a password policy and the method {@link #getAuthenticationPolicy} will
* return a {@code PasswordPolicy}.
*
* @return {@code true} if this authentication policy state is associated with
* a password policy, otherwise {@code false}.
*/
public boolean isPasswordPolicy()
{
return getAuthenticationPolicy().isPasswordPolicy();
}
/**
* Returns {@code true} if the provided password value matches any of the
* user's passwords.
*
* @param password
* The user-provided password to verify.
* @return {@code true} if the provided password value matches any of the
* user's passwords.
* @throws DirectoryException
* If verification unexpectedly failed.
*/
public abstract boolean passwordMatches(ByteString password)
throws DirectoryException;
}