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