001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2006-2010 Sun Microsystems, Inc. 025 * Portions Copyright 2011-2015 ForgeRock AS 026 */ 027package org.opends.server.core; 028 029import static org.opends.messages.CoreMessages.*; 030import static org.opends.server.config.ConfigConstants.*; 031import static org.opends.server.protocols.internal.InternalClientConnection.*; 032import static org.opends.server.schema.SchemaConstants.*; 033import static org.opends.server.util.StaticUtils.*; 034 035import java.text.ParseException; 036import java.text.SimpleDateFormat; 037import java.util.ArrayList; 038import java.util.Collection; 039import java.util.Collections; 040import java.util.Date; 041import java.util.HashSet; 042import java.util.Iterator; 043import java.util.LinkedHashSet; 044import java.util.LinkedList; 045import java.util.List; 046import java.util.Map; 047import java.util.Set; 048import java.util.TimeZone; 049import java.util.TreeMap; 050 051import org.forgerock.i18n.LocalizableMessage; 052import org.forgerock.i18n.LocalizableMessageBuilder; 053import org.forgerock.i18n.slf4j.LocalizedLogger; 054import org.forgerock.opendj.ldap.ByteString; 055import org.forgerock.opendj.ldap.ConditionResult; 056import org.forgerock.opendj.ldap.GeneralizedTime; 057import org.forgerock.opendj.ldap.ModificationType; 058import org.forgerock.opendj.ldap.ResultCode; 059import org.opends.server.admin.std.meta.PasswordPolicyCfgDefn; 060import org.opends.server.api.AccountStatusNotificationHandler; 061import org.opends.server.api.AuthenticationPolicyState; 062import org.opends.server.api.PasswordGenerator; 063import org.opends.server.api.PasswordStorageScheme; 064import org.opends.server.api.PasswordValidator; 065import org.opends.server.protocols.internal.InternalClientConnection; 066import org.opends.server.protocols.ldap.LDAPAttribute; 067import org.opends.server.schema.AuthPasswordSyntax; 068import org.opends.server.schema.GeneralizedTimeSyntax; 069import org.opends.server.schema.UserPasswordSyntax; 070import org.opends.server.types.AccountStatusNotification; 071import org.opends.server.types.AccountStatusNotificationProperty; 072import org.opends.server.types.AccountStatusNotificationType; 073import org.opends.server.types.Attribute; 074import org.opends.server.types.AttributeBuilder; 075import org.opends.server.types.AttributeType; 076import org.opends.server.types.Attributes; 077import org.opends.server.types.DirectoryException; 078import org.opends.server.types.Entry; 079import org.opends.server.types.Modification; 080import org.opends.server.types.Operation; 081import org.opends.server.types.RawModification; 082 083/** 084 * This class provides a data structure for holding password policy state 085 * information for a user account. 086 */ 087public final class PasswordPolicyState extends AuthenticationPolicyState 088{ 089 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 090 091 092 /** The string representation of the user's DN. */ 093 private final String userDNString; 094 095 /** The password policy with which the account is associated. */ 096 private final PasswordPolicy passwordPolicy; 097 098 /** The current time for use in all password policy calculations. */ 099 private final long currentTime; 100 101 /** The time that the user's password was last changed. */ 102 private long passwordChangedTime = Long.MIN_VALUE; 103 104 /** Indicates whether the user's account is expired. */ 105 private ConditionResult isAccountExpired = ConditionResult.UNDEFINED; 106 /** Indicates whether the user's password is expired. */ 107 private ConditionResult isPasswordExpired = ConditionResult.UNDEFINED; 108 /** Indicates whether the warning to send to the client would be the first warning for the user. */ 109 private ConditionResult isFirstWarning = ConditionResult.UNDEFINED; 110 /** Indicates whether the user's account is locked by the idle lockout. */ 111 private ConditionResult isIdleLocked = ConditionResult.UNDEFINED; 112 /** 113 * Indicates whether the user may use a grace login if the password is expired and there are one 114 * or more grace logins remaining. 115 */ 116 private ConditionResult mayUseGraceLogin = ConditionResult.UNDEFINED; 117 /** Indicates whether the user's password must be changed. */ 118 private ConditionResult mustChangePassword = ConditionResult.UNDEFINED; 119 /** Indicates whether the user should be warned of an upcoming expiration. */ 120 private ConditionResult shouldWarn = ConditionResult.UNDEFINED; 121 122 /** The number of seconds until the user's account is automatically unlocked. */ 123 private int secondsUntilUnlock = Integer.MIN_VALUE; 124 125 /** The set of authentication failure times for this user. */ 126 private List<Long> authFailureTimes; 127 /** The set of grace login times for this user. */ 128 private List<Long> graceLoginTimes; 129 130 /** The time that the user's account should expire (or did expire). */ 131 private long accountExpirationTime = Long.MIN_VALUE; 132 /** The time that the user's entry was locked due to too many authentication failures. */ 133 private long failureLockedTime = Long.MIN_VALUE; 134 /** The time that the user last authenticated to the Directory Server. */ 135 private long lastLoginTime = Long.MIN_VALUE; 136 /** The time that the user's password should expire (or did expire). */ 137 private long passwordExpirationTime = Long.MIN_VALUE; 138 /** The last required change time with which the user complied. */ 139 private long requiredChangeTime = Long.MIN_VALUE; 140 /** The time that the user was first warned about an upcoming expiration. */ 141 private long warnedTime = Long.MIN_VALUE; 142 143 /** The set of modifications that should be applied to the user's entry. */ 144 private LinkedList<Modification> modifications = new LinkedList<>(); 145 146 147 148 /** 149 * Creates a new password policy state object with the provided information. 150 * <p> 151 * Note that this version of the constructor should only be used for testing purposes when the tests should be 152 * evaluated with a fixed time rather than the actual current time. For all other purposes, the other constructor 153 * should be used. 154 * </p> 155 * 156 * @param policy The password policy associated with the state. 157 * @param userEntry The entry with the user account. 158 * @param currentTime The time to use as the current time for all time-related determinations. 159 */ 160 PasswordPolicyState(PasswordPolicy policy, Entry userEntry, long currentTime) 161 { 162 super(userEntry); 163 this.currentTime = currentTime; 164 this.userDNString = userEntry.getName().toString(); 165 this.passwordPolicy = policy; 166 } 167 168 169 170 /** 171 * Retrieves the value of the specified attribute as a string. 172 * 173 * @param attributeType The attribute type whose value should be retrieved. 174 * 175 * @return The value of the specified attribute as a string, or <CODE>null</CODE> if there is no such value. 176 */ 177 private String getValue(AttributeType attributeType) 178 { 179 Attribute attr = getFirstAttributeNotEmpty(attributeType); 180 String stringValue = attr != null ? attr.iterator().next().toString() : null; 181 if (stringValue == null) 182 { 183 if (logger.isTraceEnabled()) 184 { 185 logger.trace("Returning null because attribute %s does not exist in user entry %s", 186 attributeType.getNameOrOID(), userDNString); 187 } 188 } 189 else 190 { 191 if (logger.isTraceEnabled()) 192 { 193 logger.trace("Returning value %s for user %s", stringValue, userDNString); 194 } 195 } 196 197 return stringValue; 198 } 199 200 private Attribute getFirstAttributeNotEmpty(AttributeType attributeType) 201 { 202 List<Attribute> attrList = userEntry.getAttribute(attributeType); 203 if (attrList != null) 204 { 205 for (Attribute a : attrList) 206 { 207 if (!a.isEmpty()) 208 { 209 return a; 210 } 211 } 212 } 213 return null; 214 } 215 216 /** 217 * Retrieves the set of values of the specified attribute from the user's entry in generalized time format. 218 * 219 * @param attributeType The attribute type whose values should be parsed as generalized time values. 220 * 221 * @return The set of generalized time values, or an empty list if there are none. 222 * 223 * @throws DirectoryException If a problem occurs while attempting to decode a value as a generalized time. 224 */ 225 private List<Long> getGeneralizedTimes(AttributeType attributeType) 226 throws DirectoryException 227 { 228 ArrayList<Long> timeValues = new ArrayList<>(); 229 230 List<Attribute> attrList = userEntry.getAttribute(attributeType); 231 if (attrList != null) 232 { 233 for (Attribute a : attrList) 234 { 235 for (ByteString v : a) 236 { 237 try 238 { 239 timeValues.add(GeneralizedTime.valueOf(v.toString()).getTimeInMillis()); 240 } 241 catch (Exception e) 242 { 243 logger.traceException(e, "Unable to decode value %s for attribute %s in user entry %s", 244 v, attributeType.getNameOrOID(), userDNString); 245 246 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 247 ERR_PWPSTATE_CANNOT_DECODE_GENERALIZED_TIME.get(v, attributeType.getNameOrOID(), userDNString, e), 248 e); 249 } 250 } 251 } 252 } 253 254 if (timeValues.isEmpty()) 255 { 256 logger.trace("Returning an empty list because attribute %s does not exist in user entry %s", 257 attributeType.getNameOrOID(), userDNString); 258 } 259 return timeValues; 260 } 261 262 263 /** 264 * Get the password storage scheme used by a given password value. 265 * 266 * @param v The encoded password value to check. 267 * 268 * @return The scheme used by the password. 269 * 270 * @throws DirectoryException If the password could not be decoded. 271 */ 272 private PasswordStorageScheme<?> getPasswordStorageScheme(ByteString v) throws DirectoryException 273 { 274 if (passwordPolicy.isAuthPasswordSyntax()) 275 { 276 String[] pwComps = AuthPasswordSyntax.decodeAuthPassword(v.toString()); 277 return DirectoryServer.getAuthPasswordStorageScheme(pwComps[0]); 278 } 279 else 280 { 281 String[] pwComps = UserPasswordSyntax.decodeUserPassword(v.toString()); 282 return DirectoryServer.getPasswordStorageScheme(pwComps[0]); 283 } 284 } 285 286 @Override 287 public PasswordPolicy getAuthenticationPolicy() 288 { 289 return passwordPolicy; 290 } 291 292 293 294 /** 295 * Retrieves the time that the password was last changed. 296 * 297 * @return The time that the password was last changed. 298 */ 299 public long getPasswordChangedTime() 300 { 301 if (passwordChangedTime < 0) 302 { 303 // Get the password changed time for the user. 304 try 305 { 306 passwordChangedTime = getGeneralizedTime0(userEntry, OP_ATTR_PWPOLICY_CHANGED_TIME_LC); 307 } 308 catch (DirectoryException e) 309 { 310 /* 311 * The password change time could not be parsed (but has been logged in the debug log). 312 * The best effort we can do from here is to a) use the current time, b) use the start 313 * of the epoch (1/1/1970), or c) use the create time stamp. Lets treat this problem as if the change time 314 * attribute did not exist and resort to the create time stamp. 315 */ 316 } 317 318 if (passwordChangedTime < 0) 319 { 320 // Get the time that the user's account was created. 321 try 322 { 323 passwordChangedTime = getGeneralizedTime0(userEntry, OP_ATTR_CREATE_TIMESTAMP_LC); 324 } 325 catch (DirectoryException e) 326 { 327 /* 328 * The create time stamp could not be parsed (but has been logged in the debug log). 329 * The best effort we can do from here is to a) use the current time, or b) use the start of 330 * the epoch (1/1/1970). Lets treat this problem as if the change time attribute did not exist 331 * and use the start of the epoch. Doing so stands a greater chance of forcing a password change. 332 */ 333 } 334 335 if (passwordChangedTime < 0) 336 { 337 passwordChangedTime = 0; 338 339 if (logger.isTraceEnabled()) 340 { 341 logger.trace( 342 "Could not determine password changed time for " + "user %s.", userDNString); 343 } 344 } 345 } 346 } 347 348 return passwordChangedTime; 349 } 350 351 352 private long getGeneralizedTime0(Entry userEntry, String attrName) throws DirectoryException 353 { 354 return getGeneralizedTime(userEntry, DirectoryServer.getAttributeTypeOrDefault(attrName)); 355 } 356 357 /** 358 * Retrieves the time that this password policy state object was created. 359 * 360 * @return The time that this password policy state object was created. 361 */ 362 public long getCurrentTime() 363 { 364 return currentTime; 365 } 366 367 368 369 /** 370 * Retrieves the unmodifiable set of values for the password attribute from the user entry. 371 * 372 * @return The unmodifiable set of values for the password attribute from the user entry. 373 */ 374 public Set<ByteString> getPasswordValues() 375 { 376 final Attribute attr = getFirstAttributeNotEmpty(passwordPolicy.getPasswordAttribute()); 377 if (attr != null) 378 { 379 Set<ByteString> values = new LinkedHashSet<>(attr.size()); 380 for (ByteString value : attr) 381 { 382 values.add(value); 383 } 384 return Collections.unmodifiableSet(values); 385 } 386 return Collections.emptySet(); 387 } 388 389 390 391 /** 392 * Sets a new value for the password changed time equal to the current time. 393 */ 394 public void setPasswordChangedTime() 395 { 396 setPasswordChangedTime(currentTime); 397 } 398 399 400 /** 401 * Sets a new value for the password changed time equal to the specified time. 402 * This method should generally only be used for testing purposes, since the variant that uses 403 * the current time is preferred almost everywhere else. 404 * 405 * @param passwordChangedTime The time to use 406 */ 407 public void setPasswordChangedTime(long passwordChangedTime) 408 { 409 if (logger.isTraceEnabled()) 410 { 411 logger.trace("Setting password changed time for user %s to current time of %d", userDNString, currentTime); 412 } 413 414 // passwordChangedTime is computed in the constructor from values in the entry. 415 if (getPasswordChangedTime() != passwordChangedTime) 416 { 417 this.passwordChangedTime = passwordChangedTime; 418 419 String timeValue = GeneralizedTimeSyntax.format(passwordChangedTime); 420 Attribute a = Attributes.create(OP_ATTR_PWPOLICY_CHANGED_TIME, timeValue); 421 422 modifications.add(new Modification(ModificationType.REPLACE, a, true)); 423 } 424 } 425 426 427 428 /** 429 * Removes the password changed time value from the user's entry. This should only be used for testing 430 * purposes, as it can really mess things up if you don't know what you're doing. 431 */ 432 public void clearPasswordChangedTime() 433 { 434 if (logger.isTraceEnabled()) 435 { 436 logger.trace("Clearing password changed time for user %s", userDNString); 437 } 438 439 Attribute a = Attributes.empty(OP_ATTR_PWPOLICY_CHANGED_TIME_LC); 440 modifications.add(new Modification(ModificationType.REPLACE, a, true)); 441 442 443 // Fall back to using the entry creation time as the password changed time, if it's defined. 444 // Otherwise, use a value of zero. 445 try 446 { 447 passwordChangedTime = getGeneralizedTime0(userEntry, OP_ATTR_CREATE_TIMESTAMP_LC); 448 if (passwordChangedTime < 0) 449 { 450 passwordChangedTime = 0; 451 } 452 } 453 catch (Exception e) 454 { 455 passwordChangedTime = 0; 456 } 457 } 458 459 460 461 /** 462 * Updates the user entry to indicate whether user account has been administratively disabled. 463 * 464 * @param isDisabled 465 * Indicates whether the user account has been administratively disabled. 466 */ 467 public void setDisabled(boolean isDisabled) 468 { 469 if (logger.isTraceEnabled()) 470 { 471 logger.trace("Updating user %s to set the disabled flag to %b", userDNString, isDisabled); 472 } 473 474 475 if (isDisabled == isDisabled()) 476 { 477 return; // requested state matches current state 478 } 479 480 this.isDisabled = ConditionResult.not(this.isDisabled); 481 482 if (isDisabled) 483 { 484 Attribute a = Attributes.create(OP_ATTR_ACCOUNT_DISABLED, String.valueOf(true)); 485 modifications.add(new Modification(ModificationType.REPLACE, a, true)); 486 } 487 else 488 { 489 // erase 490 modifications.add(new Modification(ModificationType.REPLACE, Attributes.empty(OP_ATTR_ACCOUNT_DISABLED), true)); 491 } 492 } 493 494 495 /** 496 * Indicates whether the user's account is currently expired. 497 * 498 * @return <CODE>true</CODE> if the user's account is expired, or <CODE>false</CODE> if not. 499 */ 500 public boolean isAccountExpired() 501 { 502 if (isAccountExpired != ConditionResult.UNDEFINED) 503 { 504 if (logger.isTraceEnabled()) 505 { 506 logger.trace("Returning stored result of %b for user %s", 507 isAccountExpired == ConditionResult.TRUE, userDNString); 508 } 509 510 return isAccountExpired == ConditionResult.TRUE; 511 } 512 513 try { 514 accountExpirationTime = getGeneralizedTime0(userEntry, OP_ATTR_ACCOUNT_EXPIRATION_TIME); 515 } 516 catch (Exception e) 517 { 518 logger.traceException(e, "User %s is considered to have an expired account because an error occurred " + 519 "while attempting to make the determination.", userDNString); 520 521 isAccountExpired = ConditionResult.TRUE; 522 return true; 523 } 524 525 if (accountExpirationTime > currentTime) 526 { 527 // The user does have an expiration time, but it hasn't arrived yet. 528 isAccountExpired = ConditionResult.FALSE; 529 logger.trace("The account for user %s is not expired because the expiration time has not yet arrived.", 530 userDNString); 531 } 532 else if (accountExpirationTime >= 0) 533 { 534 // The user does have an expiration time, and it is in the past. 535 isAccountExpired = ConditionResult.TRUE; 536 logger.trace("The account for user %s is expired because the expiration time in that account has passed.", 537 userDNString); 538 } 539 else 540 { 541 // The user doesn't have an expiration time in their entry, so it can't be expired. 542 isAccountExpired = ConditionResult.FALSE; 543 logger.trace("The account for user %s is not expired because there is no expiration time in the user's entry.", 544 userDNString); 545 } 546 547 return isAccountExpired == ConditionResult.TRUE; 548 } 549 550 551 552 /** 553 * Retrieves the time at which the user's account will expire. 554 * 555 * @return The time at which the user's account will expire, or -1 if it is not configured with an expiration time. 556 */ 557 public long getAccountExpirationTime() 558 { 559 if (accountExpirationTime == Long.MIN_VALUE) 560 { 561 isAccountExpired(); 562 } 563 564 return accountExpirationTime; 565 } 566 567 568 569 /** 570 * Sets the user's account expiration time to the specified value. 571 * 572 * @param accountExpirationTime The time that the user's account should expire. 573 */ 574 public void setAccountExpirationTime(long accountExpirationTime) 575 { 576 if (accountExpirationTime < 0) 577 { 578 clearAccountExpirationTime(); 579 } 580 else 581 { 582 String timeStr = GeneralizedTimeSyntax.format(accountExpirationTime); 583 584 if (logger.isTraceEnabled()) 585 { 586 logger.trace("Setting account expiration time for user %s to %s", userDNString, timeStr); 587 } 588 589 this.accountExpirationTime = accountExpirationTime; 590 591 Attribute a = Attributes.create(OP_ATTR_ACCOUNT_EXPIRATION_TIME, timeStr); 592 modifications.add(new Modification(ModificationType.REPLACE, a, true)); 593 } 594 } 595 596 597 598 /** 599 * Clears the user's account expiration time. 600 */ 601 public void clearAccountExpirationTime() 602 { 603 if (logger.isTraceEnabled()) 604 { 605 logger.trace("Clearing account expiration time for user %s", userDNString); 606 } 607 608 accountExpirationTime = -1; 609 610 String attrName = OP_ATTR_ACCOUNT_EXPIRATION_TIME; 611 modifications.add(new Modification(ModificationType.REPLACE, Attributes.empty(attrName), true)); 612 } 613 614 615 616 /** 617 * Retrieves the set of times of failed authentication attempts for the user. If authentication failure 618 * time expiration is enabled, and there are expired times in the entry, these times are removed 619 * from the instance field and an update is provided to delete those values from the entry. 620 * 621 * @return The set of times of failed authentication attempts for the user, which will be an empty list 622 * in the case of no valid (unexpired) times in the entry. 623 */ 624 public List<Long> getAuthFailureTimes() 625 { 626 if (authFailureTimes != null) 627 { 628 if (logger.isTraceEnabled()) 629 { 630 logger.trace("Returning stored auth failure time list of %d elements for user %s", 631 authFailureTimes.size(), userDNString); 632 } 633 634 return authFailureTimes; 635 } 636 637 AttributeType type = DirectoryServer.getAttributeTypeOrDefault( 638 OP_ATTR_PWPOLICY_FAILURE_TIME_LC, OP_ATTR_PWPOLICY_FAILURE_TIME); 639 try 640 { 641 authFailureTimes = getGeneralizedTimes(type); 642 } 643 catch (Exception e) 644 { 645 logger.traceException(e, "Error while processing auth failure times for user %s", userDNString); 646 647 authFailureTimes = new ArrayList<>(); 648 modifications.add(new Modification(ModificationType.REPLACE, Attributes.empty(type), true)); 649 return authFailureTimes; 650 } 651 652 if (authFailureTimes.isEmpty()) 653 { 654 if (logger.isTraceEnabled()) 655 { 656 logger.trace("Returning an empty auth failure time list for user %s because the attribute" + 657 " is absent from the entry.", userDNString); 658 } 659 660 return authFailureTimes; 661 } 662 663 // Remove any expired failures from the list. 664 if (passwordPolicy.getLockoutFailureExpirationInterval() > 0) 665 { 666 LinkedHashSet<ByteString> valuesToRemove = null; 667 668 long expirationTime = currentTime - passwordPolicy.getLockoutFailureExpirationInterval() * 1000L; 669 Iterator<Long> iterator = authFailureTimes.iterator(); 670 while (iterator.hasNext()) 671 { 672 long l = iterator.next(); 673 if (l < expirationTime) 674 { 675 if (logger.isTraceEnabled()) 676 { 677 logger.trace("Removing expired auth failure time %d for user %s", l, userDNString); 678 } 679 680 iterator.remove(); 681 682 if (valuesToRemove == null) 683 { 684 valuesToRemove = new LinkedHashSet<>(); 685 } 686 687 valuesToRemove.add(ByteString.valueOfUtf8(GeneralizedTimeSyntax.format(l))); 688 } 689 } 690 691 if (valuesToRemove != null) 692 { 693 Attribute a = newAttribute(type, valuesToRemove); 694 modifications.add(new Modification(ModificationType.DELETE, a, true)); 695 } 696 } 697 698 if (logger.isTraceEnabled()) 699 { 700 logger.trace("Returning auth failure time list of %d elements for user %s", 701 authFailureTimes.size(), userDNString); 702 } 703 704 return authFailureTimes; 705 } 706 707 708 /** 709 * Updates the set of authentication failure times to include the current time. 710 * If the number of failures reaches the policy configuration limit, lock the account. 711 */ 712 public void updateAuthFailureTimes() 713 { 714 if (passwordPolicy.getLockoutFailureCount() <= 0) 715 { 716 return; 717 } 718 719 if (logger.isTraceEnabled()) 720 { 721 logger.trace("Updating authentication failure times for user %s", userDNString); 722 } 723 724 725 List<Long> failureTimes = getAuthFailureTimes(); 726 long highestFailureTime = computeHighestTime(failureTimes); 727 // Update the current policy state 728 failureTimes.add(highestFailureTime); 729 730 // And the attribute in the user entry 731 AttributeType type = DirectoryServer.getAttributeTypeOrDefault( 732 OP_ATTR_PWPOLICY_FAILURE_TIME_LC, OP_ATTR_PWPOLICY_FAILURE_TIME); 733 Attribute addAttr = Attributes.create(type, GeneralizedTimeSyntax.format(highestFailureTime)); 734 modifications.add(new Modification(ModificationType.ADD, addAttr, true)); 735 736 // Now check to see if there have been sufficient failures to lock the account. 737 int lockoutCount = passwordPolicy.getLockoutFailureCount(); 738 if (lockoutCount > 0 && lockoutCount <= authFailureTimes.size()) 739 { 740 setFailureLockedTime(highestFailureTime); 741 if (logger.isTraceEnabled()) 742 { 743 logger.trace("Locking user account %s due to too many failures.", userDNString); 744 } 745 } 746 } 747 748 749 750 /** 751 * Explicitly specifies the auth failure times for the associated user. This should generally only be used 752 * for testing purposes. Note that it will also set or clear the locked time as appropriate. 753 * 754 * @param authFailureTimes The set of auth failure times to use for the account. An empty list or 755 * {@code null} will clear the account of any existing failures. 756 */ 757 public void setAuthFailureTimes(List<Long> authFailureTimes) 758 { 759 if (authFailureTimes == null || authFailureTimes.isEmpty()) 760 { 761 clearAuthFailureTimes(); 762 clearFailureLockedTime(); 763 return; 764 } 765 766 this.authFailureTimes = authFailureTimes; 767 768 AttributeBuilder builder = new AttributeBuilder(OP_ATTR_PWPOLICY_FAILURE_TIME_LC); 769 long highestFailureTime = -1; 770 771 for (long l : authFailureTimes) 772 { 773 highestFailureTime = Math.max(l, highestFailureTime); 774 builder.add(GeneralizedTimeSyntax.format(l)); 775 } 776 Attribute a = builder.toAttribute(); 777 778 modifications.add(new Modification(ModificationType.REPLACE, a, true)); 779 780 // Now check to see if there have been sufficient failures to lock the account. 781 int lockoutCount = passwordPolicy.getLockoutFailureCount(); 782 if (lockoutCount > 0 && lockoutCount <= authFailureTimes.size()) 783 { 784 setFailureLockedTime(highestFailureTime); 785 if (logger.isTraceEnabled()) 786 { 787 logger.trace("Locking user account %s due to too many failures.", userDNString); 788 } 789 } 790 } 791 792 793 794 /** 795 * Updates the user entry to remove any record of previous authentication failure times. 796 */ 797 private void clearAuthFailureTimes() 798 { 799 if (logger.isTraceEnabled()) 800 { 801 logger.trace("Clearing authentication failure times for user %s", userDNString); 802 } 803 804 List<Long> failureTimes = getAuthFailureTimes(); 805 if (failureTimes.isEmpty()) 806 { 807 return; 808 } 809 810 failureTimes.clear(); // Note: failureTimes != this.authFailureTimes 811 812 AttributeType type = DirectoryServer.getAttributeTypeOrDefault( 813 OP_ATTR_PWPOLICY_FAILURE_TIME_LC, OP_ATTR_PWPOLICY_FAILURE_TIME); 814 modifications.add(new Modification(ModificationType.REPLACE, Attributes.empty(type), true)); 815 } 816 817 818 /** 819 * Retrieves the time of an authentication failure lockout for the user. 820 * 821 * @return The time of an authentication failure lockout for the user, or -1 if no such time is present in the entry. 822 */ 823 private long getFailureLockedTime() 824 { 825 if (failureLockedTime != Long.MIN_VALUE) 826 { 827 return failureLockedTime; 828 } 829 830 AttributeType type = DirectoryServer.getAttributeTypeOrDefault( 831 OP_ATTR_PWPOLICY_LOCKED_TIME_LC, OP_ATTR_PWPOLICY_LOCKED_TIME); 832 try 833 { 834 failureLockedTime = getGeneralizedTime(userEntry, type); 835 } 836 catch (Exception e) 837 { 838 logger.traceException(e, "Returning current time for user %s because an error occurred", userDNString); 839 840 failureLockedTime = currentTime; 841 return failureLockedTime; 842 } 843 844 // An expired locked time is handled in lockedDueToFailures. 845 return failureLockedTime; 846 } 847 848 849 850 /** 851 Sets the failure lockout attribute in the entry to the requested time. 852 853 @param time The time to which to set the entry's failure lockout attribute. 854 */ 855 private void setFailureLockedTime(final long time) 856 { 857 if (time == getFailureLockedTime()) 858 { 859 return; 860 } 861 862 failureLockedTime = time; 863 864 AttributeType type = DirectoryServer.getAttributeTypeOrDefault( 865 OP_ATTR_PWPOLICY_LOCKED_TIME_LC, OP_ATTR_PWPOLICY_LOCKED_TIME); 866 Attribute a = Attributes.create(type, GeneralizedTimeSyntax.format(failureLockedTime)); 867 modifications.add(new Modification(ModificationType.REPLACE, a, true)); 868 } 869 870 871 872 /** 873 * Updates the user entry to remove any record of previous authentication failure lockout. 874 */ 875 private void clearFailureLockedTime() 876 { 877 if (logger.isTraceEnabled()) 878 { 879 logger.trace("Clearing failure lockout time for user %s.", userDNString); 880 } 881 882 if (-1L == getFailureLockedTime()) 883 { 884 return; 885 } 886 887 failureLockedTime = -1L; 888 889 AttributeType type = DirectoryServer.getAttributeTypeOrDefault( 890 OP_ATTR_PWPOLICY_LOCKED_TIME_LC, OP_ATTR_PWPOLICY_LOCKED_TIME); 891 modifications.add(new Modification(ModificationType.REPLACE, Attributes.empty(type), true)); 892 } 893 894 895 /** 896 * Indicates whether the associated user should be considered locked out as a result of too many 897 * authentication failures. In the case of an expired lock-out, this routine produces the update 898 * to clear the lock-out attribute and the authentication failure timestamps. 899 * In case the failure lockout time is absent from the entry, but sufficient authentication failure 900 * timestamps are present in the entry, this routine produces the update to set the lock-out attribute. 901 * 902 * @return <CODE>true</CODE> if the user is currently locked out due to too many authentication failures, 903 * or <CODE>false</CODE> if not. 904 */ 905 public boolean lockedDueToFailures() 906 { 907 // FIXME: Introduce a state field to cache the computed value of this method. 908 // Note that only a cached "locked" status can be returned due to the possibility of intervening updates to 909 // this.failureLockedTime by updateAuthFailureTimes. 910 911 // Check if the feature is enabled in the policy. 912 final int maxFailures = passwordPolicy.getLockoutFailureCount(); 913 if (maxFailures <= 0) 914 { 915 if (logger.isTraceEnabled()) 916 { 917 logger.trace("Returning false for user %s because lockout due to failures is not enabled.", userDNString); 918 } 919 920 return false; 921 } 922 923 // Get the locked time from the user's entry. If it is present and not expired, the account is locked. 924 // If it is absent, the failure timestamps must be checked, since failure timestamps sufficient to lock the 925 // account could be produced across the synchronization topology within the synchronization latency. 926 // Also, note that IETF draft-behera-ldap-password-policy-09 specifies "19700101000000Z" as the value to be set 927 // under a "locked until reset" regime; however, this implementation accepts the value as a locked entry, 928 // but observes the lockout expiration policy for all values including this one. 929 // FIXME: This "getter" is unusual in that it might produce an update to the entry in two cases. 930 // Does it make sense to factor the methods so that, e.g., an expired lockout is reported, and clearing 931 // the lockout is left to the caller? 932 if (getFailureLockedTime() < 0L) 933 { 934 // There was no locked time present in the entry; however, sufficient failure times might have accumulated 935 // to trigger a lockout. 936 if (getAuthFailureTimes().size() < maxFailures) 937 { 938 if (logger.isTraceEnabled()) 939 { 940 logger.trace("Returning false for user %s because there is no locked time.", userDNString); 941 } 942 943 return false; 944 } 945 946 // The account isn't locked but should be, so do so now. 947 setFailureLockedTime(currentTime);// FIXME: set to max(failureTimes)? 948 949 if (logger.isTraceEnabled()) 950 { 951 logger.trace("Locking user %s because there were enough existing failures even though there was" + 952 " no account locked time.", userDNString); 953 } 954 // Fall through... 955 } 956 957 // There is a failure locked time, but it may be expired. 958 if (passwordPolicy.getLockoutDuration() > 0) 959 { 960 final long unlockTime = getFailureLockedTime() + 1000L * passwordPolicy.getLockoutDuration(); 961 if (unlockTime > currentTime) 962 { 963 secondsUntilUnlock = (int) ((unlockTime - currentTime) / 1000); 964 965 if (logger.isTraceEnabled()) 966 { 967 logger.trace("Returning true for user %s because there is a locked time and the lockout duration has" + 968 " not been reached.", userDNString); 969 } 970 971 return true; 972 } 973 974 // The lockout in the entry has expired... 975 clearFailureLockout(); 976 977 if (logger.isTraceEnabled()) 978 { 979 logger.trace("Returning false for user %s because the existing lockout has expired.", userDNString); 980 } 981 982 assert -1L == getFailureLockedTime(); 983 return false; 984 } 985 986 if (logger.isTraceEnabled()) 987 { 988 logger.trace("Returning true for user %s because there is a locked time and no lockout duration.", userDNString); 989 } 990 991 assert -1L <= getFailureLockedTime(); 992 return true; 993 } 994 995 996 997 /** 998 * Retrieves the length of time in seconds until the user's account is automatically unlocked. 999 * This should only be called after calling <CODE>lockedDueToFailures</CODE>. 1000 * 1001 * @return The length of time in seconds until the user's account is automatically unlocked, or -1 if the account 1002 * is not locked or the lockout requires administrative action to clear. 1003 */ 1004 public int getSecondsUntilUnlock() 1005 { 1006 // secondsUntilUnlock is only set when failureLockedTime is present and PasswordPolicy.getLockoutDuration 1007 // is enabled; hence it is not unreasonable to find secondsUntilUnlock uninitialized. 1008 assert failureLockedTime != Long.MIN_VALUE; 1009 1010 return secondsUntilUnlock < 0 ? -1 : secondsUntilUnlock; 1011 } 1012 1013 1014 1015 /** 1016 * Updates the user account to remove any record of a previous lockout due to failed authentications. 1017 */ 1018 public void clearFailureLockout() 1019 { 1020 clearAuthFailureTimes(); 1021 clearFailureLockedTime(); 1022 } 1023 1024 1025 1026 /** 1027 * Retrieves the time that the user last authenticated to the Directory Server. 1028 * 1029 * @return The time that the user last authenticated to the Directory Server, or -1 if it cannot be determined. 1030 */ 1031 public long getLastLoginTime() 1032 { 1033 if (lastLoginTime != Long.MIN_VALUE) 1034 { 1035 if (logger.isTraceEnabled()) 1036 { 1037 logger.trace("Returning stored last login time of %d for user %s.", lastLoginTime, userDNString); 1038 } 1039 1040 return lastLoginTime; 1041 } 1042 1043 // The policy configuration must be checked since the entry cannot be evaluated without both an attribute 1044 // name and timestamp format. 1045 AttributeType type = passwordPolicy.getLastLoginTimeAttribute(); 1046 String format = passwordPolicy.getLastLoginTimeFormat(); 1047 1048 if (type == null || format == null) 1049 { 1050 lastLoginTime = -1; 1051 if (logger.isTraceEnabled()) 1052 { 1053 logger.trace("Returning -1 for user %s because no last login time will be maintained.", userDNString); 1054 } 1055 1056 return lastLoginTime; 1057 } 1058 1059 boolean isGeneralizedTime = SYNTAX_GENERALIZED_TIME_NAME.equals(type.getSyntax().getName()); 1060 lastLoginTime = -1; 1061 List<Attribute> attrList = userEntry.getAttribute(type); 1062 1063 if (attrList != null) 1064 { 1065 for (Attribute a : attrList) 1066 { 1067 if (a.isEmpty()) 1068 { 1069 continue; 1070 } 1071 1072 String valueString = a.iterator().next().toString(); 1073 try 1074 { 1075 lastLoginTime = parseTime(format, valueString, isGeneralizedTime); 1076 1077 if (logger.isTraceEnabled()) 1078 { 1079 logger.trace("Returning last login time of %d for user %s, decoded using current last login time format.", 1080 lastLoginTime, userDNString); 1081 } 1082 1083 return lastLoginTime; 1084 } 1085 catch (Exception e) 1086 { 1087 logger.traceException(e); 1088 1089 // This could mean that the last login time was encoded using a previous format. 1090 for (String f : passwordPolicy.getPreviousLastLoginTimeFormats()) 1091 { 1092 try 1093 { 1094 lastLoginTime = parseTime(f, valueString, isGeneralizedTime); 1095 1096 if (logger.isTraceEnabled()) 1097 { 1098 logger.trace("Returning last login time of %d for user %s decoded using previous last login time " + 1099 "format of %s.", lastLoginTime, userDNString, f); 1100 } 1101 1102 return lastLoginTime; 1103 } 1104 catch (Exception e2) 1105 { 1106 logger.traceException(e); 1107 } 1108 } 1109 1110 assert lastLoginTime == -1; 1111 if (logger.isTraceEnabled()) 1112 { 1113 logger.trace("Returning -1 for user %s because the last login time value %s could not be parsed " + 1114 "using any known format.", userDNString, valueString); 1115 } 1116 1117 return lastLoginTime; 1118 } 1119 } 1120 } 1121 1122 assert lastLoginTime == -1; 1123 if (logger.isTraceEnabled()) 1124 { 1125 logger.trace("Returning %d for user %s because no last login time value exists.", lastLoginTime, userDNString); 1126 } 1127 1128 return lastLoginTime; 1129 } 1130 1131 private long parseTime(String format, String time, boolean isGeneralizedTime) throws ParseException 1132 { 1133 SimpleDateFormat dateFormat = new SimpleDateFormat(format); 1134 if (isGeneralizedTime) 1135 { 1136 dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); 1137 } 1138 return dateFormat.parse(time).getTime(); 1139 } 1140 1141 /** 1142 * Updates the user entry to set the current time as the last login time. 1143 */ 1144 public void setLastLoginTime() 1145 { 1146 setLastLoginTime(currentTime); 1147 } 1148 1149 1150 1151 /** 1152 * Updates the user entry to use the specified last login time. This should be used primarily for testing purposes, 1153 * as the variant that uses the current time should be used most of the time. 1154 * 1155 * @param lastLoginTime The last login time to set in the user entry. 1156 */ 1157 public void setLastLoginTime(long lastLoginTime) 1158 { 1159 AttributeType type = passwordPolicy.getLastLoginTimeAttribute(); 1160 String format = passwordPolicy.getLastLoginTimeFormat(); 1161 1162 if (type == null || format == null) 1163 { 1164 return; 1165 } 1166 1167 String timestamp; 1168 try 1169 { 1170 SimpleDateFormat dateFormat = new SimpleDateFormat(format); 1171 // If the attribute has a Generalized Time syntax, make it UTC time. 1172 if (SYNTAX_GENERALIZED_TIME_NAME.equals(type.getSyntax().getName())) 1173 { 1174 dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); 1175 } 1176 timestamp = dateFormat.format(new Date(lastLoginTime)); 1177 this.lastLoginTime = dateFormat.parse(timestamp).getTime(); 1178 } 1179 catch (Exception e) 1180 { 1181 logger.traceException(e, "Unable to set last login time for user %s because an error occurred", userDNString); 1182 return; 1183 } 1184 1185 1186 String existingTimestamp = getValue(type); 1187 if (existingTimestamp != null && timestamp.equals(existingTimestamp)) 1188 { 1189 logger.trace("Not updating last login time for user %s because the new value matches the existing value.", 1190 userDNString); 1191 return; 1192 } 1193 1194 1195 Attribute a = Attributes.create(type, timestamp); 1196 modifications.add(new Modification(ModificationType.REPLACE, a, true)); 1197 1198 logger.trace("Updated the last login time for user %s to %s", userDNString, timestamp); 1199 } 1200 1201 1202 1203 /** 1204 * Clears the last login time from the user's entry. This should generally be used only for testing purposes. 1205 */ 1206 public void clearLastLoginTime() 1207 { 1208 if (logger.isTraceEnabled()) 1209 { 1210 logger.trace("Clearing last login time for user %s", userDNString); 1211 } 1212 1213 lastLoginTime = -1; 1214 1215 modifications.add(new Modification(ModificationType.REPLACE, Attributes.empty(OP_ATTR_LAST_LOGIN_TIME), true)); 1216 } 1217 1218 1219 /** 1220 * Indicates whether the user's account is currently locked because it has been idle for too long. 1221 * 1222 * @return <CODE>true</CODE> if the user's account is locked because it has been idle for too long, 1223 * or <CODE>false</CODE> if not. 1224 */ 1225 public boolean lockedDueToIdleInterval() 1226 { 1227 if (isIdleLocked != ConditionResult.UNDEFINED) 1228 { 1229 if (logger.isTraceEnabled()) 1230 { 1231 logger.trace("Returning stored result of %b for user %s", isIdleLocked == ConditionResult.TRUE, userDNString); 1232 } 1233 1234 return isIdleLocked == ConditionResult.TRUE; 1235 } 1236 1237 // Return immediately if this feature is disabled, since the feature is not responsible for any state attribute 1238 // in the entry. 1239 if (passwordPolicy.getIdleLockoutInterval() <= 0) 1240 { 1241 isIdleLocked = ConditionResult.FALSE; 1242 1243 if (logger.isTraceEnabled()) 1244 { 1245 logger.trace("Returning false for user %s because no idle lockout interval is defined.", userDNString); 1246 } 1247 return false; 1248 } 1249 1250 long lockTime = currentTime - 1000L * passwordPolicy.getIdleLockoutInterval(); 1251 if (lockTime < 0) 1252 { 1253 lockTime = 0; 1254 } 1255 1256 long theLastLoginTime = getLastLoginTime(); 1257 if (theLastLoginTime > lockTime || getPasswordChangedTime() > lockTime) 1258 { 1259 isIdleLocked = ConditionResult.FALSE; 1260 if (logger.isTraceEnabled()) 1261 { 1262 StringBuilder reason = new StringBuilder(); 1263 if(theLastLoginTime > lockTime) 1264 { 1265 reason.append("the last login time is in an acceptable window"); 1266 } 1267 else 1268 { 1269 if(theLastLoginTime < 0) 1270 { 1271 reason.append("there is no last login time, but "); 1272 } 1273 reason.append("the password changed time is in an acceptable window"); 1274 } 1275 logger.trace("Returning false for user %s because %s.", userDNString, reason); 1276 } 1277 } 1278 else 1279 { 1280 isIdleLocked = ConditionResult.TRUE; 1281 if (logger.isTraceEnabled()) 1282 { 1283 String reason = theLastLoginTime < 0 1284 ? "there is no last login time and the password changed time is not in an acceptable window" 1285 : "neither last login time nor password changed time are in an acceptable window"; 1286 logger.trace("Returning true for user %s because %s.", userDNString, reason); 1287 } 1288 } 1289 1290 return isIdleLocked == ConditionResult.TRUE; 1291 } 1292 1293 1294 1295/** 1296* Indicates whether the user's password must be changed before any other operation can be performed. 1297* 1298* @return <CODE>true</CODE> if the user's password must be changed before any other operation can be performed. 1299*/ 1300 public boolean mustChangePassword() 1301 { 1302 if(mustChangePassword != ConditionResult.UNDEFINED) 1303 { 1304 if (logger.isTraceEnabled()) 1305 { 1306 logger.trace("Returning stored result of %b for user %s.", 1307 mustChangePassword == ConditionResult.TRUE, userDNString); 1308 } 1309 1310 return mustChangePassword == ConditionResult.TRUE; 1311 } 1312 1313 // If the password policy doesn't use force change on add or force change on reset, or if it forbids the user 1314 // from changing his password, then return false. 1315 // FIXME: the only getter responsible for a state attribute (pwdReset) that considers the policy before 1316 // checking the entry for the presence of the attribute. 1317 if (!passwordPolicy.isAllowUserPasswordChanges() 1318 || (!passwordPolicy.isForceChangeOnAdd() && !passwordPolicy.isForceChangeOnReset())) 1319 { 1320 mustChangePassword = ConditionResult.FALSE; 1321 if (logger.isTraceEnabled()) 1322 { 1323 logger.trace("Returning false for user %s because neither force change on add nor force change on reset" + 1324 " is enabled, or users are not allowed to self-modify passwords.", userDNString); 1325 } 1326 1327 return false; 1328 } 1329 1330 AttributeType type = DirectoryServer.getAttributeTypeOrDefault( 1331 OP_ATTR_PWPOLICY_RESET_REQUIRED_LC, OP_ATTR_PWPOLICY_RESET_REQUIRED); 1332 try 1333 { 1334 mustChangePassword = getBoolean(userEntry, type); 1335 } 1336 catch (Exception e) 1337 { 1338 logger.traceException(e, "Returning true for user %s because an error occurred", userDNString); 1339 1340 mustChangePassword = ConditionResult.TRUE; 1341 1342 return true; 1343 } 1344 1345 if(mustChangePassword == ConditionResult.UNDEFINED) 1346 { 1347 mustChangePassword = ConditionResult.FALSE; 1348 logger.trace("Returning %b for user since the attribute \"%s\" is not present in the entry.", 1349 false, userDNString, OP_ATTR_PWPOLICY_RESET_REQUIRED); 1350 1351 return false; 1352 } 1353 1354 final boolean result = mustChangePassword == ConditionResult.TRUE; 1355 logger.trace("Returning %b for user %s.", result, userDNString); 1356 return result; 1357 } 1358 1359 1360 1361/** 1362* Updates the user entry to indicate whether the user's password must be changed. 1363* 1364* @param mustChangePassword Indicates whether the user's password must be changed. 1365*/ 1366 public void setMustChangePassword(boolean mustChangePassword) 1367 { 1368 if (logger.isTraceEnabled()) 1369 { 1370 logger.trace("Updating user %s to set the reset flag to %b", userDNString, mustChangePassword); 1371 } 1372 1373 if (mustChangePassword == mustChangePassword()) 1374 { 1375 return; // requested state matches current state 1376 } 1377 1378 AttributeType type = DirectoryServer.getAttributeTypeOrDefault( 1379 OP_ATTR_PWPOLICY_RESET_REQUIRED_LC, OP_ATTR_PWPOLICY_RESET_REQUIRED); 1380 this.mustChangePassword = ConditionResult.not(this.mustChangePassword); 1381 if (mustChangePassword) 1382 { 1383 Attribute a = Attributes.create(type, String.valueOf(true)); 1384 modifications.add(new Modification(ModificationType.REPLACE, a, true)); 1385 } 1386 else 1387 { 1388 modifications.add(new Modification(ModificationType.REPLACE, Attributes.empty(type), true)); 1389 } 1390 } 1391 1392 1393 /** 1394 * Indicates whether the user's account is locked because the password has been reset by an administrator 1395 * but the user did not change the password in a timely manner. 1396 * 1397 * @return <CODE>true</CODE> if the user's account is locked because of the maximum reset age, 1398 * or <CODE>false</CODE> if not. 1399 */ 1400 public boolean lockedDueToMaximumResetAge() 1401 { 1402 // This feature is responsible for neither a state field nor an entry state attribute. 1403 if (passwordPolicy.getMaxPasswordResetAge() <= 0L) 1404 { 1405 if (logger.isTraceEnabled()) 1406 { 1407 logger.trace("Returning false for user %s because there is no maximum reset age.", userDNString); 1408 } 1409 1410 return false; 1411 } 1412 1413 if (! mustChangePassword()) 1414 { 1415 if (logger.isTraceEnabled()) 1416 { 1417 logger.trace("Returning false for user %s because the user's password has not been reset.", userDNString); 1418 } 1419 1420 return false; 1421 } 1422 1423 long maxResetTime = getPasswordChangedTime() + 1000L * passwordPolicy.getMaxPasswordResetAge(); 1424 boolean locked = maxResetTime < currentTime; 1425 1426 if (logger.isTraceEnabled()) 1427 { 1428 logger.trace("Returning %b for user %s after comparing the current and max reset times.", locked, userDNString); 1429 } 1430 1431 return locked; 1432 } 1433 1434 /** 1435 * Returns whether the account was locked for any reason. 1436 * 1437 * @return true if the account is locked, false otherwise 1438 */ 1439 public boolean isLocked() 1440 { 1441 return lockedDueToIdleInterval() || lockedDueToMaximumResetAge() || lockedDueToFailures(); 1442 } 1443 1444 /** 1445 * Retrieves the time that the user's password should expire (if the expiration is in the future) or 1446 * did expire (if the expiration was in the past). Note that this method should be called after the 1447 * <CODE>lockedDueToMaximumResetAge</CODE> method because grace logins will not be allowed in the case 1448 * that the maximum reset age has passed whereas they may be used for expiration due to maximum password 1449 * age or forced change time. 1450 * 1451 * @return The time that the user's password should/did expire, or -1 if it should not expire. 1452 */ 1453 public long getPasswordExpirationTime() 1454 { 1455 if (passwordExpirationTime == Long.MIN_VALUE) 1456 { 1457 passwordExpirationTime = Long.MAX_VALUE; 1458 1459 boolean checkWarning = false; 1460 1461 long maxAge = passwordPolicy.getMaxPasswordAge(); 1462 if (maxAge > 0L) 1463 { 1464 long expTime = getPasswordChangedTime() + 1000L * maxAge; 1465 if (expTime < passwordExpirationTime) 1466 { 1467 passwordExpirationTime = expTime; 1468 checkWarning = true; 1469 } 1470 } 1471 1472 long maxResetAge = passwordPolicy.getMaxPasswordResetAge(); 1473 if (mustChangePassword() && maxResetAge > 0L) 1474 { 1475 long expTime = getPasswordChangedTime() + 1000L * maxResetAge; 1476 if (expTime < passwordExpirationTime) 1477 { 1478 passwordExpirationTime = expTime; 1479 checkWarning = false; 1480 } 1481 } 1482 1483 long mustChangeTime = passwordPolicy.getRequireChangeByTime(); 1484 if (mustChangeTime > 0) 1485 { 1486 long reqChangeTime = getRequiredChangeTime(); 1487 if (reqChangeTime != mustChangeTime && mustChangeTime < passwordExpirationTime) 1488 { 1489 passwordExpirationTime = mustChangeTime; 1490 checkWarning = true; 1491 } 1492 } 1493 1494 if (passwordExpirationTime == Long.MAX_VALUE) 1495 { 1496 passwordExpirationTime = -1; 1497 shouldWarn = ConditionResult.FALSE; 1498 isFirstWarning = ConditionResult.FALSE; 1499 isPasswordExpired = ConditionResult.FALSE; 1500 mayUseGraceLogin = ConditionResult.TRUE; 1501 } 1502 else if (checkWarning) 1503 { 1504 mayUseGraceLogin = ConditionResult.TRUE; 1505 1506 long warningInterval = passwordPolicy.getPasswordExpirationWarningInterval(); 1507 if (warningInterval > 0L) 1508 { 1509 long shouldWarnTime = passwordExpirationTime - warningInterval * 1000L; 1510 if (shouldWarnTime > currentTime) 1511 { 1512 // The warning time is in the future, so we know the password isn't expired. 1513 shouldWarn = ConditionResult.FALSE; 1514 isFirstWarning = ConditionResult.FALSE; 1515 isPasswordExpired = ConditionResult.FALSE; 1516 } 1517 else 1518 { 1519 // We're at least in the warning period, but the password may be expired. 1520 long theWarnedTime = getWarnedTime(); 1521 1522 if (passwordExpirationTime > currentTime) 1523 { 1524 // The password is not expired but we should warn the user. 1525 shouldWarn = ConditionResult.TRUE; 1526 isPasswordExpired = ConditionResult.FALSE; 1527 1528 if (theWarnedTime < 0) 1529 { 1530 isFirstWarning = ConditionResult.TRUE; 1531 setWarnedTime(); 1532 1533 if (! passwordPolicy.isExpirePasswordsWithoutWarning()) 1534 { 1535 passwordExpirationTime = currentTime + warningInterval * 1000L; 1536 } 1537 } 1538 else 1539 { 1540 isFirstWarning = ConditionResult.FALSE; 1541 1542 if (! passwordPolicy.isExpirePasswordsWithoutWarning()) 1543 { 1544 passwordExpirationTime = theWarnedTime + warningInterval * 1000L; 1545 } 1546 } 1547 } 1548 else 1549 { 1550 // The expiration time has passed, but we may not actually be expired if the user has not 1551 // yet seen a warning. 1552 if (passwordPolicy.isExpirePasswordsWithoutWarning()) 1553 { 1554 shouldWarn = ConditionResult.FALSE; 1555 isFirstWarning = ConditionResult.FALSE; 1556 isPasswordExpired = ConditionResult.TRUE; 1557 } 1558 else if (theWarnedTime > 0) 1559 { 1560 passwordExpirationTime = theWarnedTime + warningInterval*1000L; 1561 if (passwordExpirationTime > currentTime) 1562 { 1563 shouldWarn = ConditionResult.TRUE; 1564 isFirstWarning = ConditionResult.FALSE; 1565 isPasswordExpired = ConditionResult.FALSE; 1566 } 1567 else 1568 { 1569 shouldWarn = ConditionResult.FALSE; 1570 isFirstWarning = ConditionResult.FALSE; 1571 isPasswordExpired = ConditionResult.TRUE; 1572 } 1573 } 1574 else 1575 { 1576 shouldWarn = ConditionResult.TRUE; 1577 isFirstWarning = ConditionResult.TRUE; 1578 isPasswordExpired = ConditionResult.FALSE; 1579 passwordExpirationTime = currentTime + warningInterval*1000L; 1580 } 1581 } 1582 } 1583 } 1584 else 1585 { 1586 // There will never be a warning, and the user's password may be expired. 1587 shouldWarn = ConditionResult.FALSE; 1588 isFirstWarning = ConditionResult.FALSE; 1589 1590 if (currentTime > passwordExpirationTime) 1591 { 1592 isPasswordExpired = ConditionResult.TRUE; 1593 } 1594 else 1595 { 1596 isPasswordExpired = ConditionResult.FALSE; 1597 } 1598 } 1599 } 1600 else 1601 { 1602 mayUseGraceLogin = ConditionResult.FALSE; 1603 shouldWarn = ConditionResult.FALSE; 1604 isFirstWarning = ConditionResult.FALSE; 1605 1606 if (passwordExpirationTime < currentTime) 1607 { 1608 isPasswordExpired = ConditionResult.TRUE; 1609 } 1610 else 1611 { 1612 isPasswordExpired = ConditionResult.FALSE; 1613 } 1614 } 1615 } 1616 1617 if (logger.isTraceEnabled()) 1618 { 1619 logger.trace("Returning password expiration time of %d for user %s.", passwordExpirationTime, userDNString); 1620 } 1621 1622 return passwordExpirationTime; 1623 } 1624 1625 1626 1627 /** 1628 * Indicates whether the user's password is currently expired. 1629 * 1630 * @return <CODE>true</CODE> if the user's password is currently expired, or <CODE>false</CODE> if not. 1631 */ 1632 public boolean isPasswordExpired() 1633 { 1634 refreshIfUndefined(isPasswordExpired); 1635 return isPasswordExpired == ConditionResult.TRUE; 1636 } 1637 1638 private void refreshIfUndefined(ConditionResult cond) 1639 { 1640 if (cond == null || cond == ConditionResult.UNDEFINED) 1641 { 1642 getPasswordExpirationTime(); 1643 } 1644 } 1645 1646 /** 1647 * Indicates whether the user's last password change was within the minimum password age. 1648 * 1649 * @return <CODE>true</CODE> if the password minimum age is nonzero, the account is not in force-change mode, 1650 * and the last password change was within the minimum age, or <CODE>false</CODE> otherwise. 1651 */ 1652 public boolean isWithinMinimumAge() 1653 { 1654 // This feature is responsible for neither a state field nor entry state attribute. 1655 long minAge = passwordPolicy.getMinPasswordAge(); 1656 if (minAge <= 0L) 1657 { 1658 // There is no minimum age, so the user isn't in it. 1659 if (logger.isTraceEnabled()) 1660 { 1661 logger.trace("Returning false because there is no minimum age."); 1662 } 1663 1664 return false; 1665 } 1666 else if (getPasswordChangedTime() + minAge * 1000L < currentTime) 1667 { 1668 // It's been long enough since the user changed their password. 1669 if (logger.isTraceEnabled()) 1670 { 1671 logger.trace("Returning false because the minimum age has expired."); 1672 } 1673 1674 return false; 1675 } 1676 else if (mustChangePassword()) 1677 { 1678 // The user is in a must-change mode, so the minimum age doesn't apply. 1679 if (logger.isTraceEnabled()) 1680 { 1681 logger.trace("Returning false because the account is in a must-change state."); 1682 } 1683 1684 return false; 1685 } 1686 else 1687 { 1688 // The user is within the minimum age. 1689 if (logger.isTraceEnabled()) 1690 { 1691 logger.trace("Returning true."); 1692 } 1693 1694 return true; 1695 } 1696 } 1697 1698 1699 1700 /** 1701 * Indicates whether the user may use a grace login if the password is expired and there is at least one 1702 * grace login remaining. Note that this does not check to see if the user's password is expired, does not 1703 * verify that there are any remaining grace logins, and does not update the set of grace login times. 1704 * 1705 * @return <CODE>true</CODE> if the user may use a grace login if the password is expired and there is 1706 * at least one grace login remaining, or <CODE>false</CODE> if the user may not use a grace 1707 * login for some reason. 1708 */ 1709 public boolean mayUseGraceLogin() 1710 { 1711 refreshIfUndefined(mayUseGraceLogin); 1712 return mayUseGraceLogin == ConditionResult.TRUE; 1713 } 1714 1715 1716 1717 /** 1718 * Indicates whether the user should receive a warning notification that the password is about to expire. 1719 * 1720 * @return <CODE>true</CODE> if the user should receive a warning notification that the password is about to expire, 1721 * or <CODE>false</CODE> if not. 1722 */ 1723 public boolean shouldWarn() 1724 { 1725 refreshIfUndefined(shouldWarn); 1726 return shouldWarn == ConditionResult.TRUE; 1727 } 1728 1729 1730 1731 /** 1732 * Indicates whether the warning that the user should receive would be the first warning for the user. 1733 * 1734 * @return <CODE>true</CODE> if the warning that should be sent to the user would be the first warning, 1735 * or <CODE>false</CODE> if not. 1736 */ 1737 public boolean isFirstWarning() 1738 { 1739 refreshIfUndefined(isFirstWarning); 1740 return isFirstWarning == ConditionResult.TRUE; 1741 } 1742 1743 1744 1745 /** 1746 * Retrieves the length of time in seconds until the user's password expires. 1747 * 1748 * @return The length of time in seconds until the user's password expires, 1749 * 0 if the password is currently expired, or -1 if the password should not expire. 1750 */ 1751 public int getSecondsUntilExpiration() 1752 { 1753 long expirationTime = getPasswordExpirationTime(); 1754 if (expirationTime < 0) 1755 { 1756 return -1; 1757 } 1758 else if (expirationTime < currentTime) 1759 { 1760 return 0; 1761 } 1762 else 1763 { 1764 return (int) ((expirationTime - currentTime) / 1000); 1765 } 1766 } 1767 1768 1769 1770 /** 1771 * Retrieves the timestamp for the last required change time that the user complied with. 1772 * 1773 * @return The timestamp for the last required change time that the user complied with, 1774 * or -1 if the user's password has not been changed in compliance with this configuration. 1775 */ 1776 public long getRequiredChangeTime() 1777 { 1778 if (requiredChangeTime != Long.MIN_VALUE) 1779 { 1780 if (logger.isTraceEnabled()) 1781 { 1782 logger.trace("Returning stored required change time of %d for user %s", requiredChangeTime, userDNString); 1783 } 1784 1785 return requiredChangeTime; 1786 } 1787 1788 try 1789 { 1790 requiredChangeTime = getGeneralizedTime0(userEntry, OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME); 1791 } 1792 catch (Exception e) 1793 { 1794 logger.traceException(e, "Returning %d for user %s because an error occurred", requiredChangeTime, userDNString); 1795 1796 requiredChangeTime = -1; 1797 return requiredChangeTime; 1798 } 1799 1800 logger.trace("Returning required change time of %d for user %s", requiredChangeTime, userDNString); 1801 1802 return requiredChangeTime; 1803 } 1804 1805 1806 1807 /** 1808 * Updates the user entry with a timestamp indicating that the password has been changed in accordance 1809 * with the require change time. 1810 */ 1811 public void setRequiredChangeTime() 1812 { 1813 long requiredChangeByTimePolicy = passwordPolicy.getRequireChangeByTime(); 1814 if (requiredChangeByTimePolicy > 0) 1815 { 1816 setRequiredChangeTime(requiredChangeByTimePolicy); 1817 } 1818 } 1819 1820 1821 1822 /** 1823 * Updates the user entry with a timestamp indicating that the password has been changed in accordance 1824 * with the require change time. 1825 * 1826 * @param requiredChangeTime The timestamp to use for the required change time value. 1827 */ 1828 public void setRequiredChangeTime(long requiredChangeTime) 1829 { 1830 if (logger.isTraceEnabled()) 1831 { 1832 logger.trace("Updating required change time for user %s", userDNString); 1833 } 1834 1835 if (getRequiredChangeTime() != requiredChangeTime) 1836 { 1837 this.requiredChangeTime = requiredChangeTime; 1838 1839 String timeValue = GeneralizedTimeSyntax.format(requiredChangeTime); 1840 Attribute a = Attributes.create(OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME, timeValue); 1841 modifications.add(new Modification(ModificationType.REPLACE, a, true)); 1842 } 1843 } 1844 1845 1846 1847 /** 1848 * Updates the user entry to remove any timestamp indicating that the password has been changed in accordance 1849 * with the required change time. 1850 */ 1851 public void clearRequiredChangeTime() 1852 { 1853 if (logger.isTraceEnabled()) 1854 { 1855 logger.trace("Clearing required change time for user %s", userDNString); 1856 } 1857 1858 this.requiredChangeTime = Long.MIN_VALUE; 1859 1860 String attrName = OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME; 1861 modifications.add(new Modification(ModificationType.REPLACE, Attributes.empty(attrName), true)); 1862 } 1863 1864 1865 /** 1866 * Retrieves the time that the user was first warned about an upcoming expiration. 1867 * 1868 * @return The time that the user was first warned about an upcoming expiration, or -1 if the user has 1869 * not been warned. 1870 */ 1871 public long getWarnedTime() 1872 { 1873 if (warnedTime == Long.MIN_VALUE) 1874 { 1875 try 1876 { 1877 warnedTime = getGeneralizedTime0(userEntry, OP_ATTR_PWPOLICY_WARNED_TIME); 1878 } 1879 catch (Exception e) 1880 { 1881 logger.traceException(e, "Unable to decode the warned time for user %s", userDNString); 1882 warnedTime = -1; 1883 } 1884 } 1885 1886 logger.trace("Returning a warned time of %d for user %s", warnedTime, userDNString); 1887 return warnedTime; 1888 } 1889 1890 1891 1892 /** 1893 * Updates the user entry to set the warned time to the current time. 1894 */ 1895 public void setWarnedTime() 1896 { 1897 setWarnedTime(currentTime); 1898 } 1899 1900 1901 1902 /** 1903 * Updates the user entry to set the warned time to the specified time. This method should generally 1904 * only be used for testing purposes, since the variant that uses the current time is preferred almost 1905 * everywhere else. 1906 * 1907 * @param warnedTime The value to use for the warned time. 1908 */ 1909 public void setWarnedTime(long warnedTime) 1910 { 1911 long warnTime = getWarnedTime(); 1912 if (warnTime == warnedTime) 1913 { 1914 if (logger.isTraceEnabled()) 1915 { 1916 logger.trace("Not updating warned time for user %s because the warned time is the same as the specified time.", 1917 userDNString); 1918 } 1919 1920 return; 1921 } 1922 1923 this.warnedTime = warnedTime; 1924 1925 AttributeType type = DirectoryServer.getAttributeTypeOrDefault(OP_ATTR_PWPOLICY_WARNED_TIME); 1926 Attribute a = Attributes.create(type, GeneralizedTimeSyntax.createGeneralizedTimeValue(currentTime)); 1927 1928 modifications.add(new Modification(ModificationType.REPLACE, a, true)); 1929 1930 if (logger.isTraceEnabled()) 1931 { 1932 logger.trace("Updated the warned time for user %s", userDNString); 1933 } 1934 } 1935 1936 1937 1938 /** 1939 * Updates the user entry to clear the warned time. 1940 */ 1941 public void clearWarnedTime() 1942 { 1943 if (logger.isTraceEnabled()) 1944 { 1945 logger.trace("Clearing warned time for user %s", userDNString); 1946 } 1947 1948 if (getWarnedTime() < 0) 1949 { 1950 return; 1951 } 1952 warnedTime = -1; 1953 1954 String attrName = OP_ATTR_PWPOLICY_WARNED_TIME; 1955 modifications.add(new Modification(ModificationType.REPLACE, Attributes.empty(attrName), true)); 1956 1957 if (logger.isTraceEnabled()) 1958 { 1959 logger.trace("Cleared the warned time for user %s", userDNString); 1960 } 1961 } 1962 1963 1964 1965 /** 1966 * Retrieves the times that the user has authenticated to the server using a grace login. 1967 * 1968 * @return The times that the user has authenticated to the server using a grace login. 1969 */ 1970 public List<Long> getGraceLoginTimes() 1971 { 1972 if (graceLoginTimes == null) 1973 { 1974 AttributeType type = DirectoryServer.getAttributeTypeOrDefault( 1975 OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME_LC, OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME); 1976 try 1977 { 1978 graceLoginTimes = getGeneralizedTimes(type); 1979 } 1980 catch (Exception e) 1981 { 1982 logger.traceException(e, "Error while processing grace login times for user %s", userDNString); 1983 1984 graceLoginTimes = new ArrayList<>(); 1985 1986 modifications.add(new Modification(ModificationType.REPLACE, Attributes.empty(type), true)); 1987 } 1988 } 1989 1990 logger.trace("Returning grace login times for user %s", userDNString); 1991 return graceLoginTimes; 1992 } 1993 1994 1995 1996 /** 1997 * Retrieves the number of grace logins that the user has left. 1998 * 1999 * @return The number of grace logins that the user has left, or -1 if grace logins are not allowed. 2000 */ 2001 public int getGraceLoginsRemaining() 2002 { 2003 int maxGraceLogins = passwordPolicy.getGraceLoginCount(); 2004 if (maxGraceLogins <= 0) 2005 { 2006 return -1; 2007 } 2008 2009 List<Long> theGraceLoginTimes = getGraceLoginTimes(); 2010 return maxGraceLogins - theGraceLoginTimes.size(); 2011 } 2012 2013 2014 2015 /** 2016 * Updates the set of grace login times for the user to include the current time. 2017 */ 2018 public void updateGraceLoginTimes() 2019 { 2020 if (logger.isTraceEnabled()) 2021 { 2022 logger.trace("Updating grace login times for user %s", userDNString); 2023 } 2024 2025 List<Long> graceTimes = getGraceLoginTimes(); 2026 long highestGraceTime = computeHighestTime(graceTimes); 2027 graceTimes.add(highestGraceTime); // graceTimes == this.graceLoginTimes 2028 2029 AttributeType type = DirectoryServer.getAttributeTypeOrDefault( 2030 OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME_LC, OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME); 2031 Attribute addAttr = Attributes.create(type, GeneralizedTimeSyntax.format(highestGraceTime)); 2032 modifications.add(new Modification(ModificationType.ADD, addAttr, true)); 2033 } 2034 2035 private long computeHighestTime(List<Long> graceTimes) 2036 { 2037 long highestTime = -1; 2038 for (long l : graceTimes) 2039 { 2040 highestTime = Math.max(l, highestTime); 2041 } 2042 2043 if (highestTime >= currentTime) 2044 { 2045 highestTime++; 2046 } 2047 else 2048 { 2049 highestTime = currentTime; 2050 } 2051 return highestTime; 2052 } 2053 2054 2055 2056 /** 2057 * Specifies the set of grace login use times for the associated user. If the provided list is empty 2058 * or {@code null}, then the set will be cleared. 2059 * 2060 * @param graceLoginTimes The grace login use times for the associated user. 2061 */ 2062 public void setGraceLoginTimes(List<Long> graceLoginTimes) 2063 { 2064 if (graceLoginTimes == null || graceLoginTimes.isEmpty()) 2065 { 2066 clearGraceLoginTimes(); 2067 return; 2068 } 2069 2070 if (logger.isTraceEnabled()) 2071 { 2072 logger.trace("Updating grace login times for user %s", userDNString); 2073 } 2074 2075 this.graceLoginTimes = graceLoginTimes; 2076 2077 AttributeBuilder builder = new AttributeBuilder(OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME_LC); 2078 for (long l : graceLoginTimes) 2079 { 2080 builder.add(GeneralizedTimeSyntax.format(l)); 2081 } 2082 Attribute a = builder.toAttribute(); 2083 2084 modifications.add(new Modification(ModificationType.REPLACE, a, true)); 2085 } 2086 2087 2088 2089 /** 2090 * Updates the user entry to remove any record of previous grace logins. 2091 */ 2092 public void clearGraceLoginTimes() 2093 { 2094 if (logger.isTraceEnabled()) 2095 { 2096 logger.trace("Clearing grace login times for user %s", userDNString); 2097 } 2098 2099 List<Long> graceTimes = getGraceLoginTimes(); 2100 if (graceTimes.isEmpty()) 2101 { 2102 return; 2103 } 2104 graceTimes.clear(); // graceTimes == this.graceLoginTimes 2105 2106 AttributeType type = DirectoryServer.getAttributeTypeOrDefault( 2107 OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME_LC, OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME); 2108 modifications.add(new Modification(ModificationType.REPLACE, Attributes.empty(type), true)); 2109 } 2110 2111 2112 /** 2113 * Retrieves a list of the clear-text passwords for the user. If the user does not have any passwords 2114 * in the clear, then the list will be empty. 2115 * 2116 * @return A list of the clear-text passwords for the user. 2117 */ 2118 public List<ByteString> getClearPasswords() 2119 { 2120 LinkedList<ByteString> clearPasswords = new LinkedList<>(); 2121 2122 List<Attribute> attrList = userEntry.getAttribute(passwordPolicy.getPasswordAttribute()); 2123 2124 if (attrList == null) 2125 { 2126 return clearPasswords; 2127 } 2128 2129 for (Attribute a : attrList) 2130 { 2131 for (ByteString v : a) 2132 { 2133 try 2134 { 2135 String[] pwComponents = getPwComponents(v); 2136 2137 String schemeName = pwComponents[0]; 2138 PasswordStorageScheme<?> scheme = getPasswordStorageScheme(schemeName); 2139 if (scheme == null) 2140 { 2141 if (logger.isTraceEnabled()) 2142 { 2143 logger.trace("User entry %s contains a password with scheme %s that is not defined in the server.", 2144 userDNString, schemeName); 2145 } 2146 2147 continue; 2148 } 2149 2150 if (scheme.isReversible()) 2151 { 2152 clearPasswords.add(getPlaintextValue(scheme, pwComponents)); 2153 } 2154 } 2155 catch (Exception e) 2156 { 2157 logger.traceException(e); 2158 2159 if (logger.isTraceEnabled()) 2160 { 2161 logger.trace("Cannot get clear password value for user %s: %s", userDNString, e); 2162 } 2163 } 2164 } 2165 } 2166 2167 return clearPasswords; 2168 } 2169 2170 private ByteString getPlaintextValue(PasswordStorageScheme<?> scheme, String[] pwComponents) 2171 throws DirectoryException 2172 { 2173 return passwordPolicy.isAuthPasswordSyntax() 2174 ? scheme.getAuthPasswordPlaintextValue(pwComponents[1], pwComponents[2]) 2175 : scheme.getPlaintextValue(ByteString.valueOfUtf8(pwComponents[1])); 2176 } 2177 2178 @Override 2179 public boolean passwordMatches(ByteString password) 2180 { 2181 List<Attribute> attrList = userEntry.getAttribute(passwordPolicy.getPasswordAttribute()); 2182 if (attrList == null || attrList.isEmpty()) 2183 { 2184 if (logger.isTraceEnabled()) 2185 { 2186 logger.trace("Returning false because user %s does not have any values for password attribute %s", 2187 userDNString, passwordPolicy.getPasswordAttribute().getNameOrOID()); 2188 } 2189 2190 return false; 2191 } 2192 2193 for (Attribute a : attrList) 2194 { 2195 for (ByteString v : a) 2196 { 2197 try 2198 { 2199 String[] pwComponents = getPwComponents(v); 2200 String schemeName = pwComponents[0]; 2201 PasswordStorageScheme<?> scheme = getPasswordStorageScheme(schemeName); 2202 if (scheme == null) 2203 { 2204 if (logger.isTraceEnabled()) 2205 { 2206 logger.trace("User entry %s contains a password with scheme %s that is not defined in the server.", 2207 userDNString, schemeName); 2208 } 2209 2210 continue; 2211 } 2212 2213 if (passwordMatches(password, pwComponents, scheme)) 2214 { 2215 if (logger.isTraceEnabled()) 2216 { 2217 logger.trace("Returning true for user %s because the provided password matches a value " + 2218 "encoded with scheme %s", userDNString, schemeName); 2219 } 2220 2221 return true; 2222 } 2223 } 2224 catch (Exception e) 2225 { 2226 logger.traceException(e, "An error occurred while attempting to process a password value for user %s", 2227 userDNString); 2228 } 2229 } 2230 } 2231 2232 // If we've gotten here, then we couldn't find a match. 2233 logger.trace("Returning false because the provided password does not match any of the stored password " + 2234 "values for user %s", userDNString); 2235 2236 return false; 2237 } 2238 2239 2240 /** 2241 * Get the broken-down components of the given password value. 2242 * 2243 * @param usesAuthPasswordSyntax true if the value is an authPassword. 2244 * @param v The encoded password value to break down. 2245 * 2246 * @return An array of components. 2247 */ 2248 private String[] getPwComponents(ByteString v) throws DirectoryException 2249 { 2250 return passwordPolicy.isAuthPasswordSyntax() 2251 ? AuthPasswordSyntax.decodeAuthPassword(v.toString()) 2252 : UserPasswordSyntax.decodeUserPassword(v.toString()); 2253 } 2254 2255 /** 2256 * Indicates whether the provided password value is pre-encoded. 2257 * 2258 * @param passwordValue The value for which to make the determination. 2259 * 2260 * @return <CODE>true</CODE> if the provided password value is pre-encoded, or <CODE>false</CODE> if it is not. 2261 */ 2262 public boolean passwordIsPreEncoded(ByteString passwordValue) 2263 { 2264 return passwordPolicy.isAuthPasswordSyntax() 2265 ? AuthPasswordSyntax.isEncoded(passwordValue) 2266 : UserPasswordSyntax.isEncoded(passwordValue); 2267 } 2268 2269 2270 2271 /** 2272 * Encodes the provided password using the default storage schemes (using the appropriate syntax for the 2273 * password attribute). 2274 * 2275 * @param password The password to be encoded. 2276 * 2277 * @return The password encoded using the default schemes. 2278 * 2279 * @throws DirectoryException If a problem occurs while attempting to encode the password. 2280 */ 2281 public List<ByteString> encodePassword(ByteString password) 2282 throws DirectoryException 2283 { 2284 List<PasswordStorageScheme<?>> schemes = passwordPolicy.getDefaultPasswordStorageSchemes(); 2285 List<ByteString> encodedPasswords = new ArrayList<>(schemes.size()); 2286 2287 if (passwordPolicy.isAuthPasswordSyntax()) 2288 { 2289 for (PasswordStorageScheme<?> s : schemes) 2290 { 2291 encodedPasswords.add(s.encodeAuthPassword(password)); 2292 } 2293 } 2294 else 2295 { 2296 for (PasswordStorageScheme<?> s : schemes) 2297 { 2298 encodedPasswords.add(s.encodePasswordWithScheme(password)); 2299 } 2300 } 2301 2302 return encodedPasswords; 2303 } 2304 2305 2306 2307 /** 2308 * Indicates whether the provided password appears to be acceptable according to the password validators. 2309 * 2310 * @param operation The operation that provided the password. 2311 * @param userEntry The user entry in which the password is used. 2312 * @param newPassword The password to be validated. 2313 * @param currentPasswords The set of clear-text current passwords for the user (this may be a subset 2314 * if not all of them are available in the clear, or empty if none of them 2315 * are available in the clear). 2316 * @param invalidReason A buffer that may be used to hold the invalid reason if the password is rejected. 2317 * 2318 * @return <CODE>true</CODE> if the password is acceptable for use, or <CODE>false</CODE> if it is not. 2319 */ 2320 public boolean passwordIsAcceptable(Operation operation, Entry userEntry, ByteString newPassword, 2321 Set<ByteString> currentPasswords, LocalizableMessageBuilder invalidReason) 2322 { 2323 for (PasswordValidator<?> validator : passwordPolicy.getPasswordValidators()) 2324 { 2325 if (!validator.passwordIsAcceptable(newPassword, currentPasswords, operation, userEntry, invalidReason)) 2326 { 2327 if (logger.isTraceEnabled()) 2328 { 2329 logger.trace("The password provided for user %s failed validation: %s", userDNString, invalidReason); 2330 } 2331 return false; 2332 } 2333 } 2334 return true; 2335 } 2336 2337 2338 2339 /** 2340 * Performs any processing that may be necessary to remove deprecated storage schemes from the user's entry 2341 * that match the provided password and re-encodes them using the default schemes. 2342 * 2343 * @param password The clear-text password provided by the user. 2344 */ 2345 public void handleDeprecatedStorageSchemes(ByteString password) 2346 { 2347 if (passwordPolicy.getDeprecatedPasswordStorageSchemes().isEmpty()) 2348 { 2349 if (logger.isTraceEnabled()) 2350 { 2351 logger.trace("Doing nothing for user %s because no deprecated storage schemes have been defined.", 2352 userDNString); 2353 } 2354 2355 return; 2356 } 2357 2358 2359 AttributeType type = passwordPolicy.getPasswordAttribute(); 2360 List<Attribute> attrList = userEntry.getAttribute(type); 2361 if (attrList == null || attrList.isEmpty()) 2362 { 2363 if (logger.isTraceEnabled()) 2364 { 2365 logger.trace("Doing nothing for entry %s because no password values were found.", userDNString); 2366 } 2367 2368 return; 2369 } 2370 2371 2372 HashSet<String> existingDefaultSchemes = new HashSet<>(); 2373 LinkedHashSet<ByteString> removedValues = new LinkedHashSet<>(); 2374 LinkedHashSet<ByteString> updatedValues = new LinkedHashSet<>(); 2375 2376 for (Attribute a : attrList) 2377 { 2378 for (ByteString v : a) { 2379 try { 2380 String[] pwComponents = getPwComponents(v); 2381 2382 String schemeName = pwComponents[0]; 2383 PasswordStorageScheme<?> scheme = getPasswordStorageScheme(schemeName); 2384 if (scheme == null) { 2385 if (logger.isTraceEnabled()) { 2386 logger.trace("Skipping password value for user %s because the associated storage scheme %s " + 2387 "is not configured for use.", userDNString, schemeName); 2388 } 2389 continue; 2390 } 2391 2392 if (passwordMatches(password, pwComponents, scheme)) 2393 { 2394 if (passwordPolicy.isDefaultPasswordStorageScheme(schemeName)) { 2395 existingDefaultSchemes.add(schemeName); 2396 updatedValues.add(v); 2397 } else if (passwordPolicy.isDeprecatedPasswordStorageScheme(schemeName)) { 2398 if (logger.isTraceEnabled()) { 2399 logger.trace("Marking password with scheme %s for removal from user entry %s.", 2400 schemeName, userDNString); 2401 } 2402 removedValues.add(v); 2403 } else { 2404 updatedValues.add(v); 2405 } 2406 } 2407 } catch (Exception e) { 2408 logger.traceException(e, "Skipping password value for user %s because an error occurred while attempting " + 2409 "to decode it based on the user password syntax", userDNString); 2410 } 2411 } 2412 } 2413 2414 if (removedValues.isEmpty()) 2415 { 2416 logger.trace("User entry %s does not have any password values encoded using deprecated schemes.", userDNString); 2417 return; 2418 } 2419 2420 LinkedHashSet<ByteString> addedValues = new LinkedHashSet<>(); 2421 for (PasswordStorageScheme<?> s : passwordPolicy.getDefaultPasswordStorageSchemes()) 2422 { 2423 if (! existingDefaultSchemes.contains(toLowerCase(s.getStorageSchemeName()))) 2424 { 2425 try 2426 { 2427 ByteString encodedPassword = encodePassword(password, s); 2428 addedValues.add(encodedPassword); 2429 updatedValues.add(encodedPassword); 2430 } 2431 catch (Exception e) 2432 { 2433 logger.traceException(e); 2434 2435 if (logger.isTraceEnabled()) 2436 { 2437 logger.traceException(e, "Unable to encode password for user %s using default scheme %s", 2438 userDNString, s.getStorageSchemeName()); 2439 } 2440 } 2441 } 2442 } 2443 2444 if (updatedValues.isEmpty()) 2445 { 2446 logger.trace( 2447 "Not updating user entry %s because removing deprecated schemes would leave the user without a password.", 2448 userDNString); 2449 return; 2450 } 2451 2452 Attribute a = newAttribute(type, removedValues); 2453 modifications.add(new Modification(ModificationType.DELETE, a, true)); 2454 2455 if (! addedValues.isEmpty()) 2456 { 2457 Attribute a2 = newAttribute(type, addedValues); 2458 modifications.add(new Modification(ModificationType.ADD, a2, true)); 2459 } 2460 2461 if (logger.isTraceEnabled()) 2462 { 2463 logger.trace("Updating user entry %s to replace password values encoded with deprecated schemes " + 2464 "with values encoded with the default schemes.", userDNString); 2465 } 2466 } 2467 2468 private PasswordStorageScheme<?> getPasswordStorageScheme(String schemeName) 2469 { 2470 return passwordPolicy.isAuthPasswordSyntax() 2471 ? DirectoryServer.getAuthPasswordStorageScheme(schemeName) 2472 : DirectoryServer.getPasswordStorageScheme(schemeName); 2473 } 2474 2475 private boolean passwordMatches(ByteString password, String[] pwComponents, PasswordStorageScheme<?> scheme) 2476 { 2477 return passwordPolicy.isAuthPasswordSyntax() 2478 ? scheme.authPasswordMatches(password, pwComponents[1], pwComponents[2]) 2479 : scheme.passwordMatches(password, ByteString.valueOfUtf8(pwComponents[1])); 2480 } 2481 2482 private ByteString encodePassword(ByteString password, PasswordStorageScheme<?> s) throws DirectoryException 2483 { 2484 return passwordPolicy.isAuthPasswordSyntax() 2485 ? s.encodeAuthPassword(password) 2486 : s.encodePasswordWithScheme(password); 2487 } 2488 2489 /** 2490 * Indicates whether password history information should be maintained for this user. 2491 * 2492 * @return {@code true} if password history information should be maintained for this user, or {@code false} if not. 2493 */ 2494 public boolean maintainHistory() 2495 { 2496 return passwordPolicy.getPasswordHistoryCount() > 0 2497 || passwordPolicy.getPasswordHistoryDuration() > 0; 2498 } 2499 2500 2501 2502 /** 2503 * Indicates whether the provided password is equal to any of the current passwords, 2504 * or any of the passwords in the history. 2505 * 2506 * @param password The password for which to make the determination. 2507 * 2508 * @return {@code true} if the provided password is equal to any of the current passwords or any of the passwords 2509 * in the history, or {@code false} if not. 2510 */ 2511 public boolean isPasswordInHistory(ByteString password) 2512 { 2513 if (! maintainHistory()) 2514 { 2515 if (logger.isTraceEnabled()) 2516 { 2517 logger.trace("Returning false because password history checking is disabled."); 2518 } 2519 return false; 2520 } 2521 2522 // Check to see if the provided password is equal to any of the current passwords. 2523 // If so, then we'll consider it to be in the history. 2524 if (passwordMatches(password)) 2525 { 2526 if (logger.isTraceEnabled()) 2527 { 2528 logger.trace("Returning true because the provided password is currently in use."); 2529 } 2530 return true; 2531 } 2532 2533 // Get the attribute containing the history and check to see if any of the values is equal to the provided password. 2534 // However, first prune the list by size and duration if necessary. 2535 TreeMap<Long, ByteString> historyMap = getSortedHistoryValues(null); 2536 2537 int historyCount = passwordPolicy.getPasswordHistoryCount(); 2538 if (historyCount > 0 && historyMap.size() > historyCount) 2539 { 2540 int numToDelete = historyMap.size() - historyCount; 2541 Iterator<Long> iterator = historyMap.keySet().iterator(); 2542 while (iterator.hasNext() && numToDelete > 0) 2543 { 2544 iterator.next(); 2545 iterator.remove(); 2546 numToDelete--; 2547 } 2548 } 2549 2550 long historyDuration = passwordPolicy.getPasswordHistoryDuration(); 2551 if (historyDuration > 0L) 2552 { 2553 long retainDate = currentTime - 1000 * historyDuration; 2554 Iterator<Long> iterator = historyMap.keySet().iterator(); 2555 while (iterator.hasNext()) 2556 { 2557 long historyDate = iterator.next(); 2558 if (historyDate >= retainDate) 2559 { 2560 break; 2561 } 2562 iterator.remove(); 2563 } 2564 } 2565 2566 for (ByteString v : historyMap.values()) 2567 { 2568 if (historyValueMatches(password, v)) 2569 { 2570 if (logger.isTraceEnabled()) 2571 { 2572 logger.trace("Returning true because the password is in the history."); 2573 } 2574 2575 return true; 2576 } 2577 } 2578 2579 // If we've gotten here, then the password isn't in the history. 2580 if (logger.isTraceEnabled()) 2581 { 2582 logger.trace("Returning false because the password isn't in the history."); 2583 } 2584 return false; 2585 } 2586 2587 2588 2589 /** 2590 * Gets a sorted list of the password history values contained in the user's entry. 2591 * The values will be sorted by timestamp. 2592 * 2593 * @param removeAttrs A list into which any values will be placed that could not be properly decoded. 2594 * It may be {@code null} if this is not needed. 2595 */ 2596 private TreeMap<Long,ByteString> getSortedHistoryValues(List<Attribute> removeAttrs) 2597 { 2598 TreeMap<Long, ByteString> historyMap = new TreeMap<>(); 2599 AttributeType historyType = DirectoryServer.getAttributeTypeOrDefault(OP_ATTR_PWPOLICY_HISTORY_LC); 2600 List<Attribute> attrList = userEntry.getAttribute(historyType); 2601 if (attrList != null) 2602 { 2603 for (Attribute a : attrList) 2604 { 2605 for (ByteString v : a) 2606 { 2607 String histStr = v.toString(); 2608 int hashPos = histStr.indexOf('#'); 2609 if (hashPos <= 0) 2610 { 2611 if (logger.isTraceEnabled()) 2612 { 2613 logger.trace("Found value " + histStr + " in the history with no timestamp. Marking it for removal."); 2614 } 2615 2616 if (removeAttrs != null) 2617 { 2618 removeAttrs.add(Attributes.create(a.getAttributeType(), v)); 2619 } 2620 } 2621 else 2622 { 2623 try 2624 { 2625 ByteString timeValue = ByteString.valueOfUtf8(histStr.substring(0, hashPos)); 2626 long timestamp = GeneralizedTimeSyntax.decodeGeneralizedTimeValue(timeValue); 2627 historyMap.put(timestamp, v); 2628 } 2629 catch (Exception e) 2630 { 2631 if (logger.isTraceEnabled()) 2632 { 2633 logger.traceException(e); 2634 logger.trace("Could not decode the timestamp in history value " + histStr + " -- " + e + 2635 ". Marking it for removal."); 2636 } 2637 2638 if (removeAttrs != null) 2639 { 2640 removeAttrs.add(Attributes.create(a.getAttributeType(), v)); 2641 } 2642 } 2643 } 2644 } 2645 } 2646 } 2647 2648 return historyMap; 2649 } 2650 2651 2652 2653 /** 2654 * Indicates whether the provided password matches the given history value. 2655 * 2656 * @param password The clear-text password for which to make the determination. 2657 * @param historyValue The encoded history value to compare against the clear-text password. 2658 * 2659 * @return {@code true} if the provided password matches the history value, or {@code false} if not. 2660 */ 2661 private boolean historyValueMatches(ByteString password, ByteString historyValue) { 2662 // According to draft-behera-ldap-password-policy, password history values should be in the format 2663 // time#syntaxoid#encodedvalue. In this method, we only care about the syntax OID and encoded password. 2664 try 2665 { 2666 String histStr = historyValue.toString(); 2667 int hashPos1 = histStr.indexOf('#'); 2668 if (hashPos1 <= 0) 2669 { 2670 if (logger.isTraceEnabled()) 2671 { 2672 logger.trace("Returning false because the password history value didn't include any hash characters."); 2673 } 2674 2675 return false; 2676 } 2677 2678 int hashPos2 = histStr.indexOf('#', hashPos1+1); 2679 if (hashPos2 < 0) 2680 { 2681 if (logger.isTraceEnabled()) 2682 { 2683 logger.trace("Returning false because the password history value only had one hash character."); 2684 } 2685 2686 return false; 2687 } 2688 2689 String syntaxOID = toLowerCase(histStr.substring(hashPos1+1, hashPos2)); 2690 if (SYNTAX_AUTH_PASSWORD_OID.equals(syntaxOID)) 2691 { 2692 return logResult("auth", encodedAuthPasswordMatches(password, histStr.substring(hashPos2+1))); 2693 } 2694 else if (SYNTAX_USER_PASSWORD_OID.equals(syntaxOID)) 2695 { 2696 return logResult("user", encodedUserPasswordMatches(password, histStr.substring(hashPos2+1))); 2697 } 2698 else 2699 { 2700 if (logger.isTraceEnabled()) 2701 { 2702 logger.trace("Returning false because the syntax OID " + syntaxOID + 2703 " didn't match for either the auth or user password syntax."); 2704 } 2705 2706 return false; 2707 } 2708 } 2709 catch (Exception e) 2710 { 2711 if (logger.isTraceEnabled()) 2712 { 2713 logger.traceException(e); 2714 logger.trace("Returning false because of an exception: " + stackTraceToSingleLineString(e)); 2715 } 2716 2717 return false; 2718 } 2719 } 2720 2721 private boolean encodedAuthPasswordMatches(ByteString password, String encodedAuthPassword) throws DirectoryException 2722 { 2723 String[] authPWComponents = AuthPasswordSyntax.decodeAuthPassword(encodedAuthPassword); 2724 PasswordStorageScheme<?> scheme = DirectoryServer.getAuthPasswordStorageScheme(authPWComponents[0]); 2725 return scheme.authPasswordMatches(password, authPWComponents[1], authPWComponents[2]); 2726 } 2727 2728 private boolean encodedUserPasswordMatches(ByteString password, String encodedUserPassword) throws DirectoryException 2729 { 2730 String[] userPWComponents = UserPasswordSyntax.decodeUserPassword(encodedUserPassword); 2731 PasswordStorageScheme<?> scheme = DirectoryServer.getPasswordStorageScheme(userPWComponents[0]); 2732 return scheme.passwordMatches(password, ByteString.valueOfUtf8(userPWComponents[1])); 2733 } 2734 2735 private boolean logResult(String passwordType, boolean passwordMatches) 2736 { 2737 if (passwordMatches) 2738 { 2739 logger.trace("Returning true because the %s password history value matched.", passwordType); 2740 return true; 2741 } 2742 else 2743 { 2744 logger.trace("Returning false because the %s password history value did not match.", passwordType); 2745 return false; 2746 } 2747 } 2748 2749 /** 2750 * Updates the password history information for this user by adding one of the passwords to it. 2751 * It will choose the first password encoded using a secure storage scheme, and will fall back to 2752 * a password encoded using an insecure storage scheme if necessary. 2753 */ 2754 public void updatePasswordHistory() 2755 { 2756 List<Attribute> attrList = userEntry.getAttribute(passwordPolicy.getPasswordAttribute()); 2757 if (attrList != null) 2758 { 2759 for (Attribute a : attrList) 2760 { 2761 ByteString insecurePassword = null; 2762 for (ByteString v : a) 2763 { 2764 try 2765 { 2766 PasswordStorageScheme<?> scheme = getPasswordStorageScheme(v); 2767 2768 if (scheme.isStorageSchemeSecure()) 2769 { 2770 addPasswordToHistory(v.toString()); 2771 insecurePassword = null; 2772 // no need to check any more values for this attribute 2773 break; 2774 } 2775 else if (insecurePassword == null) 2776 { 2777 insecurePassword = v; 2778 } 2779 } 2780 catch (DirectoryException e) 2781 { 2782 if (logger.isTraceEnabled()) 2783 { 2784 logger.trace("Encoded password " + v + " cannot be decoded and cannot be added to history."); 2785 } 2786 } 2787 } 2788 // If we get here we haven't found a password encoded securely, so we have to use one of the other values. 2789 if (insecurePassword != null) 2790 { 2791 addPasswordToHistory(insecurePassword.toString()); 2792 } 2793 } 2794 } 2795 } 2796 2797 2798 2799 /** 2800 * Adds the provided password to the password history. If appropriate, one or more old passwords may be 2801 * evicted from the list if the total size would exceed the configured count, or if passwords are older 2802 * than the configured duration. 2803 * 2804 * @param encodedPassword The encoded password (in either user password or auth password format) 2805 * to be added to the history. 2806 */ 2807 private void addPasswordToHistory(String encodedPassword) 2808 { 2809 if (! maintainHistory()) 2810 { 2811 if (logger.isTraceEnabled()) 2812 { 2813 logger.trace("Not doing anything because password history maintenance is disabled."); 2814 } 2815 2816 return; 2817 } 2818 2819 2820 // Get a sorted list of the existing values to see if there are any that should be removed. 2821 LinkedList<Attribute> removeAttrs = new LinkedList<>(); 2822 TreeMap<Long, ByteString> historyMap = getSortedHistoryValues(removeAttrs); 2823 2824 2825 // If there is a maximum number of values to retain and we would be over the limit with the new value, 2826 // then get rid of enough values (oldest first) to satisfy the count. 2827 AttributeType historyType = DirectoryServer.getAttributeTypeOrDefault(OP_ATTR_PWPOLICY_HISTORY_LC); 2828 int historyCount = passwordPolicy.getPasswordHistoryCount(); 2829 if (historyCount > 0 && historyMap.size() >= historyCount) 2830 { 2831 int numToDelete = historyMap.size() - historyCount + 1; 2832 LinkedHashSet<ByteString> removeValues = new LinkedHashSet<>(numToDelete); 2833 Iterator<ByteString> iterator = historyMap.values().iterator(); 2834 while (iterator.hasNext() && numToDelete > 0) 2835 { 2836 ByteString v = iterator.next(); 2837 removeValues.add(v); 2838 iterator.remove(); 2839 numToDelete--; 2840 2841 if (logger.isTraceEnabled()) 2842 { 2843 logger.trace("Removing history value %s to preserve the history count.", v); 2844 } 2845 } 2846 2847 if (! removeValues.isEmpty()) 2848 { 2849 removeAttrs.add(newAttribute(historyType, removeValues)); 2850 } 2851 } 2852 2853 2854 // If there is a maximum duration, then get rid of any values that would be over the duration. 2855 long historyDuration = passwordPolicy.getPasswordHistoryDuration(); 2856 if (historyDuration > 0L) 2857 { 2858 long minAgeToKeep = currentTime - 1000L * historyDuration; 2859 Iterator<Long> iterator = historyMap.keySet().iterator(); 2860 LinkedHashSet<ByteString> removeValues = new LinkedHashSet<>(); 2861 while (iterator.hasNext()) 2862 { 2863 long timestamp = iterator.next(); 2864 if (timestamp >= minAgeToKeep) 2865 { 2866 break; 2867 } 2868 2869 ByteString v = historyMap.get(timestamp); 2870 removeValues.add(v); 2871 iterator.remove(); 2872 2873 if (logger.isTraceEnabled()) 2874 { 2875 logger.trace("Removing history value %s to preserve the history duration.", v); 2876 } 2877 } 2878 2879 if (! removeValues.isEmpty()) 2880 { 2881 removeAttrs.add(newAttribute(historyType, removeValues)); 2882 } 2883 } 2884 2885 2886 // At this point, we can add the new value. However, we want to make sure that its timestamp 2887 // (which is the current time) doesn't conflict with any value already in the list. If there is a conflict, 2888 // then simply add one to it until we don't have any more conflicts. 2889 long newTimestamp = currentTime; 2890 while (historyMap.containsKey(newTimestamp)) 2891 { 2892 newTimestamp++; 2893 } 2894 String newHistStr = GeneralizedTimeSyntax.format(newTimestamp) + "#" + 2895 passwordPolicy.getPasswordAttribute().getSyntax().getOID() + "#" + encodedPassword; 2896 Attribute newHistAttr = Attributes.create(historyType, newHistStr); 2897 2898 if (logger.isTraceEnabled()) 2899 { 2900 logger.trace("Going to add history value " + newHistStr); 2901 } 2902 2903 2904 // Apply the changes, either by adding modifications or by directly updating the entry. 2905 for (Attribute a : removeAttrs) 2906 { 2907 modifications.add(new Modification(ModificationType.DELETE, a, true)); 2908 } 2909 2910 modifications.add(new Modification(ModificationType.ADD, newHistAttr, true)); 2911 } 2912 2913 private Attribute newAttribute(AttributeType type, LinkedHashSet<ByteString> values) 2914 { 2915 AttributeBuilder builder = new AttributeBuilder(type); 2916 builder.addAll(values); 2917 return builder.toAttribute(); 2918 } 2919 2920 /** 2921 * Retrieves the password history state values for the user. This is only intended for testing purposes. 2922 * 2923 * @return The password history state values for the user. 2924 */ 2925 public String[] getPasswordHistoryValues() 2926 { 2927 ArrayList<String> historyValues = new ArrayList<>(); 2928 AttributeType historyType = DirectoryServer.getAttributeTypeOrDefault(OP_ATTR_PWPOLICY_HISTORY_LC); 2929 List<Attribute> attrList = userEntry.getAttribute(historyType); 2930 if (attrList != null) 2931 { 2932 for (Attribute a : attrList) 2933 { 2934 for (ByteString v : a) 2935 { 2936 historyValues.add(v.toString()); 2937 } 2938 } 2939 } 2940 2941 return historyValues.toArray(new String[historyValues.size()]); 2942 } 2943 2944 2945 2946 /** 2947 * Clears the password history state information for the user. This is only intended for testing purposes. 2948 */ 2949 public void clearPasswordHistory() 2950 { 2951 if (logger.isTraceEnabled()) 2952 { 2953 logger.trace("Clearing password history for user %s", userDNString); 2954 } 2955 2956 modifications.add(new Modification(ModificationType.REPLACE, Attributes.empty(OP_ATTR_PWPOLICY_HISTORY_LC), true)); 2957 } 2958 2959 2960 /** 2961 * Generates a new password for the user. 2962 * 2963 * @return The new password that has been generated, or <CODE>null</CODE> if no password generator has been defined. 2964 * 2965 * @throws DirectoryException If an error occurs while attempting to generate the new password. 2966 */ 2967 public ByteString generatePassword() 2968 throws DirectoryException 2969 { 2970 PasswordGenerator<?> generator = passwordPolicy.getPasswordGenerator(); 2971 if (generator == null) 2972 { 2973 if (logger.isTraceEnabled()) 2974 { 2975 logger.trace("Unable to generate a new password for user %s because no password generator has been defined" + 2976 "in the associated password policy.", userDNString); 2977 } 2978 2979 return null; 2980 } 2981 2982 return generator.generatePassword(userEntry); 2983 } 2984 2985 2986 2987 /** 2988 * Generates an account status notification for this user. 2989 * 2990 * @param notificationType The type for the account status notification. 2991 * @param userEntry The entry for the user to which this notification applies. 2992 * @param message The human-readable message for the notification. 2993 * @param notificationProperties The set of properties for the notification. 2994 */ 2995 public void generateAccountStatusNotification( 2996 AccountStatusNotificationType notificationType, 2997 Entry userEntry, LocalizableMessage message, 2998 Map<AccountStatusNotificationProperty,List<String>> notificationProperties) 2999 { 3000 generateAccountStatusNotification( 3001 new AccountStatusNotification(notificationType, userEntry, message, notificationProperties)); 3002 } 3003 3004 3005 3006 /** 3007 * Generates an account status notification for this user. 3008 * 3009 * @param notification The account status notification that should be generated. 3010 */ 3011 public void generateAccountStatusNotification(AccountStatusNotification notification) 3012 { 3013 Collection<AccountStatusNotificationHandler<?>> handlers = passwordPolicy.getAccountStatusNotificationHandlers(); 3014 for (AccountStatusNotificationHandler<?> handler : handlers) 3015 { 3016 handler.handleStatusNotification(notification); 3017 } 3018 } 3019 3020 3021 3022 /** 3023 * Retrieves the set of modifications that correspond to changes made in password policy processing 3024 * that may need to be applied to the user entry. 3025 * 3026 * @return The set of modifications that correspond to changes made in password policy processing 3027 * that may need to be applied to the user entry. 3028 */ 3029 public List<Modification> getModifications() 3030 { 3031 return modifications; 3032 } 3033 3034 3035 3036 @Override 3037 public void finalizeStateAfterBind() 3038 throws DirectoryException 3039 { 3040 // If there are no modifications, then there's nothing to do. 3041 if (modifications.isEmpty()) 3042 { 3043 return; 3044 } 3045 3046 // Convert the set of modifications to a set of LDAP modifications. 3047 ArrayList<RawModification> modList = new ArrayList<>(); 3048 for (Modification m : modifications) 3049 { 3050 modList.add(RawModification.create(m.getModificationType(), new LDAPAttribute(m.getAttribute()))); 3051 } 3052 3053 InternalClientConnection conn = getRootConnection(); 3054 ModifyOperation internalModify = conn.processModify(ByteString.valueOfUtf8(userDNString), modList); 3055 3056 ResultCode resultCode = internalModify.getResultCode(); 3057 if (resultCode != ResultCode.SUCCESS) 3058 { 3059 LocalizableMessage message = ERR_PWPSTATE_CANNOT_UPDATE_USER_ENTRY.get( 3060 userDNString, internalModify.getErrorMessage()); 3061 3062 // If this is a root user, or if the password policy says that we should ignore these problems, 3063 // then log a warning message. Otherwise, cause the bind to fail. 3064 if (DirectoryServer.isRootDN(userEntry.getName()) 3065 || passwordPolicy.getStateUpdateFailurePolicy() == PasswordPolicyCfgDefn.StateUpdateFailurePolicy.IGNORE) 3066 { 3067 logger.error(message); 3068 } 3069 else 3070 { 3071 throw new DirectoryException(resultCode, message); 3072 } 3073 } 3074 } 3075}