001    /*
002     * Copyright 2007-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 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.unboundidds.controls;
022    
023    
024    
025    import java.util.ArrayList;
026    
027    import com.unboundid.asn1.ASN1Element;
028    import com.unboundid.asn1.ASN1Enumerated;
029    import com.unboundid.asn1.ASN1Exception;
030    import com.unboundid.asn1.ASN1Integer;
031    import com.unboundid.asn1.ASN1OctetString;
032    import com.unboundid.asn1.ASN1Sequence;
033    import com.unboundid.ldap.sdk.Control;
034    import com.unboundid.ldap.sdk.DecodeableControl;
035    import com.unboundid.ldap.sdk.LDAPException;
036    import com.unboundid.ldap.sdk.LDAPResult;
037    import com.unboundid.ldap.sdk.ResultCode;
038    import com.unboundid.util.NotMutable;
039    import com.unboundid.util.ThreadSafety;
040    import com.unboundid.util.ThreadSafetyLevel;
041    
042    import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
043    import static com.unboundid.util.Debug.*;
044    import static com.unboundid.util.StaticUtils.*;
045    
046    
047    
048    /**
049     * <BLOCKQUOTE>
050     *   <B>NOTE:</B>  This class is part of the Commercial Edition of the UnboundID
051     *   LDAP SDK for Java.  It is not available for use in applications that
052     *   include only the Standard Edition of the LDAP SDK, and is not supported for
053     *   use in conjunction with non-UnboundID products.
054     * </BLOCKQUOTE>
055     * This class provides an implementation of the password policy response control
056     * as described in draft-behera-ldap-password-policy.  It may be used to provide
057     * information related to a user's password policy.  It may include at most one
058     * warning from the set of {@link PasswordPolicyWarningType} values and at most
059     * one error from the set of {@link PasswordPolicyErrorType} values.  See the
060     * documentation for those classes for more information on the information that
061     * may be included.  See the {@link PasswordPolicyRequestControl} documentation
062     * for an example that demonstrates the use of the password policy request and
063     * response controls.
064     */
065    @NotMutable()
066    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
067    public final class PasswordPolicyResponseControl
068           extends Control
069           implements DecodeableControl
070    {
071      /**
072       * The OID (1.3.6.1.4.1.42.2.27.8.5.1) for the password policy response
073       * control.
074       */
075      public static final String PASSWORD_POLICY_RESPONSE_OID =
076           "1.3.6.1.4.1.42.2.27.8.5.1";
077    
078    
079    
080      /**
081       * The BER type for the password policy warning element.
082       */
083      private static final byte TYPE_WARNING = (byte) 0xA0;
084    
085    
086    
087      /**
088       * The BER type for the password policy error element.
089       */
090      private static final byte TYPE_ERROR = (byte) 0x81;
091    
092    
093    
094      /**
095       * The BER type for the "time before expiration" warning element.
096       */
097      private static final byte TYPE_TIME_BEFORE_EXPIRATION = (byte) 0x80;
098    
099    
100    
101      /**
102       * The BER type for the "grace logins remaining" warning element.
103       */
104      private static final byte TYPE_GRACE_LOGINS_REMAINING = (byte) 0x81;
105    
106    
107    
108      /**
109       * The serial version UID for this serializable class.
110       */
111      private static final long serialVersionUID = 1835830253434331833L;
112    
113    
114    
115      // The password policy warning value, if applicable.
116      private final int warningValue;
117    
118      // The password policy error type, if applicable.
119      private final PasswordPolicyErrorType errorType;
120    
121      // The password policy warning type, if applicable.
122      private final PasswordPolicyWarningType warningType;
123    
124    
125    
126      /**
127       * Creates a new empty control instance that is intended to be used only for
128       * decoding controls via the {@code DecodeableControl} interface.
129       */
130      PasswordPolicyResponseControl()
131      {
132        warningType  = null;
133        errorType    = null;
134        warningValue = -1;
135      }
136    
137    
138    
139      /**
140       * Creates a new password policy response control with the provided
141       * information.  It will not be critical.
142       *
143       * @param  warningType   The password policy warning type for this response
144       *                       control, or {@code null} if there should be no
145       *                       warning type.
146       * @param  warningValue  The value for the password policy warning type, or -1
147       *                       if there is no warning type.
148       * @param  errorType     The password policy error type for this response
149       *                       control, or {@code null} if there should be no error
150       *                       type.
151       */
152      public PasswordPolicyResponseControl(
153                  final PasswordPolicyWarningType warningType,
154                  final int warningValue, final PasswordPolicyErrorType errorType)
155      {
156        this(warningType, warningValue, errorType, false);
157      }
158    
159    
160    
161      /**
162       * Creates a new password policy response control with the provided
163       * information.
164       *
165       * @param  warningType   The password policy warning type for this response
166       *                       control, or {@code null} if there should be no
167       *                       warning type.
168       * @param  warningValue  The value for the password policy warning type, or -1
169       *                       if there is no warning type.
170       * @param  errorType     The password policy error type for this response
171       *                       control, or {@code null} if there should be no error
172       *                       type.
173       * @param  isCritical    Indicates whether this control should be marked
174       *                       critical.
175       */
176      public PasswordPolicyResponseControl(
177                  final PasswordPolicyWarningType warningType,
178                  final int warningValue, final PasswordPolicyErrorType errorType,
179                  final boolean isCritical)
180      {
181        super(PASSWORD_POLICY_RESPONSE_OID, isCritical,
182              encodeValue(warningType, warningValue, errorType));
183    
184        this.warningType = warningType;
185        this.errorType   = errorType;
186    
187        if (warningType == null)
188        {
189          this.warningValue = -1;
190        }
191        else
192        {
193          this.warningValue = warningValue;
194        }
195      }
196    
197    
198    
199      /**
200       * Creates a new password policy response control with the provided
201       * information.
202       *
203       * @param  oid         The OID for the control.
204       * @param  isCritical  Indicates whether the control should be marked
205       *                     critical.
206       * @param  value       The encoded value for the control.  This may be
207       *                     {@code null} if no value was provided.
208       *
209       * @throws  LDAPException  If the provided control cannot be decoded as a
210       *                         password policy response control.
211       */
212      public PasswordPolicyResponseControl(final String oid,
213                                           final boolean isCritical,
214                                           final ASN1OctetString value)
215             throws LDAPException
216      {
217        super(oid, isCritical, value);
218    
219        if (value == null)
220        {
221          throw new LDAPException(ResultCode.DECODING_ERROR,
222                                  ERR_PWP_RESPONSE_NO_VALUE.get());
223        }
224    
225        final ASN1Sequence valueSequence;
226        try
227        {
228          final ASN1Element valueElement = ASN1Element.decode(value.getValue());
229          valueSequence = ASN1Sequence.decodeAsSequence(valueElement);
230        }
231        catch (ASN1Exception ae)
232        {
233          debugException(ae);
234          throw new LDAPException(ResultCode.DECODING_ERROR,
235                                  ERR_PWP_RESPONSE_VALUE_NOT_SEQUENCE.get(ae), ae);
236        }
237    
238        final ASN1Element[] valueElements = valueSequence.elements();
239        if (valueElements.length > 2)
240        {
241          throw new LDAPException(ResultCode.DECODING_ERROR,
242                                  ERR_PWP_RESPONSE_INVALID_ELEMENT_COUNT.get(
243                                       valueElements.length));
244        }
245    
246        int                       wv = -1;
247        PasswordPolicyErrorType   et = null;
248        PasswordPolicyWarningType wt = null;
249        for (final ASN1Element e : valueElements)
250        {
251          switch (e.getType())
252          {
253            case TYPE_WARNING:
254              if (wt == null)
255              {
256                try
257                {
258                  final ASN1Element warningElement =
259                       ASN1Element.decode(e.getValue());
260                  wv = ASN1Integer.decodeAsInteger(warningElement).intValue();
261                  switch (warningElement.getType())
262                  {
263                    case TYPE_TIME_BEFORE_EXPIRATION:
264                      wt = PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION;
265                      break;
266    
267                    case TYPE_GRACE_LOGINS_REMAINING:
268                      wt = PasswordPolicyWarningType.GRACE_LOGINS_REMAINING;
269                      break;
270    
271                    default:
272                      throw new LDAPException(ResultCode.DECODING_ERROR,
273                                     ERR_PWP_RESPONSE_INVALID_WARNING_TYPE.get(
274                                          toHex(warningElement.getType())));
275                  }
276                }
277                catch (ASN1Exception ae)
278                {
279                  debugException(ae);
280                  throw new LDAPException(ResultCode.DECODING_ERROR,
281                                 ERR_PWP_RESPONSE_CANNOT_DECODE_WARNING.get(ae),
282                                 ae);
283                }
284              }
285              else
286              {
287                throw new LDAPException(ResultCode.DECODING_ERROR,
288                                        ERR_PWP_RESPONSE_MULTIPLE_WARNING.get());
289              }
290              break;
291    
292            case TYPE_ERROR:
293              if (et == null)
294              {
295                try
296                {
297                  final ASN1Enumerated errorElement =
298                       ASN1Enumerated.decodeAsEnumerated(e);
299                  et = PasswordPolicyErrorType.valueOf(errorElement.intValue());
300                  if (et == null)
301                  {
302                      throw new LDAPException(ResultCode.DECODING_ERROR,
303                                     ERR_PWP_RESPONSE_INVALID_ERROR_TYPE.get(
304                                          errorElement.intValue()));
305                  }
306                }
307                catch (ASN1Exception ae)
308                {
309                  debugException(ae);
310                  throw new LDAPException(ResultCode.DECODING_ERROR,
311                                 ERR_PWP_RESPONSE_CANNOT_DECODE_ERROR.get(ae), ae);
312                }
313              }
314              else
315              {
316                throw new LDAPException(ResultCode.DECODING_ERROR,
317                                        ERR_PWP_RESPONSE_MULTIPLE_ERROR.get());
318              }
319              break;
320    
321            default:
322              throw new LDAPException(ResultCode.DECODING_ERROR,
323                                      ERR_PWP_RESPONSE_INVALID_TYPE.get(
324                                           toHex(e.getType())));
325          }
326        }
327    
328        warningType  = wt;
329        warningValue = wv;
330        errorType    = et;
331      }
332    
333    
334    
335      /**
336       * {@inheritDoc}
337       */
338      public PasswordPolicyResponseControl
339                  decodeControl(final String oid, final boolean isCritical,
340                                final ASN1OctetString value)
341             throws LDAPException
342      {
343        return new PasswordPolicyResponseControl(oid, isCritical, value);
344      }
345    
346    
347    
348      /**
349       * Extracts a password policy response control from the provided result.
350       *
351       * @param  result  The result from which to retrieve the password policy
352       *                 response control.
353       *
354       * @return  The password policy response control contained in the provided
355       *          result, or {@code null} if the result did not contain a password
356       *          policy response control.
357       *
358       * @throws  LDAPException  If a problem is encountered while attempting to
359       *                         decode the password policy response control
360       *                         contained in the provided result.
361       */
362      public static PasswordPolicyResponseControl get(final LDAPResult result)
363             throws LDAPException
364      {
365        final Control c = result.getResponseControl(PASSWORD_POLICY_RESPONSE_OID);
366        if (c == null)
367        {
368          return null;
369        }
370    
371        if (c instanceof PasswordPolicyResponseControl)
372        {
373          return (PasswordPolicyResponseControl) c;
374        }
375        else
376        {
377          return new PasswordPolicyResponseControl(c.getOID(), c.isCritical(),
378               c.getValue());
379        }
380      }
381    
382    
383    
384      /**
385       * Encodes the provided information as appropriate for use as the value of a
386       * password policy response control.
387       *
388       * @param  warningType   The warning type to use for the warning element, or
389       *                       {@code null} if there is not to be a warning element.
390       * @param  warningValue  The value to use for the warning element.
391       * @param  errorType     The error type to use for the error element, or
392       *                       {@code null} if there is not to be an error element.
393       *
394       * @return  The ASN.1 octet string containing the encoded control value.
395       */
396      private static ASN1OctetString
397              encodeValue(final PasswordPolicyWarningType warningType,
398                          final int warningValue,
399                          final PasswordPolicyErrorType errorType)
400      {
401        final ArrayList<ASN1Element> valueElements = new ArrayList<ASN1Element>(2);
402    
403        if (warningType != null)
404        {
405          switch (warningType)
406          {
407            case TIME_BEFORE_EXPIRATION:
408              valueElements.add(new ASN1Element(TYPE_WARNING,
409                   new ASN1Integer(TYPE_TIME_BEFORE_EXPIRATION,
410                                   warningValue).encode()));
411              break;
412    
413            case GRACE_LOGINS_REMAINING:
414              valueElements.add(new ASN1Element(TYPE_WARNING,
415                   new ASN1Integer(TYPE_GRACE_LOGINS_REMAINING,
416                                   warningValue).encode()));
417              break;
418          }
419        }
420    
421        if (errorType != null)
422        {
423          valueElements.add(new ASN1Enumerated(TYPE_ERROR, errorType.intValue()));
424        }
425    
426        return new ASN1OctetString(new ASN1Sequence(valueElements).encode());
427      }
428    
429    
430    
431      /**
432       * Retrieves the warning type for this password policy response control, if
433       * available.
434       *
435       * @return  The warning type for this password policy response control, or
436       *          {@code null} if there is no warning type.
437       */
438      public PasswordPolicyWarningType getWarningType()
439      {
440        return warningType;
441      }
442    
443    
444    
445      /**
446       * Retrieves the warning value for this password policy response control, if
447       * available.
448       *
449       * @return  The warning value for this password policy response control, or -1
450       *          if there is no warning type.
451       */
452      public int getWarningValue()
453      {
454        return warningValue;
455      }
456    
457    
458    
459      /**
460       * Retrieves the error type for this password policy response control, if
461       * available.
462       *
463       * @return  The error type for this password policy response control, or
464       *          {@code null} if there is no error type.
465       */
466      public PasswordPolicyErrorType getErrorType()
467      {
468        return errorType;
469      }
470    
471    
472    
473      /**
474       * {@inheritDoc}
475       */
476      @Override()
477      public String getControlName()
478      {
479        return INFO_CONTROL_NAME_PW_POLICY_RESPONSE.get();
480      }
481    
482    
483    
484      /**
485       * {@inheritDoc}
486       */
487      @Override()
488      public void toString(final StringBuilder buffer)
489      {
490        boolean elementAdded = false;
491    
492        buffer.append("PasswordPolicyResponseControl(");
493    
494        if (warningType != null)
495        {
496          buffer.append("warningType='");
497          buffer.append(warningType.getName());
498          buffer.append("', warningValue=");
499          buffer.append(warningValue);
500          elementAdded = true;
501        }
502    
503        if (errorType != null)
504        {
505          if (elementAdded)
506          {
507            buffer.append(", ");
508          }
509    
510          buffer.append("errorType='");
511          buffer.append(errorType.getName());
512          buffer.append('\'');
513          elementAdded = true;
514        }
515    
516        if (elementAdded)
517        {
518          buffer.append(", ");
519        }
520    
521        buffer.append("isCritical=");
522        buffer.append(isCritical());
523        buffer.append(')');
524      }
525    }