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 }