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