001/*
002 * Copyright 2019 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2019 Ping Identity Corporation
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 */
021package com.unboundid.ldap.sdk.unboundidds.extensions;
022
023
024
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.Iterator;
028import java.util.List;
029
030import com.unboundid.asn1.ASN1Element;
031import com.unboundid.asn1.ASN1OctetString;
032import com.unboundid.asn1.ASN1Sequence;
033import com.unboundid.ldap.sdk.Control;
034import com.unboundid.ldap.sdk.ExtendedResult;
035import com.unboundid.ldap.sdk.LDAPException;
036import com.unboundid.ldap.sdk.ResultCode;
037import com.unboundid.util.Debug;
038import com.unboundid.util.NotMutable;
039import com.unboundid.util.StaticUtils;
040import com.unboundid.util.ThreadSafety;
041import com.unboundid.util.ThreadSafetyLevel;
042import com.unboundid.util.Validator;
043
044import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*;
045
046
047
048/**
049 * This class provides an implementation of an extended result that may be used
050 * provide the client with the passwords generated by the server in response to
051 * a {@link GeneratePasswordExtendedRequest}.
052 * <BR>
053 * <BLOCKQUOTE>
054 *   <B>NOTE:</B>  This class, and other classes within the
055 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
056 *   supported for use against Ping Identity, UnboundID, and
057 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
058 *   for proprietary functionality or for external specifications that are not
059 *   considered stable or mature enough to be guaranteed to work in an
060 *   interoperable way with other types of LDAP servers.
061 * </BLOCKQUOTE>
062 * <BR>
063 * If the extended request was processed successfully, then this result will
064 * have an OID of 1.3.6.1.4.1.30221.2.6.63 and a value with the following
065 * encoding:
066 * <BR><BR>
067 * <PRE>
068 *   GeneratePasswordResponse ::= SEQUENCE {
069 *        passwordPolicyDN       LDAPDN,
070 *        generatedPasswords     SEQUENCE OF SEQUENCE {
071 *             generatedPassword       OCTET STRING,
072 *             validationAttempted     BOOLEAN,
073 *             validationErrors        [0] SEQUENCE OF OCTET STRING OPTIONAL,
074 *             ... },
075 *        ... }
076 * </PRE>
077 * <BR><BR>
078 * The elements of the response value are:
079 * <UL>
080 *   <LI>passwordPolicyDN -- The DN of the password policy that was used to
081 *       select the password generator and validators used in the course of
082 *       creating the passwords.</LI>
083 *   <LI>generatedPassword -- A clear-text password that was generated by the
084 *       server.</LI>
085 *   <LI>validationAttempted -- Indicates whether the server attempted to
086 *       perform any validation for the generated password.</LI>
087 *   <LI>validationErrors -- A list of messages describing any problems
088 *       that were identified while validating the generated password.</LI>
089 * </UL>
090 */
091@NotMutable()
092@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
093public final class GeneratePasswordExtendedResult
094       extends ExtendedResult
095{
096  /**
097   * The OID (1.3.6.1.4.1.30221.2.6.57) for the generate TOTP shared secret
098   * extended result.
099   */
100  public static final  String GENERATE_PASSWORD_RESULT_OID =
101       "1.3.6.1.4.1.30221.2.6.63";
102
103
104
105  /**
106   * The serial version UID for this serializable class.
107   */
108  private static final long  serialVersionUID = -6840636721723079194L;
109
110
111
112  // The list of generated passwords returned by the server.
113  private final List<GeneratedPassword> generatedPasswords;
114
115  // The base32-encoded representation TOTP shared secret generated by the
116  // server.
117  private final String passwordPolicyDN;
118
119
120
121  /**
122   * Creates a new generate password extended result that indicates successful
123   * processing with the provided information.
124   *
125   * @param  messageID           The message ID for the LDAP message that is
126   *                             associated with this LDAP result.
127   * @param  passwordPolicyDN    The DN of the password policy that was used in
128   *                             in the course of generating the password.  It
129   *                             must not be {@code null}.
130   * @param  generatedPasswords  The list of generated passwords.  It must not
131   *                             be {@code null} or empty.
132   * @param  controls            An optional set of controls for the response,
133   *                             if any.  It may be {@code null} or empty if no
134   *                             controls are needed.
135   */
136  public GeneratePasswordExtendedResult(final int messageID,
137              final String passwordPolicyDN,
138              final List<GeneratedPassword> generatedPasswords,
139              final Control... controls)
140  {
141    this(messageID, ResultCode.SUCCESS, null, null, null, passwordPolicyDN,
142         generatedPasswords, controls);
143  }
144
145
146
147  /**
148   * Creates a new generate password extended result with the provided
149   * information.
150   *
151   * @param  messageID           The message ID for the LDAP message that is
152   *                             associated with this LDAP result.
153   * @param  resultCode          The result code for the response.  It must not
154   *                             be {@code null}.
155   * @param  diagnosticMessage   The diagnostic message for the response.  It
156   *                             may be {@code null} if none is needed.
157   * @param  matchedDN           The matched DN for the response.  It may be
158   *                             {@code null} if none is needed.
159   * @param  referralURLs        The set of referral URLs for the response.  It
160   *                             may be {@code null} or empty if none are
161   *                             needed.
162   * @param  passwordPolicyDN    The DN of the password policy that was used in
163   *                             in the course of generating the password.  It
164   *                             must not be {@code null} for a successful
165   *                             result, but must be {@code null} for a
166   *                             non-successful result.
167   * @param  generatedPasswords  The list of generated passwords.  It must not
168   *                             be {@code null} or empty for a successful
169   *                             result, but must be {@code null} or empty for a
170   *                             non-successful result.
171   * @param  controls            An optional set of controls for the response,
172   *                             if any.  It may be {@code null} or empty if no
173   *                             controls are needed.
174   */
175  public GeneratePasswordExtendedResult(final int messageID,
176              final ResultCode resultCode, final String diagnosticMessage,
177              final String matchedDN, final String[] referralURLs,
178              final String passwordPolicyDN,
179              final List<GeneratedPassword> generatedPasswords,
180              final Control... controls)
181  {
182    super(messageID, resultCode, diagnosticMessage, matchedDN, referralURLs,
183         (resultCode == ResultCode.SUCCESS
184              ? GENERATE_PASSWORD_RESULT_OID
185              : null),
186         (resultCode == ResultCode.SUCCESS
187              ? encodeValue(passwordPolicyDN, generatedPasswords)
188              : null),
189         controls);
190
191    this.passwordPolicyDN = passwordPolicyDN;
192
193    if (resultCode == ResultCode.SUCCESS)
194    {
195      this.generatedPasswords = Collections.unmodifiableList(
196           new ArrayList<>(generatedPasswords));
197    }
198    else
199    {
200      Validator.ensureTrue((passwordPolicyDN == null),
201           "GeneratePasswordExtendedResult.passwordPolicyDN must be null for " +
202                "a non-success result.");
203      Validator.ensureTrue(
204           ((generatedPasswords == null) || generatedPasswords.isEmpty()),
205           "GeneratePasswordExtendedResult.generatedPasswords must be null " +
206                "or empty for a non-success result.");
207
208      this.generatedPasswords = Collections.emptyList();
209    }
210  }
211
212
213
214  /**
215   * Creates an ASN.1 octet string that is suitable for the value of a
216   * successful generate password extended result.
217   *
218   * @param  passwordPolicyDN    The DN of the password policy that was used in
219   *                             in the course of generating the password.  It
220   *                             must not be {@code null} for a successful
221   *                             result, but must be {@code null} for a
222   *                             non-successful result.
223   * @param  generatedPasswords  The list of generated passwords.  It must not
224   *                             be {@code null} or empty for a successful
225   *                             result, but must be {@code null} or empty for a
226   *                             non-successful result.
227   *
228   * @return  The ASN.1 octet string that was created.
229   */
230  private static ASN1OctetString encodeValue(final String passwordPolicyDN,
231                      final List<GeneratedPassword> generatedPasswords)
232  {
233    Validator.ensureNotNullOrEmpty(passwordPolicyDN,
234         "GeneratePasswordExtendedResult.passwordPolicyDN must not be null " +
235              "or empty in a success result.");
236    Validator.ensureNotNullOrEmpty(generatedPasswords,
237         "GeneratePasswordExtendedResult.generatedPasswords must not be null " +
238              "or empty in a success result.");
239
240    final List<ASN1Element> passwordElements =
241         new ArrayList<>(generatedPasswords.size());
242    for (final GeneratedPassword p : generatedPasswords)
243    {
244      passwordElements.add(p.encode());
245    }
246
247    final ASN1Sequence valueSequence = new ASN1Sequence(
248         new ASN1OctetString(passwordPolicyDN),
249         new ASN1Sequence(passwordElements));
250
251    return new ASN1OctetString(valueSequence.encode());
252  }
253
254
255
256  /**
257   * Creates a new generate password extended result from the provided extended
258   * result.
259   *
260   * @param  extendedResult  The extended result to be decoded as a generate
261   *                         password extended result.  It must not be
262   *                         {@code null}.
263   *
264   * @throws  LDAPException  If the provided extended result cannot be decoded
265   *                         as a generate password result.
266   */
267  public GeneratePasswordExtendedResult(final ExtendedResult extendedResult)
268         throws LDAPException
269  {
270    super(extendedResult);
271
272    final ASN1OctetString value = extendedResult.getValue();
273    if (value == null)
274    {
275      if (extendedResult.getResultCode() == ResultCode.SUCCESS)
276      {
277        throw new LDAPException(ResultCode.DECODING_ERROR,
278             ERR_GENERATE_PASSWORD_RESULT_SUCCESS_MISSING_VALUE.get());
279      }
280
281      passwordPolicyDN = null;
282      generatedPasswords = Collections.emptyList();
283      return;
284    }
285
286    if (extendedResult.getResultCode() != ResultCode.SUCCESS)
287    {
288      throw new LDAPException(ResultCode.DECODING_ERROR,
289           ERR_GENERATE_PASSWORD_RESULT_NON_SUCCESS_WITH_VALUE.get(
290                String.valueOf(extendedResult.getResultCode())));
291    }
292
293    try
294    {
295      final ASN1Element[] valueElements =
296           ASN1Sequence.decodeAsSequence(value.getValue()).elements();
297      passwordPolicyDN =
298           ASN1OctetString.decodeAsOctetString(valueElements[0]).stringValue();
299
300      final ASN1Element[] pwElements =
301           ASN1Sequence.decodeAsSequence(valueElements[1]).elements();
302      final List<GeneratedPassword> pwList =
303           new ArrayList<>(pwElements.length);
304      for (final ASN1Element e : pwElements)
305      {
306        pwList.add(GeneratedPassword.decode(e));
307      }
308
309      if (pwList.isEmpty())
310      {
311        throw new LDAPException(ResultCode.DECODING_ERROR,
312             ERR_GENERATE_PASSWORD_RESULT_DECODE_NO_PASSWORDS.get());
313      }
314
315      generatedPasswords = Collections.unmodifiableList(pwList);
316    }
317    catch (final LDAPException e)
318    {
319      Debug.debugException(e);
320      throw e;
321    }
322    catch (final Exception e)
323    {
324      Debug.debugException(e);
325      throw new LDAPException(ResultCode.DECODING_ERROR,
326           ERR_GENERATE_PASSWORD_RESULT_DECODING_ERROR.get(
327                StaticUtils.getExceptionMessage(e)),
328           e);
329    }
330  }
331
332
333
334  /**
335   * Retrieves the DN of the password policy that was used in the course of
336   * generating and validating the passwords.
337   *
338   * @return  The DN of the password policy that was used in the course of
339   *          generating and validating the passwords, or {@code null} if the
340   *          operation was not processed successfully.
341   */
342  public String getPasswordPolicyDN()
343  {
344    return passwordPolicyDN;
345  }
346
347
348
349  /**
350   * Retrieves the list of passwords that were generated by the server.
351   *
352   * @return  The list of passwords that were generated by the server, or an
353   *          empty list if the operation was not processed successfully.
354   */
355  public List<GeneratedPassword> getGeneratedPasswords()
356  {
357    return generatedPasswords;
358  }
359
360
361
362  /**
363   * {@inheritDoc}
364   */
365  @Override()
366  public String getExtendedResultName()
367  {
368    return INFO_GENERATE_PASSWORD_RESULT_NAME.get();
369  }
370
371
372
373  /**
374   * Appends a string representation of this extended result to the provided
375   * buffer.
376   *
377   * @param  buffer  The buffer to which a string representation of this
378   *                 extended result will be appended.
379   */
380  @Override()
381  public void toString(final StringBuilder buffer)
382  {
383    buffer.append("GeneratePasswordExtendedResult(resultCode=");
384    buffer.append(getResultCode());
385
386    final int messageID = getMessageID();
387    if (messageID >= 0)
388    {
389      buffer.append(", messageID=");
390      buffer.append(messageID);
391    }
392
393    final String diagnosticMessage = getDiagnosticMessage();
394    if (diagnosticMessage != null)
395    {
396      buffer.append(", diagnosticMessage='");
397      buffer.append(diagnosticMessage);
398      buffer.append('\'');
399    }
400
401    final String matchedDN = getMatchedDN();
402    if (matchedDN != null)
403    {
404      buffer.append(", matchedDN='");
405      buffer.append(matchedDN);
406      buffer.append('\'');
407    }
408
409    final String[] referralURLs = getReferralURLs();
410    if (referralURLs.length > 0)
411    {
412      buffer.append(", referralURLs={");
413      for (int i=0; i < referralURLs.length; i++)
414      {
415        if (i > 0)
416        {
417          buffer.append(", ");
418        }
419
420        buffer.append('\'');
421        buffer.append(referralURLs[i]);
422        buffer.append('\'');
423      }
424      buffer.append('}');
425    }
426
427    if (passwordPolicyDN != null)
428    {
429      buffer.append(", passwordPolicyDN='");
430      buffer.append(passwordPolicyDN);
431      buffer.append('\'');
432    }
433
434    if (! generatedPasswords.isEmpty())
435    {
436      buffer.append(", generatedPasswords={ ");
437
438      final Iterator<GeneratedPassword> iterator =
439           generatedPasswords.iterator();
440      while (iterator.hasNext())
441      {
442        iterator.next().toString(buffer);
443
444        if (iterator.hasNext())
445        {
446          buffer.append(", ");
447        }
448      }
449
450      buffer.append(" }");
451    }
452
453    final Control[] responseControls = getResponseControls();
454    if (responseControls.length > 0)
455    {
456      buffer.append(", responseControls={");
457      for (int i=0; i < responseControls.length; i++)
458      {
459        if (i > 0)
460        {
461          buffer.append(", ");
462        }
463
464        buffer.append(responseControls[i]);
465      }
466      buffer.append('}');
467    }
468
469    buffer.append(')');
470  }
471}