001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2010 Sun Microsystems, Inc.
015 * Portions copyright 2013-2016 ForgeRock AS.
016 */
017package org.forgerock.opendj.ldap.controls;
018
019import static com.forgerock.opendj.util.StaticUtils.getExceptionMessage;
020import static com.forgerock.opendj.ldap.CoreMessages.*;
021
022import org.forgerock.i18n.LocalizableMessage;
023import org.forgerock.i18n.slf4j.LocalizedLogger;
024import org.forgerock.opendj.ldap.ByteString;
025import org.forgerock.opendj.ldap.DecodeException;
026import org.forgerock.opendj.ldap.DecodeOptions;
027import org.forgerock.util.Reject;
028
029/**
030 * The Netscape password expiring response control as defined in
031 * draft-vchu-ldap-pwd-policy. This control serves as a warning to clients that
032 * the user's password is about to expire. The only element contained in the
033 * control value is a string representation of the number of seconds until
034 * expiration.
035 *
036 * <pre>
037 * Connection connection = ...;
038 * String DN = ...;
039 * char[] password = ...;
040 *
041 * BindResult result = connection.bind(DN, password);
042 * try {
043 *     PasswordExpiringResponseControl control =
044 *             result.getControl(PasswordExpiringResponseControl.DECODER,
045 *                     new DecodeOptions());
046 *     if (!(control == null) && control.hasValue()) {
047 *         // Password expires in control.getSecondsUntilExpiration() seconds
048 *     }
049 * } catch (DecodeException de) {
050 *     // Failed to decode the response control.
051 * }
052 * </pre>
053 *
054 * @see <a
055 *      href="http://tools.ietf.org/html/draft-vchu-ldap-pwd-policy">draft-vchu-ldap-pwd-policy
056 *      - Password Policy for LDAP Directories </a>
057 */
058public final class PasswordExpiringResponseControl implements Control {
059
060    private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
061    /** The OID for the Netscape password expiring response control. */
062    public static final String OID = "2.16.840.1.113730.3.4.5";
063
064    /** A decoder which can be used for decoding the password expiring response control. */
065    public static final ControlDecoder<PasswordExpiringResponseControl> DECODER =
066            new ControlDecoder<PasswordExpiringResponseControl>() {
067
068                @Override
069                public PasswordExpiringResponseControl decodeControl(final Control control,
070                        final DecodeOptions options) throws DecodeException {
071                    Reject.ifNull(control);
072
073                    if (control instanceof PasswordExpiringResponseControl) {
074                        return (PasswordExpiringResponseControl) control;
075                    }
076
077                    if (!control.getOID().equals(OID)) {
078                        final LocalizableMessage message =
079                                ERR_PWEXPIRING_CONTROL_BAD_OID.get(control.getOID(), OID);
080                        throw DecodeException.error(message);
081                    }
082
083                    if (!control.hasValue()) {
084                        final LocalizableMessage message = ERR_PWEXPIRING_NO_CONTROL_VALUE.get();
085                        throw DecodeException.error(message);
086                    }
087
088                    int secondsUntilExpiration;
089                    try {
090                        secondsUntilExpiration = Integer.parseInt(control.getValue().toString());
091                    } catch (final Exception e) {
092                        logger.debug(LocalizableMessage.raw("%s", e));
093
094                        final LocalizableMessage message =
095                                ERR_PWEXPIRING_CANNOT_DECODE_SECONDS_UNTIL_EXPIRATION
096                                        .get(getExceptionMessage(e));
097                        throw DecodeException.error(message);
098                    }
099
100                    return new PasswordExpiringResponseControl(control.isCritical(),
101                            secondsUntilExpiration);
102                }
103
104                @Override
105                public String getOID() {
106                    return OID;
107                }
108            };
109
110    /**
111     * Creates a new Netscape password expiring response control with the
112     * provided amount of time until expiration.
113     *
114     * @param secondsUntilExpiration
115     *            The length of time in seconds until the password actually
116     *            expires.
117     * @return The new control.
118     */
119    public static PasswordExpiringResponseControl newControl(final int secondsUntilExpiration) {
120        return new PasswordExpiringResponseControl(false, secondsUntilExpiration);
121    }
122
123    /** The length of time in seconds until the password actually expires. */
124    private final int secondsUntilExpiration;
125
126    private final boolean isCritical;
127
128    private PasswordExpiringResponseControl(final boolean isCritical,
129            final int secondsUntilExpiration) {
130        this.isCritical = isCritical;
131        this.secondsUntilExpiration = secondsUntilExpiration;
132    }
133
134    @Override
135    public String getOID() {
136        return OID;
137    }
138
139    /**
140     * Returns the length of time in seconds until the password actually
141     * expires.
142     *
143     * @return The length of time in seconds until the password actually
144     *         expires.
145     */
146    public int getSecondsUntilExpiration() {
147        return secondsUntilExpiration;
148    }
149
150    @Override
151    public ByteString getValue() {
152        return ByteString.valueOfUtf8(String.valueOf(secondsUntilExpiration));
153    }
154
155    @Override
156    public boolean hasValue() {
157        return true;
158    }
159
160    @Override
161    public boolean isCritical() {
162        return isCritical;
163    }
164
165    @Override
166    public String toString() {
167        final StringBuilder builder = new StringBuilder();
168        builder.append("PasswordExpiringResponseControl(oid=");
169        builder.append(getOID());
170        builder.append(", criticality=");
171        builder.append(isCritical());
172        builder.append(", secondsUntilExpiration=");
173        builder.append(secondsUntilExpiration);
174        builder.append(")");
175        return builder.toString();
176    }
177}