001    /*
002     * Copyright 2007-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2015 UnboundID Corp.
007     *
008     * This program is free software; you can redistribute it and/or modify
009     * it under the terms of the GNU General Public License (GPLv2 only)
010     * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011     * as published by the Free Software Foundation.
012     *
013     * This program is distributed in the hope that it will be useful,
014     * but WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program; if not, see <http://www.gnu.org/licenses>.
020     */
021    package com.unboundid.ldap.sdk.controls;
022    
023    
024    
025    import com.unboundid.asn1.ASN1OctetString;
026    import com.unboundid.ldap.sdk.Control;
027    import com.unboundid.ldap.sdk.DecodeableControl;
028    import com.unboundid.ldap.sdk.LDAPException;
029    import com.unboundid.ldap.sdk.LDAPResult;
030    import com.unboundid.ldap.sdk.ResultCode;
031    import com.unboundid.util.NotMutable;
032    import com.unboundid.util.ThreadSafety;
033    import com.unboundid.util.ThreadSafetyLevel;
034    
035    import static com.unboundid.ldap.sdk.controls.ControlMessages.*;
036    import static com.unboundid.util.Debug.*;
037    
038    
039    
040    /**
041     * This class provides an implementation of the password expired control as
042     * described in draft-vchu-ldap-pwd-policy.  It may be included in the response
043     * for an unsuccessful bind operation to indicate that the reason for the
044     * failure is that the target user's password has expired and must be reset
045     * before the user will be allowed to authenticate.  Some servers may also
046     * include this control in a successful bind response to indicate that the
047     * authenticated user must change his or her password before being allowed to
048     * perform any other operation.
049     * <BR><BR>
050     * No request control is required to trigger the server to send the password
051     * expired response control.  If the server supports the use of this control and
052     * the corresponding bind operation meets the criteria for this control to be
053     * included in the response, then it will be returned to the client.
054     * <BR><BR>
055     * <H2>Example</H2>
056     * The following example demonstrates a process that may be used to perform a
057     * simple bind to authenticate against the server and handle any password
058     * expired or password expiring control that may be included in the response:
059     * <PRE>
060     * // Send a simple bind request to the directory server.
061     * BindRequest bindRequest =
062     *      new SimpleBindRequest("uid=test.user,ou=People,dc=example,dc=com",
063     *           "password");
064     * BindResult bindResult;
065     * boolean bindSuccessful;
066     * boolean passwordExpired;
067     * boolean passwordAboutToExpire;
068     * try
069     * {
070     *   bindResult = connection.bind(bindRequest);
071     *
072     *   // If we got here, the bind was successful and we know the password was
073     *   // not expired.  However, we shouldn't ignore the result because the
074     *   // password might be about to expire.  To determine whether that is the
075     *   // case, we should see if the bind result included a password expiring
076     *   // control.
077     *   bindSuccessful = true;
078     *   passwordExpired = false;
079     *
080     *   PasswordExpiringControl expiringControl =
081     *        PasswordExpiringControl.get(bindResult);
082     *   if (expiringControl != null)
083     *   {
084     *     passwordAboutToExpire = true;
085     *     int secondsToExpiration = expiringControl.getSecondsUntilExpiration();
086     *   }
087     *   else
088     *   {
089     *     passwordAboutToExpire = false;
090     *   }
091     * }
092     * catch (LDAPException le)
093     * {
094     *   // If we got here, then the bind failed.  The failure may or may not have
095     *   // been due to an expired password.  To determine that, we should see if
096     *   // the bind result included a password expired control.
097     *   bindSuccessful = false;
098     *   passwordAboutToExpire = false;
099     *   bindResult = new BindResult(le.toLDAPResult());
100     *   ResultCode resultCode = le.getResultCode();
101     *   String errorMessageFromServer = le.getDiagnosticMessage();
102     *
103     *   PasswordExpiredControl expiredControl =
104     *        PasswordExpiredControl.get(le);
105     *   if (expiredControl != null)
106     *   {
107     *     passwordExpired = true;
108     *   }
109     *   else
110     *   {
111     *     passwordExpired = false;
112     *   }
113     * }
114     * </PRE>
115     */
116    @NotMutable()
117    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
118    public final class PasswordExpiredControl
119           extends Control
120           implements DecodeableControl
121    {
122      /**
123       * The OID (2.16.840.1.113730.3.4.4) for the password expired response
124       * control.
125       */
126      public static final String PASSWORD_EXPIRED_OID = "2.16.840.1.113730.3.4.4";
127    
128    
129    
130      /**
131       * The serial version UID for this serializable class.
132       */
133      private static final long serialVersionUID = -2731704592689892224L;
134    
135    
136    
137      /**
138       * Creates a new password expired control.
139       */
140      public PasswordExpiredControl()
141      {
142        super(PASSWORD_EXPIRED_OID, false, new ASN1OctetString("0"));
143      }
144    
145    
146    
147      /**
148       * Creates a new password expired control with the provided information.
149       *
150       * @param  oid         The OID for the control.
151       * @param  isCritical  Indicates whether the control should be marked
152       *                     critical.
153       * @param  value       The encoded value for the control.  This may be
154       *                     {@code null} if no value was provided.
155       *
156       * @throws  LDAPException  If the provided control cannot be decoded as a
157       *                         password expired response control.
158       */
159      public PasswordExpiredControl(final String oid, final boolean isCritical,
160                                    final ASN1OctetString value)
161             throws LDAPException
162      {
163        super(oid, isCritical, value);
164    
165        if (value == null)
166        {
167          throw new LDAPException(ResultCode.DECODING_ERROR,
168                                  ERR_PW_EXPIRED_NO_VALUE.get());
169        }
170    
171        try
172        {
173          Integer.parseInt(value.stringValue());
174        }
175        catch (NumberFormatException nfe)
176        {
177          debugException(nfe);
178          throw new LDAPException(ResultCode.DECODING_ERROR,
179                                  ERR_PW_EXPIRED_VALUE_NOT_INTEGER.get(), nfe);
180        }
181      }
182    
183    
184    
185      /**
186       * {@inheritDoc}
187       */
188      public PasswordExpiredControl
189                  decodeControl(final String oid, final boolean isCritical,
190                                final ASN1OctetString value)
191             throws LDAPException
192      {
193        return new PasswordExpiredControl(oid, isCritical, value);
194      }
195    
196    
197    
198      /**
199       * Extracts a password expired control from the provided result.
200       *
201       * @param  result  The result from which to retrieve the password expired
202       *                 control.
203       *
204       * @return  The password expired control contained in the provided result, or
205       *          {@code null} if the result did not contain a password expired
206       *          control.
207       *
208       * @throws  LDAPException  If a problem is encountered while attempting to
209       *                         decode the password expired control contained in
210       *                         the provided result.
211       */
212      public static PasswordExpiredControl get(final LDAPResult result)
213             throws LDAPException
214      {
215        final Control c = result.getResponseControl(PASSWORD_EXPIRED_OID);
216        if (c == null)
217        {
218          return null;
219        }
220    
221        if (c instanceof PasswordExpiredControl)
222        {
223          return (PasswordExpiredControl) c;
224        }
225        else
226        {
227          return new PasswordExpiredControl(c.getOID(), c.isCritical(),
228               c.getValue());
229        }
230      }
231    
232    
233    
234      /**
235       * Extracts a password expired control from the provided exception.
236       *
237       * @param  exception  The exception from which to retrieve the password
238       *                    expired control.
239       *
240       * @return  The password expired control contained in the provided exception,
241       *          or {@code null} if the exception did not contain a password
242       *          expired control.
243       *
244       * @throws  LDAPException  If a problem is encountered while attempting to
245       *                         decode the password expired control contained in
246       *                         the provided exception.
247       */
248      public static PasswordExpiredControl get(final LDAPException exception)
249             throws LDAPException
250      {
251        return get(exception.toLDAPResult());
252      }
253    
254    
255    
256      /**
257       * {@inheritDoc}
258       */
259      @Override()
260      public String getControlName()
261      {
262        return INFO_CONTROL_NAME_PW_EXPIRED.get();
263      }
264    
265    
266    
267      /**
268       * {@inheritDoc}
269       */
270      @Override()
271      public void toString(final StringBuilder buffer)
272      {
273        buffer.append("PasswordExpiredControl(isCritical=");
274        buffer.append(isCritical());
275        buffer.append(')');
276      }
277    }