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