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