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.controls;
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.ASN1Null;
035 import com.unboundid.asn1.ASN1OctetString;
036 import com.unboundid.asn1.ASN1Sequence;
037 import com.unboundid.ldap.sdk.Control;
038 import com.unboundid.ldap.sdk.DecodeableControl;
039 import com.unboundid.ldap.sdk.LDAPException;
040 import com.unboundid.ldap.sdk.LDAPResult;
041 import com.unboundid.ldap.sdk.ResultCode;
042 import com.unboundid.util.Debug;
043 import com.unboundid.util.NotMutable;
044 import com.unboundid.util.StaticUtils;
045 import com.unboundid.util.ThreadSafety;
046 import com.unboundid.util.ThreadSafetyLevel;
047
048 import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
049
050
051
052 /**
053 * <BLOCKQUOTE>
054 * <B>NOTE:</B> This class is part of the Commercial Edition of the UnboundID
055 * LDAP SDK for Java. It is not available for use in applications that
056 * include only the Standard Edition of the LDAP SDK, and is not supported for
057 * use in conjunction with non-UnboundID products.
058 * </BLOCKQUOTE>
059 * This class provides an implementation for a response control that can be
060 * returned by the server in the response for add, modify, and password modify
061 * requests that include the password validation details request control. This
062 * response control will provide details about the password quality requirements
063 * that are in effect for the operation and whether the password included in the
064 * request satisfies each of those requirements.
065 * <BR><BR>
066 * This response control has an OID of 1.3.6.1.4.1.30221.2.5.41, a criticality
067 * of {@code false}, and a value with the provided encoding:
068 * <PRE>
069 * PasswordValidationDetailsResponse ::= SEQUENCE {
070 * validationResult CHOICE {
071 * validationDetails [0] SEQUENCE OF
072 * PasswordQualityRequirementValidationResult,
073 * noPasswordProvided [1] NULL,
074 * multiplePasswordsProvided [2] NULL,
075 * noValidationAttempted [3] NULL,
076 * ... },
077 * missingCurrentPassword [3] BOOLEAN DEFAULT FALSE,
078 * mustChangePassword [4] BOOLEAN DEFAULT FALSE,
079 * secondsUntilExpiration [5] INTEGER OPTIONAL,
080 * ... }
081 * </PRE>
082 */
083 @NotMutable()
084 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
085 public final class PasswordValidationDetailsResponseControl
086 extends Control
087 implements DecodeableControl
088 {
089 /**
090 * The OID (1.3.6.1.4.1.30221.2.5.41) for the password validation details
091 * response control.
092 */
093 public static final String PASSWORD_VALIDATION_DETAILS_RESPONSE_OID =
094 "1.3.6.1.4.1.30221.2.5.41";
095
096
097
098 /**
099 * The BER type for the missing current password element.
100 */
101 private static final byte TYPE_MISSING_CURRENT_PASSWORD = (byte) 0x83;
102
103
104
105 /**
106 * The BER type for the must change password element.
107 */
108 private static final byte TYPE_MUST_CHANGE_PW = (byte) 0x84;
109
110
111
112 /**
113 * The BER type for the seconds until expiration element.
114 */
115 private static final byte TYPE_SECONDS_UNTIL_EXPIRATION = (byte) 0x85;
116
117
118
119 /**
120 * The serial version UID for this serializable class.
121 */
122 private static final long serialVersionUID = -2205640814914704074L;
123
124
125
126 // Indicates whether the associated password self change operation failed
127 // (or would fail if attempted without validation errors) because the user is
128 // required to provide his/her current password when performing a self change
129 // but did not do so.
130 private final boolean missingCurrentPassword;
131
132 // Indicates whether the user will be required to change his/her password
133 // immediately after the associated add or administrative password reset is
134 // complete.
135 private final boolean mustChangePassword;
136
137 // The length of time in seconds that the new password will be considered
138 // valid.
139 private final Integer secondsUntilExpiration;
140
141 // The list of the validation results for the associated operation.
142 private final List<PasswordQualityRequirementValidationResult>
143 validationResults;
144
145 // The response type for this password validation details response control.
146 private final PasswordValidationDetailsResponseType responseType;
147
148
149
150 /**
151 * Creates a new empty control instance that is intended to be used only for
152 * decoding controls via the {@code DecodeableControl} interface.
153 */
154 PasswordValidationDetailsResponseControl()
155 {
156 responseType = null;
157 validationResults = null;
158 missingCurrentPassword = true;
159 mustChangePassword = true;
160 secondsUntilExpiration = null;
161 }
162
163
164
165 /**
166 * Creates a password validation details response control with the provided
167 * information.
168 *
169 * @param responseType The response type for this password
170 * validation details response control. This
171 * must not be {@code null}.
172 * @param validationResults A list of the results obtained when
173 * validating the password against the
174 * password quality requirements. This must
175 * be {@code null} or empty if the
176 * {@code responseType} element has a value
177 * other than {@code VALIDATION_DETAILS}.
178 * @param missingCurrentPassword Indicates whether the associated operation
179 * is a self change that failed (or would have
180 * failed if not for additional validation
181 * failures) because the user did not provide
182 * his/her current password as required.
183 * @param mustChangePassword Indicates whether the associated operation
184 * is an add or administrative reset that will
185 * require the user to change his/her password
186 * immediately after authenticating before
187 * allowing them to perform any other
188 * operation in the server.
189 * @param secondsUntilExpiration The maximum length of time, in seconds,
190 * that the newly-set password will be
191 * considered valid. This may be {@code null}
192 * if the new password will be considered
193 * valid indefinitely.
194 */
195 public PasswordValidationDetailsResponseControl(
196 final PasswordValidationDetailsResponseType responseType,
197 final Collection<PasswordQualityRequirementValidationResult>
198 validationResults,
199 final boolean missingCurrentPassword,
200 final boolean mustChangePassword,
201 final Integer secondsUntilExpiration)
202 {
203 super(PASSWORD_VALIDATION_DETAILS_RESPONSE_OID, false,
204 encodeValue(responseType, validationResults, missingCurrentPassword,
205 mustChangePassword, secondsUntilExpiration));
206
207 this.responseType = responseType;
208 this.missingCurrentPassword = missingCurrentPassword;
209 this.mustChangePassword = mustChangePassword;
210 this.secondsUntilExpiration = secondsUntilExpiration;
211
212 if (validationResults == null)
213 {
214 this.validationResults = Collections.emptyList();
215 }
216 else
217 {
218 this.validationResults = Collections.unmodifiableList(
219 new ArrayList<PasswordQualityRequirementValidationResult>(
220 validationResults));
221 }
222 }
223
224
225
226 /**
227 * Creates a new password validation details response control by decoding the
228 * provided generic control information.
229 *
230 * @param oid The OID for the control.
231 * @param isCritical Indicates whether the control should be considered
232 * critical.
233 * @param value The value for the control.
234 *
235 * @throws LDAPException If the provided information cannot be decoded to
236 * create a password validation details response
237 * control.
238 */
239 public PasswordValidationDetailsResponseControl(final String oid,
240 final boolean isCritical,
241 final ASN1OctetString value)
242 throws LDAPException
243 {
244 super(oid, isCritical, value);
245
246 if (value == null)
247 {
248 throw new LDAPException(ResultCode.DECODING_ERROR,
249 ERR_PW_VALIDATION_RESPONSE_NO_VALUE.get());
250 }
251
252 try
253 {
254 final ASN1Element[] elements =
255 ASN1Sequence.decodeAsSequence(value.getValue()).elements();
256
257 responseType = PasswordValidationDetailsResponseType.forBERType(
258 elements[0].getType());
259 if (responseType == null)
260 {
261 throw new LDAPException(ResultCode.DECODING_ERROR,
262 ERR_PW_VALIDATION_RESPONSE_INVALID_RESPONSE_TYPE.get(
263 StaticUtils.toHex(elements[0].getType())));
264 }
265
266 if (responseType ==
267 PasswordValidationDetailsResponseType.VALIDATION_DETAILS)
268 {
269 final ASN1Element[] resultElements =
270 ASN1Sequence.decodeAsSequence(elements[0]).elements();
271
272 final ArrayList<PasswordQualityRequirementValidationResult> resultList =
273 new ArrayList<PasswordQualityRequirementValidationResult>(
274 resultElements.length);
275 for (final ASN1Element e : resultElements)
276 {
277 resultList.add(PasswordQualityRequirementValidationResult.decode(e));
278 }
279 validationResults = Collections.unmodifiableList(resultList);
280 }
281 else
282 {
283 validationResults = Collections.emptyList();
284 }
285
286 boolean missingCurrent = false;
287 boolean mustChange = false;
288 Integer secondsRemaining = null;
289 for (int i=1; i < elements.length; i++)
290 {
291 switch (elements[i].getType())
292 {
293 case TYPE_MISSING_CURRENT_PASSWORD:
294 missingCurrent =
295 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue();
296 break;
297
298 case TYPE_MUST_CHANGE_PW:
299 mustChange =
300 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue();
301 break;
302
303 case TYPE_SECONDS_UNTIL_EXPIRATION:
304 secondsRemaining =
305 ASN1Integer.decodeAsInteger(elements[i]).intValue();
306 break;
307
308 default:
309 // We may update this control in the future to provide support for
310 // returning additional password-related information. If we
311 // encounter an unrecognized element, just ignore it rather than
312 // throwing an exception.
313 break;
314 }
315 }
316
317 missingCurrentPassword = missingCurrent;
318 mustChangePassword = mustChange;
319 secondsUntilExpiration = secondsRemaining;
320 }
321 catch (final LDAPException le)
322 {
323 Debug.debugException(le);
324 throw le;
325 }
326 catch (final Exception e)
327 {
328 Debug.debugException(e);
329 throw new LDAPException(ResultCode.DECODING_ERROR,
330 ERR_PW_VALIDATION_RESPONSE_ERROR_PARSING_VALUE.get(
331 StaticUtils.getExceptionMessage(e)),
332 e);
333 }
334 }
335
336
337
338 /**
339 * Encodes the provided information to an ASN.1 element suitable for use as
340 * the control value.
341 *
342 * @param responseType The response type for this password
343 * validation details response control. This
344 * must not be {@code null}.
345 * @param validationResults A list of the results obtained when
346 * validating the password against the
347 * password quality requirements. This must
348 * be {@code null} or empty if the
349 * {@code responseType} element has a value
350 * other than {@code VALIDATION_DETAILS}.
351 * @param missingCurrentPassword Indicates whether the associated operation
352 * is a self change that failed (or would have
353 * failed if not for additional validation
354 * failures) because the user did not provide
355 * his/her current password as required.
356 * @param mustChangePassword Indicates whether the associated operation
357 * is an add or administrative reset that will
358 * require the user to change his/her password
359 * immediately after authenticating before
360 * allowing them to perform any other
361 * operation in the server.
362 * @param secondsUntilExpiration The maximum length of time, in seconds,
363 * that the newly-set password will be
364 * considered valid. This may be {@code null}
365 * if the new password will be considered
366 * valid indefinitely.
367 *
368 * @return The encoded control value.
369 */
370 private static ASN1OctetString encodeValue(
371 final PasswordValidationDetailsResponseType responseType,
372 final Collection<PasswordQualityRequirementValidationResult>
373 validationResults,
374 final boolean missingCurrentPassword,
375 final boolean mustChangePassword,
376 final Integer secondsUntilExpiration)
377 {
378 final ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(4);
379
380 switch (responseType)
381 {
382 case VALIDATION_DETAILS:
383 if (validationResults == null)
384 {
385 elements.add(new ASN1Sequence(responseType.getBERType()));
386 }
387 else
388 {
389 final ArrayList<ASN1Element> resultElements =
390 new ArrayList<ASN1Element>(validationResults.size());
391 for (final PasswordQualityRequirementValidationResult r :
392 validationResults)
393 {
394 resultElements.add(r.encode());
395 }
396 elements.add(new ASN1Sequence(responseType.getBERType(),
397 resultElements));
398 }
399 break;
400
401 case NO_PASSWORD_PROVIDED:
402 case MULTIPLE_PASSWORDS_PROVIDED:
403 case NO_VALIDATION_ATTEMPTED:
404 elements.add(new ASN1Null(responseType.getBERType()));
405 break;
406 }
407
408 if (missingCurrentPassword)
409 {
410 elements.add(new ASN1Boolean(TYPE_MISSING_CURRENT_PASSWORD,
411 missingCurrentPassword));
412 }
413
414 if (mustChangePassword)
415 {
416 elements.add(new ASN1Boolean(TYPE_MUST_CHANGE_PW, mustChangePassword));
417 }
418
419 if (secondsUntilExpiration != null)
420 {
421 elements.add(new ASN1Integer(TYPE_SECONDS_UNTIL_EXPIRATION,
422 secondsUntilExpiration));
423 }
424
425 return new ASN1OctetString(new ASN1Sequence(elements).encode());
426 }
427
428
429
430 /**
431 * Retrieves the response type for this password validation details response
432 * control.
433 *
434 * @return The response type for this password validation details response
435 * control.
436 */
437 public PasswordValidationDetailsResponseType getResponseType()
438 {
439 return responseType;
440 }
441
442
443
444 /**
445 * Retrieves a list of the results obtained when attempting to validate the
446 * proposed password against the password quality requirements in effect for
447 * the operation.
448 *
449 * @return A list of the results obtained when attempting to validate the
450 * proposed password against the password quality requirements in
451 * effect for the operation, or an empty list if no validation
452 * results are available.
453 */
454 public List<PasswordQualityRequirementValidationResult> getValidationResults()
455 {
456 return validationResults;
457 }
458
459
460
461 /**
462 * Indicates whether the associated operation is a self password change that
463 * requires the user to provide his/her current password when setting a new
464 * password, but no current password was provided.
465 *
466 * @return {@code true} if the associated operation is a self password change
467 * that requires the user to provide his/her current password when
468 * setting a new password but none was required, or {@code false} if
469 * the associated operation was not a self change, or if the user's
470 * current password was provided.
471 */
472 public boolean missingCurrentPassword()
473 {
474 return missingCurrentPassword;
475 }
476
477
478
479 /**
480 * Indicates whether the user will be required to immediately change his/her
481 * password after the associated add or administrative reset is complete.
482 *
483 * @return {@code true} if the associated operation is an add or
484 * administrative reset and the user will be required to change
485 * his/her password before being allowed to perform any other
486 * operation, or {@code false} if the associated operation was not am
487 * add or an administrative reset, or if the user will not be
488 * required to immediately change his/her password.
489 */
490 public boolean mustChangePassword()
491 {
492 return mustChangePassword;
493 }
494
495
496
497 /**
498 * Retrieves the maximum length of time, in seconds, that the newly-set
499 * password will be considered valid. If {@link #mustChangePassword()}
500 * returns {@code true}, then this value will be the length of time that the
501 * user has to perform a self password change before the account becomes
502 * locked. If {@code mustChangePassword()} returns {@code false}, then this
503 * value will be the length of time until the password expires.
504 *
505 * @return The maximum length of time, in seconds, that the newly-set
506 * password will be considered valid, or {@code null} if the new
507 * password will be valid indefinitely.
508 */
509 public Integer getSecondsUntilExpiration()
510 {
511 return secondsUntilExpiration;
512 }
513
514
515
516 /**
517 * {@inheritDoc}
518 */
519 public PasswordValidationDetailsResponseControl decodeControl(
520 final String oid, final boolean isCritical,
521 final ASN1OctetString value)
522 throws LDAPException
523 {
524 return new PasswordValidationDetailsResponseControl(oid, isCritical, value);
525 }
526
527
528
529 /**
530 * Extracts a password validation details response control from the provided
531 * result.
532 *
533 * @param result The result from which to retrieve the password validation
534 * details response control.
535 *
536 * @return The password validation details response control contained in the
537 * provided result, or {@code null} if the result did not contain a
538 * password validation details response control.
539 *
540 * @throws LDAPException If a problem is encountered while attempting to
541 * decode the password validation details response
542 * control contained in the provided result.
543 */
544 public static PasswordValidationDetailsResponseControl
545 get(final LDAPResult result)
546 throws LDAPException
547 {
548 final Control c =
549 result.getResponseControl(PASSWORD_VALIDATION_DETAILS_RESPONSE_OID);
550 if (c == null)
551 {
552 return null;
553 }
554
555 if (c instanceof PasswordValidationDetailsResponseControl)
556 {
557 return (PasswordValidationDetailsResponseControl) c;
558 }
559 else
560 {
561 return new PasswordValidationDetailsResponseControl(c.getOID(),
562 c.isCritical(), c.getValue());
563 }
564 }
565
566
567
568 /**
569 * {@inheritDoc}
570 */
571 @Override()
572 public String getControlName()
573 {
574 return INFO_CONTROL_NAME_PW_VALIDATION_RESPONSE.get();
575 }
576
577
578
579 /**
580 * {@inheritDoc}
581 */
582 @Override()
583 public void toString(final StringBuilder buffer)
584 {
585 buffer.append("PasswordValidationDetailsResponseControl(responseType='");
586 buffer.append(responseType.name());
587 buffer.append('\'');
588
589 if (responseType ==
590 PasswordValidationDetailsResponseType.VALIDATION_DETAILS)
591 {
592 buffer.append(", validationDetails={");
593
594 final Iterator<PasswordQualityRequirementValidationResult> iterator =
595 validationResults.iterator();
596 while (iterator.hasNext())
597 {
598 iterator.next().toString(buffer);
599 if (iterator.hasNext())
600 {
601 buffer.append(',');
602 }
603 }
604
605 buffer.append('}');
606 }
607
608 buffer.append(", missingCurrentPassword=");
609 buffer.append(missingCurrentPassword);
610 buffer.append(", mustChangePassword=");
611 buffer.append(mustChangePassword);
612
613 if (secondsUntilExpiration != null)
614 {
615 buffer.append(", secondsUntilExpiration=");
616 buffer.append(secondsUntilExpiration);
617 }
618
619 buffer.append("})");
620 }
621 }