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