001    /*
002     * Copyright 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.extensions;
022    
023    
024    
025    import java.util.ArrayList;
026    import java.util.Collection;
027    import java.util.Collections;
028    import java.util.Iterator;
029    import java.util.List;
030    
031    import com.unboundid.asn1.ASN1Boolean;
032    import com.unboundid.asn1.ASN1Element;
033    import com.unboundid.asn1.ASN1Integer;
034    import com.unboundid.asn1.ASN1OctetString;
035    import com.unboundid.asn1.ASN1Sequence;
036    import com.unboundid.ldap.sdk.Control;
037    import com.unboundid.ldap.sdk.ExtendedResult;
038    import com.unboundid.ldap.sdk.LDAPException;
039    import com.unboundid.ldap.sdk.ResultCode;
040    import com.unboundid.util.Debug;
041    import com.unboundid.util.NotMutable;
042    import com.unboundid.util.StaticUtils;
043    import com.unboundid.util.ThreadSafety;
044    import com.unboundid.util.ThreadSafetyLevel;
045    import com.unboundid.util.Validator;
046    
047    import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*;
048    
049    
050    
051    /**
052     * <BLOCKQUOTE>
053     *   <B>NOTE:</B>  This class is part of the Commercial Edition of the UnboundID
054     *   LDAP SDK for Java.  It is not available for use in applications that
055     *   include only the Standard Edition of the LDAP SDK, and is not supported for
056     *   use in conjunction with non-UnboundID products.
057     * </BLOCKQUOTE>
058     * This class provides an implementation of an extended result that can provide
059     * information about the requirements that the server will enforce for
060     * operations that change or replace a user's password, including adding a new
061     * user, a user changing his/her own password, and an administrator resetting
062     * another user's password.
063     * <BR><BR>
064     * If the get password quality request was processed successfully, then the
065     * result will include an OID of 1.3.6.1.4.1.30221.2.6.44 and a value with the
066     * following encoding:
067     * <PRE>
068     *   GetPasswordQualityRequirementsResultValue ::= SEQUENCE {
069     *        requirements                SEQUENCE OF PasswordQualityRequirement,
070     *        currentPasswordRequired     [0] BOOLEAN OPTIONAL,
071     *        mustChangePassword          [1] BOOLEAN OPTIONAL,
072     *        secondsUntilExpiration      [2] INTEGER OPTIONAL,
073     *        ... }
074     * </PRE>
075     */
076    @NotMutable()
077    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
078    public final class GetPasswordQualityRequirementsExtendedResult
079           extends ExtendedResult
080    {
081      /**
082       * The OID (1.3.6.1.4.1.30221.2.6.44) for the get password quality
083       * requirements extended result.
084       */
085      public static final String OID_GET_PASSWORD_QUALITY_REQUIREMENTS_RESULT =
086           "1.3.6.1.4.1.30221.2.6.44";
087    
088    
089    
090      /**
091       * The BER type for the current password required element.
092       */
093      private static final byte TYPE_CURRENT_PW_REQUIRED = (byte) 0x80;
094    
095    
096    
097      /**
098       * The BER type for the must change password element.
099       */
100      private static final byte TYPE_MUST_CHANGE_PW = (byte) 0x81;
101    
102    
103    
104      /**
105       * The BER type for the seconds until expiration element.
106       */
107      private static final byte TYPE_SECONDS_UNTIL_EXPIRATION = (byte) 0x82;
108    
109    
110    
111      /**
112       * The serial version UID for this serializable class.
113       */
114      private static final long serialVersionUID = -4990045432443188148L;
115    
116    
117    
118      // Indicates whether the user will be required to provide his/her current
119      // password when performing the associated self password change.
120      private final Boolean currentPasswordRequired;
121    
122      // Indicates whether the user will be required to change his/her password
123      // after performing the associated add or administrative reset.
124      private final Boolean mustChangePassword;
125    
126      // The length of time in seconds that the resulting password will be
127      // considered valid.
128      private final Integer secondsUntilExpiration;
129    
130      // The list of password quality requirements that the server will enforce for
131      // the associated operation.
132      private final List<PasswordQualityRequirement> passwordRequirements;
133    
134    
135    
136    
137      /**
138       * Creates a new get password quality requirements extended result with the
139       * provided information.
140       *
141       * @param  messageID                The message ID for the LDAP message that
142       *                                  is associated with this LDAP result.
143       * @param  resultCode               The result code for the response.  This
144       *                                  must not be {@code null}.
145       * @param  diagnosticMessage        The diagnostic message for the response.
146       *                                  This may be {@code null} if no diagnostic
147       *                                  message is needed.
148       * @param  matchedDN                The matched DN for the response.  This may
149       *                                  be {@code null} if no matched DN is
150       *                                  needed.
151       * @param  referralURLs             The set of referral URLs from the
152       *                                  response.  This may be {@code null} or
153       *                                  empty if no referral URLs are needed.
154       * @param  passwordRequirements     The password quality requirements for this
155       *                                  result.  This must be {@code null} or
156       *                                  empty if this result is for an operation
157       *                                  that was not processed successfully.  It
158       *                                  may be {@code null} or empty if the
159       *                                  server will not enforce any password
160       *                                  quality requirements for the target
161       *                                  operation.
162       * @param  currentPasswordRequired  Indicates whether the user will be
163       *                                  required to provide his/her current
164       *                                  password when performing a self change.
165       *                                  This must be {@code null} if this result
166       *                                  is for an operation that was not processed
167       *                                  successfully or if the target operation is
168       *                                  not a self change.
169       * @param  mustChangePassword       Indicates whether the user will be
170       *                                  required to change their password after
171       *                                  the associated add or administrative
172       *                                  reset before that user will be allowed to
173       *                                  issue any other requests.  This must be
174       *                                  {@code null} if this result is for an
175       *                                  operation that was not processed
176       *                                  successfully or if the target operation is
177       *                                  not an add or an administrative reset.
178       * @param  secondsUntilExpiration   Indicates the maximum length of time, in
179       *                                  seconds, that the password set in the
180       *                                  target operation will be valid.  If
181       *                                  {@code mustChangePassword} is {@code true}
182       *                                  then this will indicate the length of time
183       *                                  that the user has to change his/her
184       *                                  password after the add/reset.  If
185       *                                  {@code mustChangePassword} is {@code null}
186       *                                  or {@code false} then this will indicate
187       *                                  the length of time until the password
188       *                                  expires.  This must be {@code null} if
189       *                                  this result is for an operation that was
190       *                                  not processed successfully, or if the new
191       *                                  password will be valid indefinitely.
192       * @param  controls                 The set of controls to include in the
193       *                                  result.  It may be {@code null} or empty
194       *                                  if no controls are needed.
195       */
196      public GetPasswordQualityRequirementsExtendedResult(final int messageID,
197                  final ResultCode resultCode, final String diagnosticMessage,
198                  final String matchedDN, final String[] referralURLs,
199                  final Collection<PasswordQualityRequirement> passwordRequirements,
200                  final Boolean currentPasswordRequired,
201                  final Boolean mustChangePassword,
202                  final Integer secondsUntilExpiration,
203                  final Control... controls)
204      {
205        super(messageID, resultCode, diagnosticMessage, matchedDN, referralURLs,
206             ((resultCode == ResultCode.SUCCESS)
207                  ? OID_GET_PASSWORD_QUALITY_REQUIREMENTS_RESULT
208                  : null),
209             encodeValue(resultCode, passwordRequirements, currentPasswordRequired,
210                  mustChangePassword, secondsUntilExpiration),
211             controls);
212    
213        if ((passwordRequirements == null) || passwordRequirements.isEmpty())
214        {
215          this.passwordRequirements = Collections.emptyList();
216        }
217        else
218        {
219          this.passwordRequirements = Collections.unmodifiableList(
220               new ArrayList<PasswordQualityRequirement>(passwordRequirements));
221        }
222    
223        this.currentPasswordRequired = currentPasswordRequired;
224        this.mustChangePassword      = mustChangePassword;
225        this.secondsUntilExpiration  = secondsUntilExpiration;
226      }
227    
228    
229    
230      /**
231       * Creates a new get password quality requirements extended result from the
232       * provided generic result.
233       *
234       * @param  r  The generic extended result to parse as a get password quality
235       *            requirements result.
236       *
237       * @throws  LDAPException  If the provided generic extended result cannot be
238       *                         parsed as a get password quality requirements
239       *                         result.
240       */
241      public GetPasswordQualityRequirementsExtendedResult(final ExtendedResult r)
242             throws LDAPException
243      {
244        super(r);
245    
246        final ASN1OctetString value = r.getValue();
247        if (value == null)
248        {
249          passwordRequirements = Collections.emptyList();
250          currentPasswordRequired = null;
251          mustChangePassword = null;
252          secondsUntilExpiration = null;
253          return;
254        }
255    
256        try
257        {
258          final ASN1Element[] elements =
259               ASN1Sequence.decodeAsSequence(value.getValue()).elements();
260    
261          final ASN1Element[] requirementElements =
262               ASN1Sequence.decodeAsSequence(elements[0]).elements();
263          final ArrayList<PasswordQualityRequirement> requirementList =
264               new ArrayList<PasswordQualityRequirement>(
265                    requirementElements.length);
266          for (final ASN1Element e : requirementElements)
267          {
268            requirementList.add(PasswordQualityRequirement.decode(e));
269          }
270          passwordRequirements = Collections.unmodifiableList(requirementList);
271    
272          Boolean cpr = null;
273          Boolean mcp = null;
274          Integer sue = null;
275          for (int i=1; i < elements.length; i++)
276          {
277            switch (elements[i].getType())
278            {
279              case TYPE_CURRENT_PW_REQUIRED:
280                cpr = ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue();
281                break;
282    
283              case TYPE_MUST_CHANGE_PW:
284                mcp = ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue();
285                break;
286    
287              case TYPE_SECONDS_UNTIL_EXPIRATION:
288                sue = ASN1Integer.decodeAsInteger(elements[i]).intValue();
289                break;
290    
291              default:
292                // We may update this extended operation in the future to provide
293                // support for returning additional password-related information.
294                // If we encounter an unrecognized element, just ignore it rather
295                // than throwing an exception.
296                break;
297            }
298          }
299    
300          currentPasswordRequired = cpr;
301          mustChangePassword = mcp;
302          secondsUntilExpiration = sue;
303        }
304        catch (final Exception e)
305        {
306          Debug.debugException(e);
307          throw new LDAPException(ResultCode.DECODING_ERROR,
308               ERR_GET_PW_QUALITY_REQS_RESULT_CANNOT_DECODE.get(
309                    StaticUtils.getExceptionMessage(e)),
310               e);
311        }
312      }
313    
314    
315    
316      /**
317       * Encodes the provided information into an ASN.1 octet string suitable for
318       * use as the value for this extended result, if appropriate.
319       *
320       * @param  resultCode               The result code for the response.  This
321       *                                  must not be {@code null}.
322       * @param  passwordRequirements     The password quality requirements for this
323       *                                  result.  This must be {@code null} or
324       *                                  empty if this result is for an operation
325       *                                  that was not processed successfully.  It
326       *                                  may be {@code null} or empty if the
327       *                                  server will not enforce any password
328       *                                  quality requirements for the target
329       *                                  operation.
330       * @param  currentPasswordRequired  Indicates whether the user will be
331       *                                  required to provide his/her current
332       *                                  password when performing a self change.
333       *                                  This must be {@code null} if this result
334       *                                  is for an operation that was not processed
335       *                                  successfully or if the target operation is
336       *                                  not a self change.
337       * @param  mustChangePassword       Indicates whether the user will be
338       *                                  required to change their password after
339       *                                  the associated add or administrative
340       *                                  reset before that user will be allowed to
341       *                                  issue any other requests.  This must be
342       *                                  {@code null} if this result is for an
343       *                                  operation that was not processed
344       *                                  successfully or if the target operation is
345       *                                  not an add or an administrative reset.
346       * @param  secondsUntilExpiration   Indicates the maximum length of time, in
347       *                                  seconds, that the password set in the
348       *                                  target operation will be valid.  If
349       *                                  {@code mustChangePassword} is {@code true}
350       *                                  then this will indicate the length of time
351       *                                  that the user has to change his/her
352       *                                  password after the add/reset.  If
353       *                                  {@code mustChangePassword} is {@code null}
354       *                                  or {@code false} then this will indicate
355       *                                  the length of time until the password
356       *                                  expires.  This must be {@code null} if
357       *                                  this result is for an operation that was
358       *                                  not processed successfully, or if the new
359       *                                  password will be valid indefinitely.
360       *
361       * @return  The ASN.1 element with the encoded result value, or {@code null}
362       *          if the result should not have a value.
363       */
364      private static ASN1OctetString encodeValue(final ResultCode resultCode,
365           final Collection<PasswordQualityRequirement> passwordRequirements,
366           final Boolean currentPasswordRequired, final Boolean mustChangePassword,
367           final Integer secondsUntilExpiration)
368      {
369        if (resultCode != ResultCode.SUCCESS)
370        {
371          Validator.ensureTrue((passwordRequirements == null) ||
372               passwordRequirements.isEmpty());
373          Validator.ensureTrue(currentPasswordRequired == null);
374          Validator.ensureTrue(mustChangePassword == null);
375          Validator.ensureTrue(secondsUntilExpiration == null);
376    
377          return null;
378        }
379    
380        final ArrayList<ASN1Element> valueSequence = new ArrayList<ASN1Element>(4);
381    
382        if (passwordRequirements == null)
383        {
384          valueSequence.add(new ASN1Sequence());
385        }
386        else
387        {
388          final ArrayList<ASN1Element> requirementElements =
389               new ArrayList<ASN1Element>(passwordRequirements.size());
390          for (final PasswordQualityRequirement r : passwordRequirements)
391          {
392            requirementElements.add(r.encode());
393          }
394          valueSequence.add(new ASN1Sequence(requirementElements));
395        }
396    
397        if (currentPasswordRequired != null)
398        {
399          valueSequence.add(new ASN1Boolean(TYPE_CURRENT_PW_REQUIRED,
400               currentPasswordRequired));
401        }
402    
403        if (mustChangePassword != null)
404        {
405          valueSequence.add(new ASN1Boolean(TYPE_MUST_CHANGE_PW,
406               mustChangePassword));
407        }
408    
409        if (secondsUntilExpiration != null)
410        {
411          valueSequence.add(new ASN1Integer(TYPE_SECONDS_UNTIL_EXPIRATION,
412               secondsUntilExpiration));
413        }
414    
415        return new ASN1OctetString(new ASN1Sequence(valueSequence).encode());
416      }
417    
418    
419    
420      /**
421       * Retrieves the list of password quality requirements that specify the
422       * constraints that a proposed password must satisfy in order to be accepted
423       * by the server in an operation of the type specified in the get password
424       * quality requirements request.
425       *
426       * @return  A list of the password quality requirements returned by the
427       *          server, or an empty list if this result is for a non-successful
428       *          get password quality requirements operation or if the server
429       *          will not impose any password quality requirements for the
430       *          specified operation type.
431       */
432      public List<PasswordQualityRequirement> getPasswordRequirements()
433      {
434        return passwordRequirements;
435      }
436    
437    
438    
439      /**
440       * Retrieves a flag that indicates whether the target user will be required to
441       * provide his/her current password in order to set a new password with a self
442       * change.
443       *
444       * @return  A value of {@code Boolean.TRUE} if the target operation is a self
445       *          change and the user will be required to provide his/her current
446       *          password when setting a new one, {@code Boolean.FALSE} if the
447       *          target operation is a self change and the user will not be
448       *          required to provide his/her current password, or {@code null} if
449       *          the target operation is not a self change or if this result is for
450       *          a non-successful get password quality requirements operation.
451       */
452      public Boolean getCurrentPasswordRequired()
453      {
454        return currentPasswordRequired;
455      }
456    
457    
458    
459      /**
460       * Retrieves a flag that indicates whether the target user will be required to
461       * immediately change his/her own password after the associated add or
462       * administrative reset operation before that user will be allowed to issue
463       * any other types of requests.
464       *
465       * @return  A value of {@code Boolean.TRUE} if the target operation is an add
466       *          or administrative reset and the user will be required to
467       *          immediately perform a self change to select a new password before
468       *          being allowed to perform any other kinds of operations,
469       *          {@code Boolean.FALSE} if the target operation is an add or
470       *          administrative reset but the user will not be required to
471       *          immediately select a new password with a self change, or
472       *          {@code null} if the target operation is not an add or
473       *          administrative reset, or if this result is for a non-successful
474       *          get password quality requirements operation.
475       */
476      public Boolean getMustChangePassword()
477      {
478        return mustChangePassword;
479      }
480    
481    
482    
483      /**
484       * Retrieves the length of time, in seconds, that the new password will be
485       * considered valid after the change is applied.  If the associated operation
486       * is an add or an administrative reset and {@link #getMustChangePassword()}
487       * returns {@code Boolean.TRUE}, then this will indicate the length of time
488       * that the user has to choose a new password with a self change before the
489       * account becomes locked.  If the associated operation is a self change, or
490       * if {@code getMustChangePassword} returns {@code Boolean.FALSE}, then this
491       * will indicate the maximum length of time that the newly-selected password
492       * may be used until it expires.
493       *
494       * @return  The length of time, in seconds, that the new password will be
495       *          considered valid after the change is applied, or {@code null} if
496       *          this result is for a non-successful get password quality
497       *          requirements operation or if the newly-selected password can be
498       *          used indefinitely.
499       */
500      public Integer getSecondsUntilExpiration()
501      {
502        return secondsUntilExpiration;
503      }
504    
505    
506    
507      /**
508       * {@inheritDoc}
509       */
510      @Override()
511      public String getExtendedResultName()
512      {
513        return INFO_EXTENDED_RESULT_NAME_GET_PW_QUALITY_REQS.get();
514      }
515    
516    
517    
518      /**
519       * {@inheritDoc}
520       */
521      @Override()
522      public void toString(final StringBuilder buffer)
523      {
524        buffer.append("GetPasswordQualityRequirementsExtendedResult(resultCode=");
525        buffer.append(getResultCode());
526    
527        final int messageID = getMessageID();
528        if (messageID >= 0)
529        {
530          buffer.append(", messageID=");
531          buffer.append(messageID);
532        }
533    
534        buffer.append(", requirements{");
535    
536        final Iterator<PasswordQualityRequirement> requirementsIterator =
537             passwordRequirements.iterator();
538        while (requirementsIterator.hasNext())
539        {
540          requirementsIterator.next().toString(buffer);
541          if (requirementsIterator.hasNext())
542          {
543            buffer.append(',');
544          }
545        }
546    
547        buffer.append('}');
548    
549        if (currentPasswordRequired != null)
550        {
551          buffer.append(", currentPasswordRequired=");
552          buffer.append(currentPasswordRequired);
553        }
554    
555        if (mustChangePassword != null)
556        {
557          buffer.append(", mustChangePassword=");
558          buffer.append(mustChangePassword);
559        }
560    
561        if (secondsUntilExpiration != null)
562        {
563          buffer.append(", secondsUntilExpiration=");
564          buffer.append(secondsUntilExpiration);
565        }
566    
567        final String diagnosticMessage = getDiagnosticMessage();
568        if (diagnosticMessage != null)
569        {
570          buffer.append(", diagnosticMessage='");
571          buffer.append(diagnosticMessage);
572          buffer.append('\'');
573        }
574    
575        final String matchedDN = getMatchedDN();
576        if (matchedDN != null)
577        {
578          buffer.append(", matchedDN='");
579          buffer.append(matchedDN);
580          buffer.append('\'');
581        }
582    
583        final String[] referralURLs = getReferralURLs();
584        if (referralURLs.length > 0)
585        {
586          buffer.append(", referralURLs={");
587          for (int i=0; i < referralURLs.length; i++)
588          {
589            if (i > 0)
590            {
591              buffer.append(", ");
592            }
593    
594            buffer.append('\'');
595            buffer.append(referralURLs[i]);
596            buffer.append('\'');
597          }
598          buffer.append('}');
599        }
600    
601        final Control[] responseControls = getResponseControls();
602        if (responseControls.length > 0)
603        {
604          buffer.append(", responseControls={");
605          for (int i=0; i < responseControls.length; i++)
606          {
607            if (i > 0)
608            {
609              buffer.append(", ");
610            }
611    
612            buffer.append(responseControls[i]);
613          }
614          buffer.append('}');
615        }
616    
617        buffer.append(')');
618      }
619    }