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 2014-2016 ForgeRock AS.
016 */
017package org.forgerock.opendj.ldap.controls;
018
019import static com.forgerock.opendj.ldap.CoreMessages.ERR_PWEXPIRED_CONTROL_BAD_OID;
020import static com.forgerock.opendj.ldap.CoreMessages.ERR_PWEXPIRED_CONTROL_INVALID_VALUE;
021
022import org.forgerock.i18n.LocalizableMessage;
023import org.forgerock.opendj.ldap.ByteString;
024import org.forgerock.opendj.ldap.DecodeException;
025import org.forgerock.opendj.ldap.DecodeOptions;
026
027import org.forgerock.util.Reject;
028
029/**
030 * The Netscape password expired response control as defined in
031 * draft-vchu-ldap-pwd-policy. This control indicates to a client that their
032 * password has expired and must be changed. This control always has a value
033 * which is the string {@code "0"}.
034 *
035 * <pre>
036 * Connection connection = ...;
037 * String DN = ...;
038 * char[] password = ...;
039 *
040 * try {
041 *     connection.bind(DN, password);
042 * } catch (LdapException e) {
043 *     Result result = e.getResult();
044 *     try {
045 *         PasswordExpiredResponseControl control =
046 *                 result.getControl(PasswordExpiredResponseControl.DECODER,
047 *                         new DecodeOptions());
048 *         if (!(control == null) && control.hasValue()) {
049 *             // Password expired
050 *         }
051 *     } catch (DecodeException de) {
052 *         // Failed to decode the response control.
053 *     }
054 * }
055 * </pre>
056 *
057 * @see <a
058 *      href="http://tools.ietf.org/html/draft-vchu-ldap-pwd-policy">draft-vchu-ldap-pwd-policy
059 *      - Password Policy for LDAP Directories </a>
060 */
061public final class PasswordExpiredResponseControl implements Control {
062    /** The OID for the Netscape password expired response control. */
063    public static final String OID = "2.16.840.1.113730.3.4.4";
064
065    private final boolean isCritical;
066
067    private static final PasswordExpiredResponseControl CRITICAL_INSTANCE =
068            new PasswordExpiredResponseControl(true);
069    private static final PasswordExpiredResponseControl NONCRITICAL_INSTANCE =
070            new PasswordExpiredResponseControl(false);
071
072    /** A decoder which can be used for decoding the password expired response control. */
073    public static final ControlDecoder<PasswordExpiredResponseControl> DECODER =
074            new ControlDecoder<PasswordExpiredResponseControl>() {
075
076                @Override
077                public PasswordExpiredResponseControl decodeControl(final Control control,
078                        final DecodeOptions options) throws DecodeException {
079                    Reject.ifNull(control);
080
081                    if (control instanceof PasswordExpiredResponseControl) {
082                        return (PasswordExpiredResponseControl) control;
083                    }
084
085                    if (!control.getOID().equals(OID)) {
086                        final LocalizableMessage message =
087                                ERR_PWEXPIRED_CONTROL_BAD_OID.get(control.getOID(), OID);
088                        throw DecodeException.error(message);
089                    }
090
091                    if (control.hasValue()) {
092                        try {
093                            Integer.parseInt(control.getValue().toString());
094                        } catch (final Exception e) {
095                            final LocalizableMessage message =
096                                    ERR_PWEXPIRED_CONTROL_INVALID_VALUE.get();
097                            throw DecodeException.error(message);
098                        }
099                    }
100
101                    return control.isCritical() ? CRITICAL_INSTANCE : NONCRITICAL_INSTANCE;
102                }
103
104                @Override
105                public String getOID() {
106                    return OID;
107                }
108            };
109
110    private static final ByteString CONTROL_VALUE = ByteString.valueOfUtf8("0");
111
112    /**
113     * Creates a new Netscape password expired response control.
114     *
115     * @return The new control.
116     */
117    public static PasswordExpiredResponseControl newControl() {
118        return NONCRITICAL_INSTANCE;
119    }
120
121    private PasswordExpiredResponseControl(final boolean isCritical) {
122        this.isCritical = isCritical;
123    }
124
125    @Override
126    public String getOID() {
127        return OID;
128    }
129
130    @Override
131    public ByteString getValue() {
132        return CONTROL_VALUE;
133    }
134
135    @Override
136    public boolean hasValue() {
137        return true;
138    }
139
140    @Override
141    public boolean isCritical() {
142        return isCritical;
143    }
144
145    @Override
146    public String toString() {
147        final StringBuilder builder = new StringBuilder();
148        builder.append("PasswordExpiredResponseControl(oid=");
149        builder.append(getOID());
150        builder.append(", criticality=");
151        builder.append(isCritical());
152        builder.append(")");
153        return builder.toString();
154    }
155}