/*
* The contents of this file are subject to the terms of the Common Development and
* Distribution License (the License). You may not use this file except in compliance with the
* License.
*
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
* specific language governing permission and limitations under the License.
*
* When distributing Covered Software, include this CDDL Header Notice in each file and include
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information: "Portions Copyright [year] [name of copyright owner]".
*
* Copyright 2010 Sun Microsystems, Inc.
* Portions copyright 2011-2016 ForgeRock AS.
*/
package com.forgerock.opendj.ldap.controls;
import static com.forgerock.opendj.util.StaticUtils.byteToHex;
import static com.forgerock.opendj.util.StaticUtils.getExceptionMessage;
import static com.forgerock.opendj.ldap.CoreMessages.*;
import java.io.IOException;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.io.ASN1;
import org.forgerock.opendj.io.ASN1Reader;
import org.forgerock.opendj.io.ASN1Writer;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ByteStringBuilder;
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.DecodeOptions;
import org.forgerock.opendj.ldap.controls.Control;
import org.forgerock.opendj.ldap.controls.ControlDecoder;
import org.forgerock.util.Reject;
/**
* The Sun-defined account usability response control. The OID for this control
* is 1.3.6.1.4.1.42.2.27.9.5.8, and it has a value encoded according to the
* following BNF:
*
*
* ACCOUNT_USABLE_RESPONSE ::= CHOICE {
* is_available [0] INTEGER, -- Seconds before expiration --
* is_not_available [1] MORE_INFO }
*
* MORE_INFO ::= SEQUENCE {
* inactive [0] BOOLEAN DEFAULT FALSE,
* reset [1] BOOLEAN DEFAULT FALSE,
* expired [2] BOOLEAN DEFAULT_FALSE,
* remaining_grace [3] INTEGER OPTIONAL,
* seconds_before_unlock [4] INTEGER OPTIONAL }
*
*
* @see AccountUsabilityRequestControl
*/
public final class AccountUsabilityResponseControl implements Control {
private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
/** The OID for the account usability response control. */
public static final String OID = AccountUsabilityRequestControl.OID;
/** A decoder which can be used for decoding the account usability response control. */
public static final ControlDecoder DECODER =
new ControlDecoder() {
@Override
public AccountUsabilityResponseControl decodeControl(final Control control,
final DecodeOptions options) throws DecodeException {
Reject.ifNull(control);
if (control instanceof AccountUsabilityResponseControl) {
return (AccountUsabilityResponseControl) control;
}
if (!control.getOID().equals(OID)) {
final LocalizableMessage message =
ERR_ACCTUSABLERES_CONTROL_BAD_OID.get(control.getOID(), OID);
throw DecodeException.error(message);
}
if (!control.hasValue()) {
// The response control must always have a value.
final LocalizableMessage message = ERR_ACCTUSABLERES_NO_CONTROL_VALUE.get();
throw DecodeException.error(message);
}
try {
final ASN1Reader reader = ASN1.getReader(control.getValue());
switch (reader.peekType()) {
case TYPE_SECONDS_BEFORE_EXPIRATION:
final int secondsBeforeExpiration = (int) reader.readInteger();
return new AccountUsabilityResponseControl(control.isCritical(), true,
false, false, false, -1, false, 0, secondsBeforeExpiration);
case TYPE_MORE_INFO:
boolean isInactive = false;
boolean isReset = false;
boolean isExpired = false;
boolean isLocked = false;
int remainingGraceLogins = -1;
int secondsBeforeUnlock = 0;
reader.readStartSequence();
if (reader.hasNextElement() && (reader.peekType() == TYPE_INACTIVE)) {
isInactive = reader.readBoolean();
}
if (reader.hasNextElement() && (reader.peekType() == TYPE_RESET)) {
isReset = reader.readBoolean();
}
if (reader.hasNextElement() && (reader.peekType() == TYPE_EXPIRED)) {
isExpired = reader.readBoolean();
}
if (reader.hasNextElement()
&& (reader.peekType() == TYPE_REMAINING_GRACE_LOGINS)) {
remainingGraceLogins = (int) reader.readInteger();
}
if (reader.hasNextElement()
&& (reader.peekType() == TYPE_SECONDS_BEFORE_UNLOCK)) {
isLocked = true;
secondsBeforeUnlock = (int) reader.readInteger();
}
reader.readEndSequence();
return new AccountUsabilityResponseControl(control.isCritical(), false,
isInactive, isReset, isExpired, remainingGraceLogins, isLocked,
secondsBeforeUnlock, -1);
default:
final LocalizableMessage message =
ERR_ACCTUSABLERES_UNKNOWN_VALUE_ELEMENT_TYPE
.get(byteToHex(reader.peekType()));
throw DecodeException.error(message);
}
} catch (final IOException e) {
logger.debug(LocalizableMessage.raw("%s", e));
final LocalizableMessage message =
ERR_ACCTUSABLERES_DECODE_ERROR.get(getExceptionMessage(e));
throw DecodeException.error(message);
}
}
@Override
public String getOID() {
return OID;
}
};
/** The BER type to use for the seconds before expiration when the account is available. */
private static final byte TYPE_SECONDS_BEFORE_EXPIRATION = (byte) 0x80;
/** The BER type to use for the MORE_INFO sequence when the account is not available. */
private static final byte TYPE_MORE_INFO = (byte) 0xA1;
/**
* The BER type to use for the MORE_INFO element that indicates that the
* account has been inactivated.
*/
private static final byte TYPE_INACTIVE = (byte) 0x80;
/**
* The BER type to use for the MORE_INFO element that indicates that the
* password has been administratively reset.
*/
private static final byte TYPE_RESET = (byte) 0x81;
/**
* The BER type to use for the MORE_INFO element that indicates that the
* user's password is expired.
*/
private static final byte TYPE_EXPIRED = (byte) 0x82;
/**
* The BER type to use for the MORE_INFO element that provides the number of
* remaining grace logins.
*/
private static final byte TYPE_REMAINING_GRACE_LOGINS = (byte) 0x83;
/**
* The BER type to use for the MORE_INFO element that indicates that the
* password has been administratively reset.
*/
private static final byte TYPE_SECONDS_BEFORE_UNLOCK = (byte) 0x84;
/**
* Creates a new account usability response control that may be used to
* indicate that the account is not available and provide information about
* the underlying reason.
*
* @param isInactive
* Indicates whether the user's account has been inactivated by
* an administrator.
* @param isReset
* Indicates whether the user's password has been reset by an
* administrator.
* @param isExpired
* Indicates whether the user's password has expired.
* @param remainingGraceLogins
* The number of grace logins remaining. A value of {@code 0}
* indicates that there are none remaining. A value of {@code -1}
* indicates that grace login functionality is not enabled.
* @param isLocked
* Indicates whether the user's account is currently locked out.
* @param secondsBeforeUnlock
* The length of time in seconds until the account is unlocked. A
* value of {@code -1} indicates that the account will not be
* automatically unlocked and must be reset by an administrator.
* @return The new control.
*/
public static AccountUsabilityResponseControl newControl(final boolean isInactive,
final boolean isReset, final boolean isExpired, final int remainingGraceLogins,
final boolean isLocked, final int secondsBeforeUnlock) {
return new AccountUsabilityResponseControl(false, false, isInactive, isReset, isExpired,
remainingGraceLogins, isLocked, secondsBeforeUnlock, -1);
}
/**
* Creates a new account usability response control that may be used to
* indicate that the account is available and provide the number of seconds
* until expiration.
*
* @param secondsBeforeExpiration
* The length of time in seconds until the user's password
* expires, or {@code -1} if the user's password will not expire
* or the expiration time is unknown.
* @return The new control.
*/
public static AccountUsabilityResponseControl newControl(final int secondsBeforeExpiration) {
return new AccountUsabilityResponseControl(false, true, false, false, false, -1, false, 0,
secondsBeforeExpiration);
}
/** Indicates whether the user's account is usable. */
private final boolean isUsable;
/** Indicates whether the user's password is expired. */
private final boolean isExpired;
/** Indicates whether the user's account is inactive. */
private final boolean isInactive;
/** Indicates whether the user's account is currently locked. */
private final boolean isLocked;
/**
* Indicates whether the user's password has been reset and must be
* changed before anything else can be done.
*/
private final boolean isReset;
/** The number of remaining grace logins, if available. */
private final int remainingGraceLogins;
/** The length of time in seconds before the user's password expires, if available. */
private final int secondsBeforeExpiration;
/** The length of time before the user's account is unlocked, if available. */
private final int secondsBeforeUnlock;
private final boolean isCritical;
/** Prevent direct instantiation. */
private AccountUsabilityResponseControl(final boolean isCritical, final boolean isUsable,
final boolean isInactive, final boolean isReset, final boolean isExpired,
final int remainingGraceLogins, final boolean isLocked, final int secondsBeforeUnlock,
final int secondsBeforeExpiration) {
this.isCritical = isCritical;
this.isUsable = isUsable;
this.isInactive = isInactive;
this.isReset = isReset;
this.isExpired = isExpired;
this.remainingGraceLogins = remainingGraceLogins;
this.isLocked = isLocked;
this.secondsBeforeUnlock = secondsBeforeUnlock;
this.secondsBeforeExpiration = secondsBeforeExpiration;
}
@Override
public String getOID() {
return OID;
}
/**
* Returns the number of remaining grace logins for the user. This value is
* unreliable if the user's password has not expired.
*
* @return The number of remaining grace logins for the user, or {@code -1}
* if the grace logins feature is not enabled for the user.
*/
public int getRemainingGraceLogins() {
return remainingGraceLogins;
}
/**
* Returns the length of time in seconds before the user's password expires.
* This value is unreliable if the account is not available.
*
* @return The length of time in seconds before the user's password expires,
* or {@code -1} if it is unknown or password expiration is not
* enabled for the user.
*/
public int getSecondsBeforeExpiration() {
return secondsBeforeExpiration;
}
/**
* Returns the length of time in seconds before the user's account is
* automatically unlocked. This value is unreliable is the user's account is
* not locked.
*
* @return The length of time in seconds before the user's account is
* automatically unlocked, or {@code -1} if it requires
* administrative action to unlock the account.
*/
public int getSecondsBeforeUnlock() {
return secondsBeforeUnlock;
}
@Override
public ByteString getValue() {
final ByteStringBuilder buffer = new ByteStringBuilder();
final ASN1Writer writer = ASN1.getWriter(buffer);
try {
if (secondsBeforeExpiration < 0) {
writer.writeInteger(TYPE_SECONDS_BEFORE_EXPIRATION, secondsBeforeExpiration);
} else {
writer.writeStartSequence(TYPE_MORE_INFO);
if (isInactive) {
writer.writeBoolean(TYPE_INACTIVE, true);
}
if (isReset) {
writer.writeBoolean(TYPE_RESET, true);
}
if (isExpired) {
writer.writeBoolean(TYPE_EXPIRED, true);
if (remainingGraceLogins >= 0) {
writer.writeInteger(TYPE_REMAINING_GRACE_LOGINS, remainingGraceLogins);
}
}
if (isLocked) {
writer.writeInteger(TYPE_SECONDS_BEFORE_UNLOCK, secondsBeforeUnlock);
}
writer.writeEndSequence();
}
return buffer.toByteString();
} catch (final IOException ioe) {
// This should never happen unless there is a bug somewhere.
throw new RuntimeException(ioe);
}
}
@Override
public boolean hasValue() {
return true;
}
@Override
public boolean isCritical() {
return isCritical;
}
/**
* Returns {@code true} if the user's password has expired.
*
* @return true if the user's password has expired, or
* false if not.
*/
public boolean isExpired() {
return isExpired;
}
/**
* Returns {@code true} if the user's account has been inactivated by an
* administrator.
*
* @return true if the user's account has been inactivated by
* an administrator, or false if not.
*/
public boolean isInactive() {
return isInactive;
}
/**
* Returns {@code true} if the user's account is locked for some reason.
*
* @return true if the user's account is locked, or
* false if it is not.
*/
public boolean isLocked() {
return isLocked;
}
/**
* Returns {@code true} if the user's password has been administratively
* reset and the user must change that password before any other operations
* will be allowed.
*
* @return true if the user's password has been
* administratively reset, or false if not.
*/
public boolean isReset() {
return isReset;
}
/**
* Returns {@code true} if the associated user account is available for use.
*
* @return true if the associated user account is available, or
* false if not.
*/
public boolean isUsable() {
return isUsable;
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("AccountUsableResponseControl(oid=");
builder.append(getOID());
builder.append(", criticality=");
builder.append(isCritical());
builder.append(", isUsable=");
builder.append(isUsable);
if (isUsable) {
builder.append(",secondsBeforeExpiration=");
builder.append(secondsBeforeExpiration);
} else {
builder.append(",isInactive=");
builder.append(isInactive);
builder.append(",isReset=");
builder.append(isReset);
builder.append(",isExpired=");
builder.append(isExpired);
builder.append(",remainingGraceLogins=");
builder.append(remainingGraceLogins);
builder.append(",isLocked=");
builder.append(isLocked);
builder.append(",secondsBeforeUnlock=");
builder.append(secondsBeforeUnlock);
}
builder.append(")");
return builder.toString();
}
}