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 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2006-2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.core; 028 029 030 031 import java.text.SimpleDateFormat; 032 import java.util.ArrayList; 033 import java.util.Collection; 034 import java.util.Date; 035 import java.util.HashSet; 036 import java.util.Iterator; 037 import java.util.LinkedHashSet; 038 import java.util.LinkedList; 039 import java.util.List; 040 import java.util.Map; 041 import java.util.Set; 042 import java.util.TreeMap; 043 044 import org.opends.messages.Message; 045 import org.opends.messages.MessageBuilder; 046 import org.opends.server.admin.std.meta.PasswordPolicyCfgDefn; 047 import org.opends.server.admin.std.server.PasswordValidatorCfg; 048 import org.opends.server.api.AccountStatusNotificationHandler; 049 import org.opends.server.api.PasswordGenerator; 050 import org.opends.server.api.PasswordStorageScheme; 051 import org.opends.server.api.PasswordValidator; 052 import org.opends.server.loggers.ErrorLogger; 053 import org.opends.server.loggers.debug.DebugTracer; 054 import org.opends.server.protocols.asn1.ASN1OctetString; 055 import org.opends.server.protocols.internal.InternalClientConnection; 056 import org.opends.server.protocols.ldap.LDAPAttribute; 057 import org.opends.server.schema.AuthPasswordSyntax; 058 import org.opends.server.schema.GeneralizedTimeSyntax; 059 import org.opends.server.schema.UserPasswordSyntax; 060 import org.opends.server.types.AccountStatusNotification; 061 import org.opends.server.types.AccountStatusNotificationProperty; 062 import org.opends.server.types.AccountStatusNotificationType; 063 import org.opends.server.types.Attribute; 064 import org.opends.server.types.AttributeType; 065 import org.opends.server.types.AttributeValue; 066 import org.opends.server.types.ByteString; 067 import org.opends.server.types.ConditionResult; 068 import org.opends.server.types.DebugLogLevel; 069 import org.opends.server.types.DirectoryException; 070 import org.opends.server.types.DN; 071 import org.opends.server.types.Entry; 072 import org.opends.server.types.Modification; 073 import org.opends.server.types.ModificationType; 074 import org.opends.server.types.Operation; 075 import org.opends.server.types.RawModification; 076 import org.opends.server.types.ResultCode; 077 import org.opends.server.util.TimeThread; 078 079 import static org.opends.server.config.ConfigConstants.*; 080 import static org.opends.server.loggers.debug.DebugLogger.*; 081 import static org.opends.messages.CoreMessages.*; 082 import static org.opends.server.schema.SchemaConstants.*; 083 import static org.opends.server.util.StaticUtils.*; 084 085 086 087 /** 088 * This class provides a data structure for holding password policy state 089 * information for a user account. 090 */ 091 public class PasswordPolicyState 092 { 093 /** 094 * The tracer object for the debug logger. 095 */ 096 private static final DebugTracer TRACER = getTracer(); 097 098 099 100 // The user entry with which this state information is associated. 101 private final Entry userEntry; 102 103 // Indicates whether the user entry itself should be updated or if the updates 104 // should be stored as modifications. 105 private final boolean updateEntry; 106 107 // The string representation of the user's DN. 108 private final String userDNString; 109 110 // The password policy with which the account is associated. 111 private final PasswordPolicy passwordPolicy; 112 113 // The current time for use in all password policy calculations. 114 private final long currentTime; 115 116 // The time that the user's password was last changed. 117 private long passwordChangedTime = Long.MIN_VALUE; 118 119 // Indicates whether the user's account is expired. 120 private ConditionResult isAccountExpired = ConditionResult.UNDEFINED; 121 122 // Indicates whether the user's account is disabled. 123 private ConditionResult isDisabled = ConditionResult.UNDEFINED; 124 125 // Indicates whether the user's password is expired. 126 private ConditionResult isPasswordExpired = ConditionResult.UNDEFINED; 127 128 // Indicates whether the warning to send to the client would be the first 129 // warning for the user. 130 private ConditionResult isFirstWarning = ConditionResult.UNDEFINED; 131 132 // Indicates whether the user's account is locked by the idle lockout. 133 private ConditionResult isIdleLocked = ConditionResult.UNDEFINED; 134 135 // Indicates whether the user may use a grace login if the password is expired 136 // and there are one or more grace logins remaining. 137 private ConditionResult mayUseGraceLogin = ConditionResult.UNDEFINED; 138 139 // Indicates whether the user's password must be changed. 140 private ConditionResult mustChangePassword = ConditionResult.UNDEFINED; 141 142 // Indicates whether the user should be warned of an upcoming expiration. 143 private ConditionResult shouldWarn = ConditionResult.UNDEFINED; 144 145 // The number of seconds until the user's account is automatically unlocked. 146 private int secondsUntilUnlock = Integer.MIN_VALUE; 147 148 // The set of authentication failure times for this user. 149 private List<Long> authFailureTimes = null; 150 151 // The set of grace login times for this user. 152 private List<Long> graceLoginTimes = null; 153 154 // The time that the user's account should expire (or did expire). 155 private long accountExpirationTime = Long.MIN_VALUE; 156 157 // The time that the user's entry was locked due to too many authentication 158 // failures. 159 private long failureLockedTime = Long.MIN_VALUE; 160 161 // The time that the user last authenticated to the Directory Server. 162 private long lastLoginTime = Long.MIN_VALUE; 163 164 // The time that the user's password should expire (or did expire). 165 private long passwordExpirationTime = Long.MIN_VALUE; 166 167 // The last required change time with which the user complied. 168 private long requiredChangeTime = Long.MIN_VALUE; 169 170 // The time that the user was first warned about an upcoming expiration. 171 private long warnedTime = Long.MIN_VALUE; 172 173 // The set of modifications that should be applied to the user's entry. 174 private LinkedList<Modification> modifications 175 = new LinkedList<Modification>(); 176 177 178 179 /** 180 * Creates a new password policy state object with the provided information. 181 * 182 * @param userEntry The entry with the user account. 183 * @param updateEntry Indicates whether changes should update the provided 184 * user entry directly or whether they should be 185 * collected as a set of modifications. 186 * 187 * @throws DirectoryException If a problem occurs while attempting to 188 * determine the password policy for the user or 189 * perform any other state initialization. 190 */ 191 public PasswordPolicyState(Entry userEntry, boolean updateEntry) 192 throws DirectoryException 193 { 194 this(userEntry, updateEntry, TimeThread.getTime(), false); 195 } 196 197 198 199 /** 200 * Creates a new password policy state object with the provided information. 201 * Note that this version of the constructor should only be used for testing 202 * purposes when the tests should be evaluated with a fixed time rather than 203 * the actual current time. For all other purposes, the other constructor 204 * should be used. 205 * 206 * @param userEntry The entry with the user account. 207 * @param updateEntry Indicates whether changes should update the 208 * provided user entry directly or whether they 209 * should be collected as a set of modifications. 210 * @param currentTime The time to use as the current time for all 211 * time-related determinations. 212 * @param useDefaultOnError Indicates whether the server should fall back to 213 * using the default password policy if there is a 214 * problem with the configured policy for the user. 215 * 216 * @throws DirectoryException If a problem occurs while attempting to 217 * determine the password policy for the user or 218 * perform any other state initialization. 219 */ 220 public PasswordPolicyState(Entry userEntry, boolean updateEntry, 221 long currentTime, boolean useDefaultOnError) 222 throws DirectoryException 223 { 224 this.userEntry = userEntry; 225 this.updateEntry = updateEntry; 226 this.currentTime = currentTime; 227 228 userDNString = userEntry.getDN().toString(); 229 passwordPolicy = getPasswordPolicyInternal(this.userEntry, 230 useDefaultOnError); 231 232 // Get the password changed time for the user. 233 AttributeType type 234 = DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_CHANGED_TIME_LC); 235 if (type == null) 236 { 237 type = DirectoryServer.getDefaultAttributeType( 238 OP_ATTR_PWPOLICY_CHANGED_TIME); 239 } 240 241 passwordChangedTime = getGeneralizedTime(type); 242 if (passwordChangedTime <= 0) 243 { 244 // Get the time that the user's account was created. 245 AttributeType createTimeType 246 = DirectoryServer.getAttributeType(OP_ATTR_CREATE_TIMESTAMP_LC); 247 if (createTimeType == null) 248 { 249 createTimeType 250 = DirectoryServer.getDefaultAttributeType(OP_ATTR_CREATE_TIMESTAMP); 251 } 252 passwordChangedTime = getGeneralizedTime(createTimeType); 253 254 if (passwordChangedTime <= 0) 255 { 256 passwordChangedTime = 0; 257 258 if (debugEnabled()) 259 { 260 TRACER.debugWarning("Could not determine password changed time for " + 261 "user %s.", userDNString); 262 } 263 } 264 } 265 } 266 267 268 269 /** 270 * Retrieves the password policy for the user. If the user entry contains the 271 * ds-pwp-password-policy-dn attribute (whether real or virtual), that 272 * password policy is returned, otherwise the default password policy is 273 * returned. 274 * 275 * @param userEntry The user entry. 276 * @param useDefaultOnError Indicates whether the server should fall back to 277 * using the default password policy if there is a 278 * problem with the configured policy for the user. 279 * 280 * @return The password policy for the user. 281 * 282 * @throws DirectoryException If a problem occurs while attempting to 283 * determine the password policy for the user. 284 */ 285 private static PasswordPolicy getPasswordPolicyInternal(Entry userEntry, 286 boolean useDefaultOnError) 287 throws DirectoryException 288 { 289 String userDNString = userEntry.getDN().toString(); 290 AttributeType type = 291 DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_POLICY_DN, true); 292 293 List<Attribute> attrList = userEntry.getAttribute(type); 294 if (attrList != null) 295 { 296 for (Attribute a : attrList) 297 { 298 if(a.getValues().isEmpty()) continue; 299 300 AttributeValue v = a.getValues().iterator().next(); 301 DN subentryDN; 302 try 303 { 304 subentryDN = DN.decode(v.getValue()); 305 } 306 catch (Exception e) 307 { 308 if (debugEnabled()) 309 { 310 TRACER.debugCaught(DebugLogLevel.ERROR, e); 311 } 312 313 if (debugEnabled()) 314 { 315 TRACER.debugError("Could not parse password policy subentry " + 316 "DN %s for user %s: %s", 317 v.getStringValue(), userDNString, 318 stackTraceToSingleLineString(e)); 319 } 320 321 Message message = ERR_PWPSTATE_CANNOT_DECODE_SUBENTRY_VALUE_AS_DN.get( 322 v.getStringValue(), userDNString, e.getMessage()); 323 if (useDefaultOnError) 324 { 325 ErrorLogger.logError(message); 326 return DirectoryServer.getDefaultPasswordPolicy(); 327 } 328 else 329 { 330 throw new DirectoryException(ResultCode.INVALID_DN_SYNTAX, message, 331 e); 332 } 333 } 334 335 PasswordPolicy policy = DirectoryServer.getPasswordPolicy(subentryDN); 336 if (policy == null) 337 { 338 if (debugEnabled()) 339 { 340 TRACER.debugError("Password policy subentry %s for user %s " + 341 "is not defined in the Directory Server.", 342 String.valueOf(subentryDN), userDNString); 343 } 344 345 Message message = ERR_PWPSTATE_NO_SUCH_POLICY.get( 346 userDNString, String.valueOf(subentryDN)); 347 if (useDefaultOnError) 348 { 349 ErrorLogger.logError(message); 350 return DirectoryServer.getDefaultPasswordPolicy(); 351 } 352 else 353 { 354 throw new DirectoryException( 355 DirectoryServer.getServerErrorResultCode(), message); 356 } 357 } 358 359 if (debugEnabled()) 360 { 361 TRACER.debugInfo("Using password policy subentry %s for user %s.", 362 String.valueOf(subentryDN), userDNString); 363 } 364 365 return policy; 366 } 367 } 368 369 // There is no policy subentry defined: use the default. 370 if (debugEnabled()) 371 { 372 TRACER.debugInfo("Using the default password policy for user %s", 373 userDNString); 374 } 375 376 return DirectoryServer.getDefaultPasswordPolicy(); 377 } 378 379 380 381 /** 382 * Retrieves the value of the specified attribute as a string. 383 * 384 * @param attributeType The attribute type whose value should be retrieved. 385 * 386 * @return The value of the specified attribute as a string, or 387 * <CODE>null</CODE> if there is no such value. 388 */ 389 private String getValue(AttributeType attributeType) 390 { 391 String stringValue = null; 392 393 List<Attribute> attrList = userEntry.getAttribute(attributeType); 394 if (attrList != null) 395 { 396 for (Attribute a : attrList) 397 { 398 if (a.getValues().isEmpty()) continue; 399 400 stringValue = a.getValues().iterator().next().getStringValue(); 401 break ; 402 } 403 } 404 405 if (stringValue == null) 406 { 407 if (debugEnabled()) 408 { 409 TRACER.debugInfo("Returning null because attribute %s does not " + 410 "exist in user entry %s", 411 attributeType.getNameOrOID(), userDNString); 412 } 413 } 414 else 415 { 416 if (debugEnabled()) 417 { 418 TRACER.debugInfo("Returning value %s for user %s", 419 stringValue, userDNString); 420 } 421 } 422 423 return stringValue; 424 } 425 426 427 428 /** 429 * Retrieves the value of the specified attribute from the user's entry as a 430 * time in generalized time format. 431 * 432 * @param attributeType The attribute type whose value should be parsed as a 433 * generalized time value. 434 * 435 * @return The requested time, or -1 if it could not be determined. 436 * 437 * @throws DirectoryException If a problem occurs while attempting to 438 * decode the value as a generalized time. 439 */ 440 private long getGeneralizedTime(AttributeType attributeType) 441 throws DirectoryException 442 { 443 long timeValue = -1 ; 444 445 List<Attribute> attrList = userEntry.getAttribute(attributeType); 446 if (attrList != null) 447 { 448 for (Attribute a : attrList) 449 { 450 if (a.getValues().isEmpty()) continue; 451 452 AttributeValue v = a.getValues().iterator().next(); 453 try 454 { 455 timeValue = GeneralizedTimeSyntax.decodeGeneralizedTimeValue( 456 v.getNormalizedValue()); 457 } 458 catch (Exception e) 459 { 460 if (debugEnabled()) 461 { 462 TRACER.debugCaught(DebugLogLevel.ERROR, e); 463 464 TRACER.debugWarning("Unable to decode value %s for attribute %s " + 465 "in user entry %s: %s", 466 v.getStringValue(), attributeType.getNameOrOID(), 467 userDNString, stackTraceToSingleLineString(e)); 468 } 469 470 Message message = ERR_PWPSTATE_CANNOT_DECODE_GENERALIZED_TIME. 471 get(v.getStringValue(), attributeType.getNameOrOID(), 472 userDNString, String.valueOf(e)); 473 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 474 message, e); 475 } 476 break ; 477 } 478 } 479 480 if (timeValue == -1) 481 { 482 if (debugEnabled()) 483 { 484 TRACER.debugInfo("Returning -1 because attribute %s does not " + 485 "exist in user entry %s", 486 attributeType.getNameOrOID(), userDNString); 487 } 488 } 489 // FIXME: else to be consistent... 490 491 return timeValue; 492 } 493 494 495 496 /** 497 * Retrieves the set of values of the specified attribute from the user's 498 * entry in generalized time format. 499 * 500 * @param attributeType The attribute type whose values should be parsed as 501 * generalized time values. 502 * 503 * @return The set of generalized time values, or an empty list if there are 504 * none. 505 * 506 * @throws DirectoryException If a problem occurs while attempting to 507 * decode a value as a generalized time. 508 */ 509 private List<Long> getGeneralizedTimes(AttributeType attributeType) 510 throws DirectoryException 511 { 512 ArrayList<Long> timeValues = new ArrayList<Long>(); 513 514 List<Attribute> attrList = userEntry.getAttribute(attributeType); 515 if (attrList != null) 516 { 517 for (Attribute a : attrList) 518 { 519 for (AttributeValue v : a.getValues()) 520 { 521 try 522 { 523 timeValues.add(GeneralizedTimeSyntax.decodeGeneralizedTimeValue( 524 v.getNormalizedValue())); 525 } 526 catch (Exception e) 527 { 528 if (debugEnabled()) 529 { 530 TRACER.debugCaught(DebugLogLevel.ERROR, e); 531 532 TRACER.debugWarning("Unable to decode value %s for attribute %s" + 533 "in user entry %s: %s", 534 v.getStringValue(), attributeType.getNameOrOID(), 535 userDNString, stackTraceToSingleLineString(e)); 536 } 537 538 Message message = ERR_PWPSTATE_CANNOT_DECODE_GENERALIZED_TIME. 539 get(v.getStringValue(), attributeType.getNameOrOID(), 540 userDNString, String.valueOf(e)); 541 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 542 message, e); 543 } 544 } 545 } 546 } 547 548 if (timeValues.isEmpty()) 549 { 550 if (debugEnabled()) 551 { 552 TRACER.debugInfo("Returning an empty list because attribute %s " + 553 "does not exist in user entry %s", 554 attributeType.getNameOrOID(), userDNString); 555 } 556 } 557 return timeValues; 558 } 559 560 561 562 /** 563 * Retrieves the value of the specified attribute from the user's entry as a 564 * Boolean. 565 * 566 * @param attributeType The attribute type whose value should be parsed as a 567 * Boolean. 568 * 569 * @return The attribute's value represented as a ConditionResult value, or 570 * ConditionResult.UNDEFINED if the specified attribute does not 571 * exist in the entry. 572 * 573 * @throws DirectoryException If the value cannot be decoded as a Boolean. 574 */ 575 private ConditionResult getBoolean(AttributeType attributeType) 576 throws DirectoryException 577 { 578 List<Attribute> attrList = userEntry.getAttribute(attributeType); 579 if (attrList != null) 580 { 581 for (Attribute a : attrList) 582 { 583 if (a.getValues().isEmpty()) continue; 584 585 String valueString 586 = toLowerCase(a.getValues().iterator().next().getStringValue()); 587 588 if (valueString.equals("true") || valueString.equals("yes") || 589 valueString.equals("on") || valueString.equals("1")) 590 { 591 if (debugEnabled()) 592 { 593 TRACER.debugInfo("Attribute %s resolves to true for user entry " + 594 "%s", attributeType.getNameOrOID(), userDNString); 595 } 596 597 return ConditionResult.TRUE; 598 } 599 600 if (valueString.equals("false") || valueString.equals("no") || 601 valueString.equals("off") || valueString.equals("0")) 602 { 603 if (debugEnabled()) 604 { 605 TRACER.debugInfo("Attribute %s resolves to false for user " + 606 "entry %s", attributeType.getNameOrOID(), userDNString); 607 } 608 609 return ConditionResult.FALSE; 610 } 611 612 if(debugEnabled()) 613 { 614 TRACER.debugError("Unable to resolve value %s for attribute %s " + 615 "in user entry %s as a Boolean.", 616 valueString, attributeType.getNameOrOID(), 617 userDNString); 618 } 619 620 Message message = ERR_PWPSTATE_CANNOT_DECODE_BOOLEAN.get( 621 valueString, attributeType.getNameOrOID(), userDNString); 622 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 623 message); 624 } 625 } 626 627 if (debugEnabled()) 628 { 629 TRACER.debugInfo("Returning %s because attribute %s does not exist " + 630 "in user entry %s", 631 ConditionResult.UNDEFINED.toString(), 632 attributeType.getNameOrOID(), userDNString); 633 } 634 635 return ConditionResult.UNDEFINED; 636 } 637 638 639 640 /** 641 * Retrieves the password policy associated with this state information. 642 * 643 * @return The password policy associated with this state information. 644 */ 645 public PasswordPolicy getPolicy() 646 { 647 return passwordPolicy; 648 } 649 650 651 652 /** 653 * Retrieves the time that the password was last changed. 654 * 655 * @return The time that the password was last changed. 656 */ 657 public long getPasswordChangedTime() 658 { 659 return passwordChangedTime; 660 } 661 662 663 664 /** 665 * Retrieves the time that this password policy state object was created. 666 * 667 * @return The time that this password policy state object was created. 668 */ 669 public long getCurrentTime() 670 { 671 return currentTime; 672 } 673 674 675 676 /** 677 * Retrieves the set of values for the password attribute from the user entry. 678 * 679 * @return The set of values for the password attribute from the user entry. 680 */ 681 public LinkedHashSet<AttributeValue> getPasswordValues() 682 { 683 List<Attribute> attrList = 684 userEntry.getAttribute(passwordPolicy.getPasswordAttribute()); 685 if (attrList != null) 686 { 687 for (Attribute a : attrList) 688 { 689 if (a.getValues().isEmpty()) continue; 690 691 return a.getValues(); 692 } 693 } 694 695 return new LinkedHashSet<AttributeValue>(0); 696 } 697 698 699 700 /** 701 * Sets a new value for the password changed time equal to the current time. 702 */ 703 public void setPasswordChangedTime() 704 { 705 setPasswordChangedTime(currentTime); 706 } 707 708 709 710 /** 711 * Sets a new value for the password changed time equal to the specified time. 712 * This method should generally only be used for testing purposes, since the 713 * variant that uses the current time is preferred almost everywhere else. 714 * 715 * @param passwordChangedTime The time to use 716 */ 717 public void setPasswordChangedTime(long passwordChangedTime) 718 { 719 if (debugEnabled()) 720 { 721 TRACER.debugInfo("Setting password changed time for user %s to " + 722 "current time of %d", userDNString, currentTime); 723 } 724 725 // passwordChangedTime is computed in the constructor from values in the 726 // entry. 727 if (this.passwordChangedTime != passwordChangedTime) 728 { 729 this.passwordChangedTime = passwordChangedTime; 730 731 AttributeType type = 732 DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_CHANGED_TIME_LC); 733 if (type == null) 734 { 735 type = DirectoryServer.getDefaultAttributeType( 736 OP_ATTR_PWPOLICY_CHANGED_TIME); 737 } 738 739 LinkedHashSet<AttributeValue> values = 740 new LinkedHashSet<AttributeValue>(1); 741 String timeValue = GeneralizedTimeSyntax.format(passwordChangedTime); 742 values.add(new AttributeValue(type, timeValue)); 743 744 Attribute a = new Attribute(type, OP_ATTR_PWPOLICY_CHANGED_TIME, values); 745 746 if (updateEntry) 747 { 748 ArrayList<Attribute> attrList = new ArrayList<Attribute>(1); 749 attrList.add(a); 750 userEntry.putAttribute(type, attrList); 751 } 752 else 753 { 754 modifications.add(new Modification(ModificationType.REPLACE, a, true)); 755 } 756 } 757 } 758 759 760 761 /** 762 * Removes the password changed time value from the user's entry. This should 763 * only be used for testing purposes, as it can really mess things up if you 764 * don't know what you're doing. 765 */ 766 public void clearPasswordChangedTime() 767 { 768 if (debugEnabled()) 769 { 770 TRACER.debugInfo("Clearing password changed time for user %s", 771 userDNString); 772 } 773 774 AttributeType type = 775 DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_CHANGED_TIME_LC, 776 true); 777 if (updateEntry) 778 { 779 userEntry.removeAttribute(type); 780 } 781 else 782 { 783 Attribute a = new Attribute(type); 784 modifications.add(new Modification(ModificationType.REPLACE, a, true)); 785 } 786 787 788 // Fall back to using the entry creation time as the password changed time, 789 // if it's defined. Otherwise, use a value of zero. 790 AttributeType createTimeType = 791 DirectoryServer.getAttributeType(OP_ATTR_CREATE_TIMESTAMP_LC, true); 792 try 793 { 794 passwordChangedTime = getGeneralizedTime(createTimeType); 795 if (passwordChangedTime <= 0) 796 { 797 passwordChangedTime = 0; 798 } 799 } 800 catch (Exception e) 801 { 802 passwordChangedTime = 0; 803 } 804 } 805 806 807 808 809 /** 810 * Indicates whether the user account has been administratively disabled. 811 * 812 * @return <CODE>true</CODE> if the user account has been administratively 813 * disabled, or <CODE>false</CODE> otherwise. 814 */ 815 public boolean isDisabled() 816 { 817 if (isDisabled != ConditionResult.UNDEFINED) 818 { 819 if (debugEnabled()) 820 { 821 TRACER.debugInfo("Returning stored result of %b for user %s", 822 (isDisabled == ConditionResult.TRUE), userDNString); 823 } 824 825 return isDisabled == ConditionResult.TRUE; 826 } 827 828 AttributeType type = 829 DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_DISABLED, true); 830 try 831 { 832 isDisabled = getBoolean(type); 833 } 834 catch (Exception e) 835 { 836 if (debugEnabled()) 837 { 838 TRACER.debugCaught(DebugLogLevel.ERROR, e); 839 } 840 841 isDisabled = ConditionResult.TRUE; 842 if (debugEnabled()) 843 { 844 TRACER.debugWarning("User %s is considered administratively " + 845 "disabled because an error occurred while attempting to make " + 846 "the determination: %s.", 847 userDNString, stackTraceToSingleLineString(e)); 848 } 849 850 return true; 851 } 852 853 if (isDisabled == ConditionResult.UNDEFINED) 854 { 855 isDisabled = ConditionResult.FALSE; 856 if (debugEnabled()) 857 { 858 TRACER.debugInfo("User %s is not administratively disabled since " + 859 "the attribute \"%s\" is not present in the entry.", 860 userDNString, OP_ATTR_ACCOUNT_DISABLED); 861 } 862 return false; 863 } 864 865 if (debugEnabled()) 866 { 867 TRACER.debugInfo("User %s %s administratively disabled.", 868 userDNString, 869 ((isDisabled == ConditionResult.TRUE) ? " is" : " is not")); 870 } 871 872 return isDisabled == ConditionResult.TRUE; 873 } 874 875 876 877 /** 878 * Updates the user entry to indicate whether user account has been 879 * administratively disabled. 880 * 881 * @param isDisabled Indicates whether the user account has been 882 * administratively disabled. 883 */ 884 public void setDisabled(boolean isDisabled) 885 { 886 if (debugEnabled()) 887 { 888 TRACER.debugInfo("Updating user %s to set the disabled flag to %b", 889 userDNString, isDisabled); 890 } 891 892 893 if (isDisabled == isDisabled()) 894 { 895 return; // requested state matches current state 896 } 897 898 this.isDisabled = ConditionResult.inverseOf(this.isDisabled); 899 900 AttributeType type = 901 DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_DISABLED, true); 902 903 if (isDisabled) 904 { 905 LinkedHashSet<AttributeValue> values 906 = new LinkedHashSet<AttributeValue>(1); 907 values.add(new AttributeValue(type, String.valueOf(true))); 908 Attribute a = new Attribute(type, OP_ATTR_ACCOUNT_DISABLED, values); 909 910 if (updateEntry) 911 { 912 ArrayList<Attribute> attrList = new ArrayList<Attribute>(1); 913 attrList.add(a); 914 userEntry.putAttribute(type, attrList); 915 } 916 else 917 { 918 modifications.add(new Modification(ModificationType.REPLACE, a, true)); 919 } 920 } 921 else 922 { 923 // erase 924 if (updateEntry) 925 { 926 userEntry.removeAttribute(type); 927 } 928 else 929 { 930 modifications.add(new Modification(ModificationType.REPLACE, 931 new Attribute(type), true)); 932 } 933 } 934 } 935 936 937 938 /** 939 * Indicates whether the user's account is currently expired. 940 * 941 * @return <CODE>true</CODE> if the user's account is expired, or 942 * <CODE>false</CODE> if not. 943 */ 944 public boolean isAccountExpired() 945 { 946 if (isAccountExpired != ConditionResult.UNDEFINED) 947 { 948 if (debugEnabled()) 949 { 950 TRACER.debugInfo("Returning stored result of %b for user %s", 951 (isAccountExpired == ConditionResult.TRUE), userDNString); 952 } 953 954 return isAccountExpired == ConditionResult.TRUE; 955 } 956 957 AttributeType type = 958 DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_EXPIRATION_TIME, 959 true); 960 961 try 962 { 963 accountExpirationTime = getGeneralizedTime(type); 964 } 965 catch (Exception e) 966 { 967 if (debugEnabled()) 968 { 969 TRACER.debugCaught(DebugLogLevel.ERROR, e); 970 } 971 972 isAccountExpired = ConditionResult.TRUE; 973 if (debugEnabled()) 974 { 975 TRACER.debugWarning("User %s is considered to have an expired " + 976 "account because an error occurred while attempting to make " + 977 "the determination: %s.", 978 userDNString, stackTraceToSingleLineString(e)); 979 } 980 981 return true; 982 } 983 984 if (accountExpirationTime > currentTime) 985 { 986 // The user does have an expiration time, but it hasn't arrived yet. 987 isAccountExpired = ConditionResult.FALSE; 988 if (debugEnabled()) 989 { 990 TRACER.debugInfo("The account for user %s is not expired because " + 991 "the expiration time has not yet arrived.", userDNString); 992 } 993 } 994 else if (accountExpirationTime >= 0) 995 { 996 // The user does have an expiration time, and it is in the past. 997 isAccountExpired = ConditionResult.TRUE; 998 if (debugEnabled()) 999 { 1000 TRACER.debugInfo("The account for user %s is expired because the " + 1001 "expiration time in that account has passed.", userDNString); 1002 } 1003 } 1004 else 1005 { 1006 // The user doesn't have an expiration time in their entry, so it 1007 // can't be expired. 1008 isAccountExpired = ConditionResult.FALSE; 1009 if (debugEnabled()) 1010 { 1011 TRACER.debugInfo("The account for user %s is not expired because " + 1012 "there is no expiration time in the user's entry.", 1013 userDNString); 1014 } 1015 } 1016 1017 return isAccountExpired == ConditionResult.TRUE; 1018 } 1019 1020 1021 1022 /** 1023 * Retrieves the time at which the user's account will expire. 1024 * 1025 * @return The time at which the user's account will expire, or -1 if it is 1026 * not configured with an expiration time. 1027 */ 1028 public long getAccountExpirationTime() 1029 { 1030 if (accountExpirationTime == Long.MIN_VALUE) 1031 { 1032 isAccountExpired(); 1033 } 1034 1035 return accountExpirationTime; 1036 } 1037 1038 1039 1040 /** 1041 * Sets the user's account expiration time to the specified value. 1042 * 1043 * @param accountExpirationTime The time that the user's account should 1044 * expire. 1045 */ 1046 public void setAccountExpirationTime(long accountExpirationTime) 1047 { 1048 if (accountExpirationTime < 0) 1049 { 1050 clearAccountExpirationTime(); 1051 } 1052 else 1053 { 1054 String timeStr = GeneralizedTimeSyntax.format(accountExpirationTime); 1055 1056 if (debugEnabled()) 1057 { 1058 TRACER.debugInfo("Setting account expiration time for user %s to %s", 1059 userDNString, timeStr); 1060 } 1061 1062 this.accountExpirationTime = accountExpirationTime; 1063 AttributeType type = 1064 DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_EXPIRATION_TIME, 1065 true); 1066 1067 LinkedHashSet<AttributeValue> values = 1068 new LinkedHashSet<AttributeValue>(1); 1069 values.add(new AttributeValue(type, timeStr)); 1070 1071 Attribute a = new Attribute(type, OP_ATTR_ACCOUNT_EXPIRATION_TIME, 1072 values); 1073 1074 if (updateEntry) 1075 { 1076 ArrayList<Attribute> attrList = new ArrayList<Attribute>(1); 1077 attrList.add(a); 1078 userEntry.putAttribute(type, attrList); 1079 } 1080 else 1081 { 1082 modifications.add(new Modification(ModificationType.REPLACE, a, true)); 1083 } 1084 } 1085 } 1086 1087 1088 1089 /** 1090 * Clears the user's account expiration time. 1091 */ 1092 public void clearAccountExpirationTime() 1093 { 1094 if (debugEnabled()) 1095 { 1096 TRACER.debugInfo("Clearing account expiration time for user %s", 1097 userDNString); 1098 } 1099 1100 accountExpirationTime = -1; 1101 1102 AttributeType type = 1103 DirectoryServer.getAttributeType(OP_ATTR_ACCOUNT_EXPIRATION_TIME, 1104 true); 1105 1106 if (updateEntry) 1107 { 1108 userEntry.removeAttribute(type); 1109 } 1110 else 1111 { 1112 modifications.add(new Modification(ModificationType.REPLACE, 1113 new Attribute(type), true)); 1114 } 1115 } 1116 1117 1118 1119 /** 1120 * Retrieves the set of times of failed authentication attempts for the user. 1121 * If authentication failure time expiration is enabled, and there are expired 1122 * times in the entry, these times are removed from the instance field and an 1123 * update is provided to delete those values from the entry. 1124 * 1125 * @return The set of times of failed authentication attempts for the user, 1126 * which will be an empty list in the case of no valid (unexpired) 1127 * times in the entry. 1128 */ 1129 public List<Long> getAuthFailureTimes() 1130 { 1131 if (authFailureTimes != null) 1132 { 1133 if (debugEnabled()) 1134 { 1135 TRACER.debugInfo("Returning stored auth failure time list of %d " + 1136 "elements for user %s" + 1137 authFailureTimes.size(), userDNString); 1138 } 1139 1140 return authFailureTimes; 1141 } 1142 1143 AttributeType type = 1144 DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_FAILURE_TIME_LC); 1145 if (type == null) 1146 { 1147 type = DirectoryServer.getDefaultAttributeType( 1148 OP_ATTR_PWPOLICY_FAILURE_TIME); 1149 } 1150 1151 try 1152 { 1153 authFailureTimes = getGeneralizedTimes(type); 1154 } 1155 catch (Exception e) 1156 { 1157 if (debugEnabled()) 1158 { 1159 TRACER.debugCaught(DebugLogLevel.ERROR, e); 1160 } 1161 1162 if (debugEnabled()) 1163 { 1164 TRACER.debugWarning("Error while processing auth failure times " + 1165 "for user %s: %s", 1166 userDNString, stackTraceToSingleLineString(e)); 1167 } 1168 1169 authFailureTimes = new ArrayList<Long>(); 1170 1171 if (updateEntry) 1172 { 1173 userEntry.removeAttribute(type); 1174 } 1175 else 1176 { 1177 modifications.add(new Modification(ModificationType.REPLACE, 1178 new Attribute(type), true)); 1179 } 1180 1181 return authFailureTimes; 1182 } 1183 1184 if (authFailureTimes.isEmpty()) 1185 { 1186 if (debugEnabled()) 1187 { 1188 TRACER.debugInfo("Returning an empty auth failure time list for " + 1189 "user %s because the attribute is absent from the entry.", 1190 userDNString); 1191 } 1192 1193 return authFailureTimes; 1194 } 1195 1196 // Remove any expired failures from the list. 1197 if (passwordPolicy.getLockoutFailureExpirationInterval() > 0) 1198 { 1199 LinkedHashSet<AttributeValue> valuesToRemove = null; 1200 1201 long expirationTime = currentTime - 1202 (passwordPolicy.getLockoutFailureExpirationInterval() * 1000L); 1203 Iterator<Long> iterator = authFailureTimes.iterator(); 1204 while (iterator.hasNext()) 1205 { 1206 long l = iterator.next(); 1207 if (l < expirationTime) 1208 { 1209 if (debugEnabled()) 1210 { 1211 TRACER.debugInfo("Removing expired auth failure time %d for " + 1212 "user %s", l, userDNString); 1213 } 1214 1215 iterator.remove(); 1216 1217 if (valuesToRemove == null) 1218 { 1219 valuesToRemove = new LinkedHashSet<AttributeValue>(); 1220 } 1221 1222 valuesToRemove.add(new AttributeValue(type, 1223 GeneralizedTimeSyntax.format(l))); 1224 } 1225 } 1226 1227 if (valuesToRemove != null) 1228 { 1229 if (updateEntry) 1230 { 1231 if (authFailureTimes.isEmpty()) 1232 { 1233 userEntry.removeAttribute(type); 1234 } 1235 else 1236 { 1237 LinkedHashSet<AttributeValue> keepValues = 1238 new LinkedHashSet<AttributeValue>(authFailureTimes.size()); 1239 for (Long l : authFailureTimes) 1240 { 1241 keepValues.add( 1242 new AttributeValue(type, GeneralizedTimeSyntax.format(l))); 1243 } 1244 ArrayList<Attribute> keepList = new ArrayList<Attribute>(1); 1245 keepList.add(new Attribute(type, OP_ATTR_PWPOLICY_FAILURE_TIME, 1246 keepValues)); 1247 userEntry.putAttribute(type, keepList); 1248 } 1249 } 1250 else 1251 { 1252 Attribute a = new Attribute(type, OP_ATTR_PWPOLICY_FAILURE_TIME, 1253 valuesToRemove); 1254 modifications.add(new Modification(ModificationType.DELETE, a, 1255 true)); 1256 } 1257 } 1258 } 1259 1260 if (debugEnabled()) 1261 { 1262 TRACER.debugInfo("Returning auth failure time list of %d elements " + 1263 "for user %s", authFailureTimes.size(), userDNString); 1264 } 1265 1266 return authFailureTimes; 1267 } 1268 1269 1270 1271 /** 1272 * Updates the set of authentication failure times to include the current 1273 * time. If the number of failures reaches the policy configuration limit, 1274 * lock the account. 1275 */ 1276 public void updateAuthFailureTimes() 1277 { 1278 if (passwordPolicy.getLockoutFailureCount() <= 0) 1279 { 1280 return; 1281 } 1282 1283 if (debugEnabled()) 1284 { 1285 TRACER.debugInfo("Updating authentication failure times for user %s", 1286 userDNString); 1287 } 1288 1289 1290 List<Long> failureTimes = getAuthFailureTimes(); 1291 // Note: failureTimes == this.authFailureTimes 1292 long highestFailureTime = -1; 1293 for (Long l : failureTimes) 1294 { 1295 highestFailureTime = Math.max(l, highestFailureTime); 1296 } 1297 1298 if (highestFailureTime >= currentTime) 1299 { 1300 highestFailureTime++; 1301 } 1302 else 1303 { 1304 highestFailureTime = currentTime; 1305 } 1306 failureTimes.add(highestFailureTime); 1307 1308 AttributeType type = 1309 DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_FAILURE_TIME_LC); 1310 if (type == null) 1311 { 1312 type = DirectoryServer.getDefaultAttributeType( 1313 OP_ATTR_PWPOLICY_FAILURE_TIME); 1314 } 1315 1316 LinkedHashSet<AttributeValue> values = 1317 new LinkedHashSet<AttributeValue>(failureTimes.size()); 1318 for (Long l : failureTimes) 1319 { 1320 values.add(new AttributeValue(type, GeneralizedTimeSyntax.format(l))); 1321 } 1322 1323 Attribute a = new Attribute(type, OP_ATTR_PWPOLICY_FAILURE_TIME, values); 1324 ArrayList<Attribute> attrList = new ArrayList<Attribute>(1); 1325 attrList.add(a); 1326 1327 LinkedHashSet<AttributeValue> addValues = 1328 new LinkedHashSet<AttributeValue>(1); 1329 addValues.add(new AttributeValue(type, 1330 GeneralizedTimeSyntax.format(highestFailureTime))); 1331 Attribute addAttr = new Attribute(type, OP_ATTR_PWPOLICY_FAILURE_TIME, 1332 addValues); 1333 1334 if (updateEntry) 1335 { 1336 userEntry.putAttribute(type, attrList); 1337 } 1338 else 1339 { 1340 modifications.add(new Modification(ModificationType.ADD, addAttr, true)); 1341 } 1342 1343 // Now check to see if there have been sufficient failures to lock the 1344 // account. 1345 int lockoutCount = passwordPolicy.getLockoutFailureCount(); 1346 if ((lockoutCount > 0) && (lockoutCount <= authFailureTimes.size())) 1347 { 1348 setFailureLockedTime(highestFailureTime); 1349 if (debugEnabled()) 1350 { 1351 TRACER.debugInfo("Locking user account %s due to too many failures.", 1352 userDNString); 1353 } 1354 } 1355 } 1356 1357 1358 1359 /** 1360 * Explicitly specifies the auth failure times for the associated user. This 1361 * should generally only be used for testing purposes. Note that it will also 1362 * set or clear the locked time as appropriate. 1363 * 1364 * @param authFailureTimes The set of auth failure times to use for the 1365 * account. An empty list or {@code null} will 1366 * clear the account of any existing failures. 1367 */ 1368 public void setAuthFailureTimes(List<Long> authFailureTimes) 1369 { 1370 if ((authFailureTimes == null) || authFailureTimes.isEmpty()) 1371 { 1372 clearAuthFailureTimes(); 1373 clearFailureLockedTime(); 1374 return; 1375 } 1376 1377 long highestFailureTime = -1; 1378 for (Long l : authFailureTimes) 1379 { 1380 highestFailureTime = Math.max(l, highestFailureTime); 1381 } 1382 1383 AttributeType type = 1384 DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_FAILURE_TIME_LC, 1385 true); 1386 1387 LinkedHashSet<AttributeValue> values = 1388 new LinkedHashSet<AttributeValue>(authFailureTimes.size()); 1389 for (Long l : authFailureTimes) 1390 { 1391 values.add(new AttributeValue(type, GeneralizedTimeSyntax.format(l))); 1392 } 1393 1394 Attribute a = new Attribute(type, OP_ATTR_PWPOLICY_FAILURE_TIME, values); 1395 1396 if (updateEntry) 1397 { 1398 ArrayList<Attribute> attrList = new ArrayList<Attribute>(1); 1399 attrList.add(a); 1400 userEntry.putAttribute(type, attrList); 1401 } 1402 else 1403 { 1404 modifications.add(new Modification(ModificationType.REPLACE, a, true)); 1405 } 1406 1407 // Now check to see if there have been sufficient failures to lock the 1408 // account. 1409 int lockoutCount = passwordPolicy.getLockoutFailureCount(); 1410 if ((lockoutCount > 0) && (lockoutCount <= authFailureTimes.size())) 1411 { 1412 setFailureLockedTime(highestFailureTime); 1413 if (debugEnabled()) 1414 { 1415 TRACER.debugInfo("Locking user account %s due to too many failures.", 1416 userDNString); 1417 } 1418 } 1419 } 1420 1421 1422 1423 /** 1424 * Updates the user entry to remove any record of previous authentication 1425 * failure times. 1426 */ 1427 private void clearAuthFailureTimes() 1428 { 1429 if (debugEnabled()) 1430 { 1431 TRACER.debugInfo("Clearing authentication failure times for user %s", 1432 userDNString); 1433 } 1434 1435 List<Long> failureTimes = getAuthFailureTimes(); 1436 if (failureTimes.isEmpty()) 1437 { 1438 return; 1439 } 1440 1441 failureTimes.clear(); // Note: failureTimes == this.authFailureTimes 1442 1443 AttributeType type = 1444 DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_FAILURE_TIME_LC); 1445 if (type == null) 1446 { 1447 type = DirectoryServer.getDefaultAttributeType( 1448 OP_ATTR_PWPOLICY_FAILURE_TIME); 1449 } 1450 1451 if (updateEntry) 1452 { 1453 userEntry.removeAttribute(type); 1454 } 1455 else 1456 { 1457 modifications.add(new Modification(ModificationType.REPLACE, 1458 new Attribute(type), true)); 1459 } 1460 } 1461 1462 1463 /** 1464 * Retrieves the time of an authentication failure lockout for the user. 1465 * 1466 * @return The time of an authentication failure lockout for the user, or -1 1467 * if no such time is present in the entry. 1468 */ 1469 private long getFailureLockedTime() 1470 { 1471 if (failureLockedTime != Long.MIN_VALUE) 1472 { 1473 return failureLockedTime; 1474 } 1475 1476 AttributeType type = 1477 DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_LOCKED_TIME_LC); 1478 if (type == null) 1479 { 1480 type = DirectoryServer.getDefaultAttributeType( 1481 OP_ATTR_PWPOLICY_LOCKED_TIME); 1482 } 1483 1484 try 1485 { 1486 failureLockedTime = getGeneralizedTime(type); 1487 } 1488 catch (Exception e) 1489 { 1490 if (debugEnabled()) 1491 { 1492 TRACER.debugCaught(DebugLogLevel.ERROR, e); 1493 } 1494 1495 failureLockedTime = currentTime; 1496 if (debugEnabled()) 1497 { 1498 TRACER.debugWarning("Returning current time for user %s because an " + 1499 "error occurred: %s", 1500 userDNString, stackTraceToSingleLineString(e)); 1501 } 1502 1503 return failureLockedTime; 1504 } 1505 1506 // An expired locked time is handled in lockedDueToFailures. 1507 return failureLockedTime; 1508 } 1509 1510 1511 1512 /** 1513 Sets the failure lockout attribute in the entry to the requested time. 1514 1515 @param time The time to which to set the entry's failure lockout attribute. 1516 */ 1517 private void setFailureLockedTime(final long time) 1518 { 1519 if (time == getFailureLockedTime()) 1520 { 1521 return; 1522 } 1523 1524 failureLockedTime = time; 1525 1526 AttributeType type = 1527 DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_LOCKED_TIME_LC); 1528 if (type == null) 1529 { 1530 type = DirectoryServer.getDefaultAttributeType( 1531 OP_ATTR_PWPOLICY_LOCKED_TIME); 1532 } 1533 1534 LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(1); 1535 values.add(new AttributeValue(type, 1536 GeneralizedTimeSyntax.format(failureLockedTime))); 1537 Attribute a = new Attribute(type, OP_ATTR_PWPOLICY_LOCKED_TIME, values); 1538 1539 if (updateEntry) 1540 { 1541 ArrayList<Attribute> attrList = new ArrayList<Attribute>(1); 1542 attrList.add(a); 1543 userEntry.putAttribute(type, attrList); 1544 } 1545 else 1546 { 1547 modifications.add(new Modification(ModificationType.REPLACE, a, true)); 1548 } 1549 } 1550 1551 1552 1553 /** 1554 * Updates the user entry to remove any record of previous authentication 1555 * failure lockout. 1556 */ 1557 private void clearFailureLockedTime() 1558 { 1559 if (debugEnabled()) 1560 { 1561 TRACER.debugInfo("Clearing failure lockout time for user %s.", 1562 userDNString); 1563 } 1564 1565 if (-1L == getFailureLockedTime()) 1566 { 1567 return; 1568 } 1569 1570 failureLockedTime = -1L; 1571 1572 AttributeType type = 1573 DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_LOCKED_TIME_LC); 1574 if (type == null) 1575 { 1576 type = DirectoryServer.getDefaultAttributeType( 1577 OP_ATTR_PWPOLICY_LOCKED_TIME); 1578 } 1579 1580 if (updateEntry) 1581 { 1582 userEntry.removeAttribute(type); 1583 } 1584 else 1585 { 1586 modifications.add(new Modification(ModificationType.REPLACE, 1587 new Attribute(type), true)); 1588 } 1589 } 1590 1591 1592 1593 /** 1594 * Indicates whether the associated user should be considered locked out as a 1595 * result of too many authentication failures. In the case of an expired 1596 * lock-out, this routine produces the update to clear the lock-out attribute 1597 * and the authentication failure timestamps. 1598 * In case the failure lockout time is absent from the entry, but sufficient 1599 * authentication failure timestamps are present in the entry, this routine 1600 * produces the update to set the lock-out attribute. 1601 * 1602 * @return <CODE>true</CODE> if the user is currently locked out due to too 1603 * many authentication failures, or <CODE>false</CODE> if not. 1604 */ 1605 public boolean lockedDueToFailures() 1606 { 1607 // FIXME: Introduce a state field to cache the computed value of this 1608 // method. Note that only a cached "locked" status can be returned due to 1609 // the possibility of intervening updates to this.failureLockedTime by 1610 // updateAuthFailureTimes. 1611 1612 // Check if the feature is enabled in the policy. 1613 final int maxFailures = passwordPolicy.getLockoutFailureCount(); 1614 if (maxFailures <= 0) 1615 { 1616 if (debugEnabled()) 1617 { 1618 TRACER.debugInfo("Returning false for user %s because lockout due " + 1619 "to failures is not enabled.", userDNString); 1620 } 1621 1622 return false; 1623 } 1624 1625 // Get the locked time from the user's entry. If it is present and not 1626 // expired, the account is locked. If it is absent, the failure timestamps 1627 // must be checked, since failure timestamps sufficient to lock the 1628 // account could be produced across the synchronization topology within the 1629 // synchronization latency. Also, note that IETF 1630 // draft-behera-ldap-password-policy-09 specifies "19700101000000Z" as 1631 // the value to be set under a "locked until reset" regime; however, this 1632 // implementation accepts the value as a locked entry, but observes the 1633 // lockout expiration policy for all values including this one. 1634 // FIXME: This "getter" is unusual in that it might produce an update to the 1635 // entry in two cases. Does it make sense to factor the methods so that, 1636 // e.g., an expired lockout is reported, and clearing the lockout is left to 1637 // the caller? 1638 if (getFailureLockedTime() < 0L) 1639 { 1640 // There was no locked time present in the entry; however, sufficient 1641 // failure times might have accumulated to trigger a lockout. 1642 if (getAuthFailureTimes().size() < maxFailures) 1643 { 1644 if (debugEnabled()) 1645 { 1646 TRACER.debugInfo("Returning false for user %s because there is " + 1647 "no locked time.", userDNString); 1648 } 1649 1650 return false; 1651 } 1652 1653 // The account isn't locked but should be, so do so now. 1654 setFailureLockedTime(currentTime);// FIXME: set to max(failureTimes)? 1655 1656 if (debugEnabled()) 1657 { 1658 TRACER.debugInfo("Locking user %s because there were enough " + 1659 "existing failures even though there was no account locked time.", 1660 userDNString); 1661 } 1662 // Fall through... 1663 } 1664 1665 // There is a failure locked time, but it may be expired. 1666 if (passwordPolicy.getLockoutDuration() > 0) 1667 { 1668 final long unlockTime = getFailureLockedTime() + 1669 (1000L * passwordPolicy.getLockoutDuration()); 1670 if (unlockTime > currentTime) 1671 { 1672 secondsUntilUnlock = (int) ((unlockTime - currentTime) / 1000); 1673 1674 if (debugEnabled()) 1675 { 1676 TRACER.debugInfo("Returning true for user %s because there is a " + 1677 "locked time and the lockout duration has not been reached.", 1678 userDNString); 1679 } 1680 1681 return true; 1682 } 1683 1684 // The lockout in the entry has expired... 1685 clearFailureLockout(); 1686 1687 if (debugEnabled()) 1688 { 1689 TRACER.debugInfo("Returning false for user %s " + 1690 "because the existing lockout has expired.", userDNString); 1691 } 1692 1693 assert -1L == getFailureLockedTime(); 1694 return false; 1695 } 1696 1697 if (debugEnabled()) 1698 { 1699 TRACER.debugInfo("Returning true for user %s " + 1700 "because there is a locked time and no lockout duration.", 1701 userDNString); 1702 } 1703 1704 assert -1L <= getFailureLockedTime(); 1705 return true; 1706 } 1707 1708 1709 1710 /** 1711 * Retrieves the length of time in seconds until the user's account is 1712 * automatically unlocked. This should only be called after calling 1713 * <CODE>lockedDueToFailures</CODE>. 1714 * 1715 * @return The length of time in seconds until the user's account is 1716 * automatically unlocked, or -1 if the account is not locked or the 1717 * lockout requires administrative action to clear. 1718 */ 1719 public int getSecondsUntilUnlock() 1720 { 1721 // secondsUntilUnlock is only set when failureLockedTime is present and 1722 // PasswordPolicy.getLockoutDuration is enabled; hence it is not 1723 // unreasonable to find secondsUntilUnlock uninitialized. 1724 assert failureLockedTime != Long.MIN_VALUE; 1725 1726 return (secondsUntilUnlock < 0) ? -1 : secondsUntilUnlock; 1727 } 1728 1729 1730 1731 /** 1732 * Updates the user account to remove any record of a previous lockout due to 1733 * failed authentications. 1734 */ 1735 public void clearFailureLockout() 1736 { 1737 clearAuthFailureTimes(); 1738 clearFailureLockedTime(); 1739 } 1740 1741 1742 1743 /** 1744 * Retrieves the time that the user last authenticated to the Directory 1745 * Server. 1746 * 1747 * @return The time that the user last authenticated to the Directory Server, 1748 * or -1 if it cannot be determined. 1749 */ 1750 public long getLastLoginTime() 1751 { 1752 if (lastLoginTime != Long.MIN_VALUE) 1753 { 1754 if (debugEnabled()) 1755 { 1756 TRACER.debugInfo("Returning stored last login time of %d for " + 1757 "user %s.", lastLoginTime, userDNString); 1758 } 1759 1760 return lastLoginTime; 1761 } 1762 1763 // The policy configuration must be checked since the entry cannot be 1764 // evaluated without both an attribute name and timestamp format. 1765 AttributeType type = passwordPolicy.getLastLoginTimeAttribute(); 1766 String format = passwordPolicy.getLastLoginTimeFormat(); 1767 1768 if ((type == null) || (format == null)) 1769 { 1770 lastLoginTime = -1; 1771 if (debugEnabled()) 1772 { 1773 TRACER.debugInfo("Returning -1 for user %s because no last login " + 1774 "time will be maintained.", userDNString); 1775 } 1776 1777 return lastLoginTime; 1778 } 1779 1780 lastLoginTime = -1; 1781 List<Attribute> attrList = userEntry.getAttribute(type); 1782 1783 if (attrList != null) 1784 { 1785 for (Attribute a : attrList) 1786 { 1787 if (a.getValues().isEmpty()) continue; 1788 1789 String valueString = a.getValues().iterator().next().getStringValue(); 1790 1791 try 1792 { 1793 SimpleDateFormat dateFormat = new SimpleDateFormat(format); 1794 lastLoginTime = dateFormat.parse(valueString).getTime(); 1795 1796 if (debugEnabled()) 1797 { 1798 TRACER.debugInfo("Returning last login time of %d for user %s" + 1799 "decoded using current last login time format.", 1800 lastLoginTime, userDNString); 1801 } 1802 1803 return lastLoginTime; 1804 } 1805 catch (Exception e) 1806 { 1807 if (debugEnabled()) 1808 { 1809 TRACER.debugCaught(DebugLogLevel.ERROR, e); 1810 } 1811 1812 // This could mean that the last login time was encoded using a 1813 // previous format. 1814 for (String f : passwordPolicy.getPreviousLastLoginTimeFormats()) 1815 { 1816 try 1817 { 1818 SimpleDateFormat dateFormat = new SimpleDateFormat(f); 1819 lastLoginTime = dateFormat.parse(valueString).getTime(); 1820 1821 if (debugEnabled()) 1822 { 1823 TRACER.debugInfo("Returning last login time of %d for " + 1824 "user %s decoded using previous last login time format " + 1825 "of %s.", lastLoginTime, userDNString, f); 1826 } 1827 1828 return lastLoginTime; 1829 } 1830 catch (Exception e2) 1831 { 1832 if (debugEnabled()) 1833 { 1834 TRACER.debugCaught(DebugLogLevel.ERROR, e); 1835 } 1836 } 1837 } 1838 1839 assert lastLoginTime == -1; 1840 if (debugEnabled()) 1841 { 1842 TRACER.debugWarning("Returning -1 for user %s because the " + 1843 "last login time value %s could not be parsed using any " + 1844 "known format.", userDNString, valueString); 1845 } 1846 1847 return lastLoginTime; 1848 } 1849 } 1850 } 1851 1852 assert lastLoginTime == -1; 1853 if (debugEnabled()) 1854 { 1855 TRACER.debugInfo("Returning %d for user %s because no last " + 1856 "login time value exists.", lastLoginTime, userDNString); 1857 } 1858 1859 return lastLoginTime; 1860 } 1861 1862 1863 1864 /** 1865 * Updates the user entry to set the current time as the last login time. 1866 */ 1867 public void setLastLoginTime() 1868 { 1869 setLastLoginTime(currentTime); 1870 } 1871 1872 1873 1874 /** 1875 * Updates the user entry to use the specified last login time. This should 1876 * be used primarily for testing purposes, as the variant that uses the 1877 * current time should be used most of the time. 1878 * 1879 * @param lastLoginTime The last login time to set in the user entry. 1880 */ 1881 public void setLastLoginTime(long lastLoginTime) 1882 { 1883 AttributeType type = passwordPolicy.getLastLoginTimeAttribute(); 1884 String format = passwordPolicy.getLastLoginTimeFormat(); 1885 1886 if ((type == null) || (format == null)) 1887 { 1888 return; 1889 } 1890 1891 String timestamp; 1892 try 1893 { 1894 SimpleDateFormat dateFormat = new SimpleDateFormat(format); 1895 timestamp = dateFormat.format(new Date(lastLoginTime)); 1896 this.lastLoginTime = dateFormat.parse(timestamp).getTime(); 1897 } 1898 catch (Exception e) 1899 { 1900 if (debugEnabled()) 1901 { 1902 TRACER.debugCaught(DebugLogLevel.ERROR, e); 1903 } 1904 1905 if (debugEnabled()) 1906 { 1907 TRACER.debugWarning("Unable to set last login time for user %s " + 1908 "because an error occurred: %s", 1909 userDNString, stackTraceToSingleLineString(e)); 1910 } 1911 1912 return; 1913 } 1914 1915 1916 String existingTimestamp = getValue(type); 1917 if ((existingTimestamp != null) && timestamp.equals(existingTimestamp)) 1918 { 1919 if (debugEnabled()) 1920 { 1921 TRACER.debugInfo("Not updating last login time for user %s " + 1922 "because the new value matches the existing value.", 1923 userDNString); 1924 } 1925 1926 return; 1927 } 1928 1929 1930 LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(1); 1931 values.add(new AttributeValue(type, timestamp)); 1932 1933 Attribute a = new Attribute(type, type.getNameOrOID(), values); 1934 1935 if (updateEntry) 1936 { 1937 ArrayList<Attribute> attrList = new ArrayList<Attribute>(1); 1938 attrList.add(a); 1939 userEntry.putAttribute(type, attrList); 1940 } 1941 else 1942 { 1943 modifications.add(new Modification(ModificationType.REPLACE, a, true)); 1944 } 1945 1946 if (debugEnabled()) 1947 { 1948 TRACER.debugInfo("Updated the last login time for user %s to %s", 1949 userDNString, timestamp); 1950 } 1951 } 1952 1953 1954 1955 /** 1956 * Clears the last login time from the user's entry. This should generally be 1957 * used only for testing purposes. 1958 */ 1959 public void clearLastLoginTime() 1960 { 1961 if (debugEnabled()) 1962 { 1963 TRACER.debugInfo("Clearing last login time for user %s", userDNString); 1964 } 1965 1966 lastLoginTime = -1; 1967 1968 AttributeType type = 1969 DirectoryServer.getAttributeType(OP_ATTR_LAST_LOGIN_TIME, true); 1970 1971 if (updateEntry) 1972 { 1973 userEntry.removeAttribute(type); 1974 } 1975 else 1976 { 1977 modifications.add(new Modification(ModificationType.REPLACE, 1978 new Attribute(type), true)); 1979 } 1980 } 1981 1982 1983 1984 /** 1985 * Indicates whether the user's account is currently locked because it has 1986 * been idle for too long. 1987 * 1988 * @return <CODE>true</CODE> if the user's account is locked because it has 1989 * been idle for too long, or <CODE>false</CODE> if not. 1990 */ 1991 public boolean lockedDueToIdleInterval() 1992 { 1993 if (isIdleLocked != ConditionResult.UNDEFINED) 1994 { 1995 if (debugEnabled()) 1996 { 1997 TRACER.debugInfo("Returning stored result of %b for user %s", 1998 (isIdleLocked == ConditionResult.TRUE), userDNString); 1999 } 2000 2001 return isIdleLocked == ConditionResult.TRUE; 2002 } 2003 2004 // Return immediately if this feature is disabled, since the feature is not 2005 // responsible for any state attribute in the entry. 2006 if (passwordPolicy.getIdleLockoutInterval() <= 0) 2007 { 2008 isIdleLocked = ConditionResult.FALSE; 2009 2010 if (debugEnabled()) 2011 { 2012 TRACER.debugInfo("Returning false for user %s because no idle " + 2013 "lockout interval is defined.", userDNString); 2014 } 2015 return false; 2016 } 2017 2018 long lockTime = currentTime - 2019 (1000L * passwordPolicy.getIdleLockoutInterval()); 2020 if(lockTime < 0) lockTime = 0; 2021 2022 long lastLoginTime = getLastLoginTime(); 2023 if (lastLoginTime > lockTime || passwordChangedTime > lockTime) 2024 { 2025 isIdleLocked = ConditionResult.FALSE; 2026 if (debugEnabled()) 2027 { 2028 StringBuilder reason = new StringBuilder(); 2029 if(lastLoginTime > lockTime) 2030 { 2031 reason.append("the last login time is in an acceptable window"); 2032 } 2033 else 2034 { 2035 if(lastLoginTime < 0) 2036 { 2037 reason.append("there is no last login time, but "); 2038 } 2039 reason.append( 2040 "the password changed time is in an acceptable window"); 2041 } 2042 TRACER.debugInfo("Returning false for user %s because %s.", 2043 userDNString, reason.toString()); 2044 } 2045 } 2046 else 2047 { 2048 isIdleLocked = ConditionResult.TRUE; 2049 if (debugEnabled()) 2050 { 2051 String reason = (lastLoginTime < 0) 2052 ? "there is no last login time and the password " + 2053 "changed time is not in an acceptable window" 2054 : "neither last login time nor password " + 2055 "changed time are in an acceptable window"; 2056 TRACER.debugInfo("Returning true for user %s because %s.", 2057 userDNString, reason); 2058 } 2059 } 2060 2061 return isIdleLocked == ConditionResult.TRUE; 2062 } 2063 2064 2065 2066 /** 2067 * Indicates whether the user's password must be changed before any other 2068 * operation can be performed. 2069 * 2070 * @return <CODE>true</CODE> if the user's password must be changed before 2071 * any other operation can be performed. 2072 */ 2073 public boolean mustChangePassword() 2074 { 2075 if(mustChangePassword != ConditionResult.UNDEFINED) 2076 { 2077 if (debugEnabled()) 2078 { 2079 TRACER.debugInfo("Returning stored result of %b for user %s.", 2080 (mustChangePassword == ConditionResult.TRUE), userDNString); 2081 } 2082 2083 return mustChangePassword == ConditionResult.TRUE; 2084 } 2085 2086 // If the password policy doesn't use force change on add or force change on 2087 // reset, or if it forbids the user from changing his password, then return 2088 // false. 2089 // FIXME: the only getter responsible for a state attribute (pwdReset) that 2090 // considers the policy before checking the entry for the presence of the 2091 // attribute. 2092 if (! (passwordPolicy.allowUserPasswordChanges() 2093 && (passwordPolicy.forceChangeOnAdd() 2094 || passwordPolicy.forceChangeOnReset()))) 2095 { 2096 mustChangePassword = ConditionResult.FALSE; 2097 if (debugEnabled()) 2098 { 2099 TRACER.debugInfo("Returning false for user %s because neither " + 2100 "force change on add nor force change on reset is enabled, " + 2101 "or users are not allowed to self-modify passwords.", 2102 userDNString); 2103 2104 } 2105 2106 return false; 2107 } 2108 2109 AttributeType type = 2110 DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_RESET_REQUIRED_LC); 2111 if (type == null) 2112 { 2113 type = DirectoryServer.getDefaultAttributeType( 2114 OP_ATTR_PWPOLICY_RESET_REQUIRED); 2115 } 2116 2117 try 2118 { 2119 mustChangePassword = getBoolean(type); 2120 } 2121 catch (Exception e) 2122 { 2123 if (debugEnabled()) 2124 { 2125 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2126 2127 TRACER.debugWarning("Returning true for user %s because an error " + 2128 "occurred: %s", userDNString, stackTraceToSingleLineString(e)); 2129 } 2130 2131 mustChangePassword = ConditionResult.TRUE; 2132 2133 return true; 2134 } 2135 2136 if(mustChangePassword == ConditionResult.UNDEFINED) 2137 { 2138 mustChangePassword = ConditionResult.FALSE; 2139 if (debugEnabled()) 2140 { 2141 TRACER.debugInfo("Returning %b for user since the attribute \"%s\"" + 2142 " is not present in the entry.", 2143 false, userDNString, OP_ATTR_PWPOLICY_RESET_REQUIRED); 2144 } 2145 2146 return false; 2147 } 2148 2149 if (debugEnabled()) 2150 { 2151 TRACER.debugInfo("Returning %b for user %s.", 2152 (mustChangePassword == ConditionResult.TRUE), userDNString); 2153 } 2154 2155 return mustChangePassword == ConditionResult.TRUE; 2156 } 2157 2158 2159 2160 /** 2161 * Updates the user entry to indicate whether the user's password must be 2162 * changed. 2163 * 2164 * @param mustChangePassword Indicates whether the user's password must be 2165 * changed. 2166 */ 2167 public void setMustChangePassword(boolean mustChangePassword) 2168 { 2169 if (debugEnabled()) 2170 { 2171 TRACER.debugInfo("Updating user %s to set the reset flag to %b", 2172 userDNString, mustChangePassword); 2173 } 2174 2175 if (mustChangePassword == mustChangePassword()) 2176 { 2177 return; // requested state matches current state 2178 } 2179 2180 this.mustChangePassword = 2181 ConditionResult.inverseOf(this.mustChangePassword); 2182 2183 AttributeType type = 2184 DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_RESET_REQUIRED_LC); 2185 if (type == null) 2186 { 2187 type = DirectoryServer.getDefaultAttributeType( 2188 OP_ATTR_PWPOLICY_RESET_REQUIRED); 2189 } 2190 2191 if (mustChangePassword) 2192 { 2193 LinkedHashSet<AttributeValue> values = 2194 new LinkedHashSet<AttributeValue>(1); 2195 values.add(new AttributeValue(type, String.valueOf(true))); 2196 Attribute a = new Attribute(type, OP_ATTR_PWPOLICY_RESET_REQUIRED, 2197 values); 2198 2199 if (updateEntry) 2200 { 2201 ArrayList<Attribute> attrList = new ArrayList<Attribute>(1); 2202 attrList.add(a); 2203 userEntry.putAttribute(type, attrList); 2204 } 2205 else 2206 { 2207 modifications.add(new Modification(ModificationType.REPLACE, a, true)); 2208 } 2209 } 2210 else 2211 { 2212 // erase 2213 if (updateEntry) 2214 { 2215 userEntry.removeAttribute(type); 2216 } 2217 else 2218 { 2219 modifications.add(new Modification(ModificationType.REPLACE, 2220 new Attribute(type), true)); 2221 } 2222 } 2223 } 2224 2225 2226 2227 /** 2228 * Indicates whether the user's account is locked because the password has 2229 * been reset by an administrator but the user did not change the password in 2230 * a timely manner. 2231 * 2232 * @return <CODE>true</CODE> if the user's account is locked because of the 2233 * maximum reset age, or <CODE>false</CODE> if not. 2234 */ 2235 public boolean lockedDueToMaximumResetAge() 2236 { 2237 // This feature is reponsible for neither a state field nor an entry state 2238 // attribute. 2239 if (passwordPolicy.getMaximumPasswordResetAge() <= 0) 2240 { 2241 if (debugEnabled()) 2242 { 2243 TRACER.debugInfo("Returning false for user %s because there is no " + 2244 "maximum reset age.", userDNString); 2245 } 2246 2247 return false; 2248 } 2249 2250 if (! mustChangePassword()) 2251 { 2252 if (debugEnabled()) 2253 { 2254 TRACER.debugInfo("Returning false for user %s because the user's " + 2255 "password has not been reset.", userDNString); 2256 } 2257 2258 return false; 2259 } 2260 2261 long maxResetTime = passwordChangedTime + 2262 (1000L * passwordPolicy.getMaximumPasswordResetAge()); 2263 boolean locked = (maxResetTime < currentTime); 2264 2265 if (debugEnabled()) 2266 { 2267 TRACER.debugInfo("Returning %b for user %s after comparing the " + 2268 "current and max reset times.", locked, userDNString); 2269 } 2270 2271 return locked; 2272 } 2273 2274 2275 2276 /** 2277 * Retrieves the time that the user's password should expire (if the 2278 * expiration is in the future) or did expire (if the expiration was in the 2279 * past). Note that this method should be called after the 2280 * <CODE>lockedDueToMaximumResetAge</CODE> method because grace logins will 2281 * not be allowed in the case that the maximum reset age has passed whereas 2282 * they may be used for expiration due to maximum password age or forced 2283 * change time. 2284 * 2285 * @return The time that the user's password should/did expire, or -1 if it 2286 * should not expire. 2287 */ 2288 public long getPasswordExpirationTime() 2289 { 2290 if (passwordExpirationTime == Long.MIN_VALUE) 2291 { 2292 passwordExpirationTime = Long.MAX_VALUE; 2293 2294 boolean checkWarning = false; 2295 2296 int maxAge = passwordPolicy.getMaximumPasswordAge(); 2297 if (maxAge > 0) 2298 { 2299 long expTime = passwordChangedTime + (1000L*maxAge); 2300 if (expTime < passwordExpirationTime) 2301 { 2302 passwordExpirationTime = expTime; 2303 checkWarning = true; 2304 } 2305 } 2306 2307 int maxResetAge = passwordPolicy.getMaximumPasswordResetAge(); 2308 if (mustChangePassword() && (maxResetAge > 0)) 2309 { 2310 long expTime = passwordChangedTime + (1000L*maxResetAge); 2311 if (expTime < passwordExpirationTime) 2312 { 2313 passwordExpirationTime = expTime; 2314 checkWarning = false; 2315 } 2316 } 2317 2318 long mustChangeTime = passwordPolicy.getRequireChangeByTime(); 2319 if (mustChangeTime > 0) 2320 { 2321 long reqChangeTime = getRequiredChangeTime(); 2322 if ((reqChangeTime != mustChangeTime) && 2323 (mustChangeTime < passwordExpirationTime)) 2324 { 2325 passwordExpirationTime = mustChangeTime; 2326 checkWarning = true; 2327 } 2328 } 2329 2330 if (passwordExpirationTime == Long.MAX_VALUE) 2331 { 2332 passwordExpirationTime = -1; 2333 shouldWarn = ConditionResult.FALSE; 2334 isFirstWarning = ConditionResult.FALSE; 2335 isPasswordExpired = ConditionResult.FALSE; 2336 mayUseGraceLogin = ConditionResult.TRUE; 2337 } 2338 else if (checkWarning) 2339 { 2340 mayUseGraceLogin = ConditionResult.TRUE; 2341 2342 int warningInterval = passwordPolicy.getWarningInterval(); 2343 if (warningInterval > 0) 2344 { 2345 long shouldWarnTime = 2346 passwordExpirationTime - (warningInterval*1000L); 2347 if (shouldWarnTime > currentTime) 2348 { 2349 // The warning time is in the future, so we know the password isn't 2350 // expired. 2351 shouldWarn = ConditionResult.FALSE; 2352 isFirstWarning = ConditionResult.FALSE; 2353 isPasswordExpired = ConditionResult.FALSE; 2354 } 2355 else 2356 { 2357 // We're at least in the warning period, but the password may be 2358 // expired. 2359 long warnedTime = getWarnedTime(); 2360 2361 if (passwordExpirationTime > currentTime) 2362 { 2363 // The password is not expired but we should warn the user. 2364 shouldWarn = ConditionResult.TRUE; 2365 isPasswordExpired = ConditionResult.FALSE; 2366 2367 if (warnedTime < 0) 2368 { 2369 isFirstWarning = ConditionResult.TRUE; 2370 setWarnedTime(); 2371 2372 if (! passwordPolicy.expirePasswordsWithoutWarning()) 2373 { 2374 passwordExpirationTime = 2375 currentTime + (warningInterval*1000L); 2376 } 2377 } 2378 else 2379 { 2380 isFirstWarning = ConditionResult.FALSE; 2381 2382 if (! passwordPolicy.expirePasswordsWithoutWarning()) 2383 { 2384 passwordExpirationTime = warnedTime + (warningInterval*1000L); 2385 } 2386 } 2387 } 2388 else 2389 { 2390 // The expiration time has passed, but we may not actually be 2391 // expired if the user has not yet seen a warning. 2392 if (passwordPolicy.expirePasswordsWithoutWarning()) 2393 { 2394 shouldWarn = ConditionResult.FALSE; 2395 isFirstWarning = ConditionResult.FALSE; 2396 isPasswordExpired = ConditionResult.TRUE; 2397 } 2398 else if (warnedTime > 0) 2399 { 2400 passwordExpirationTime = warnedTime + (warningInterval*1000L); 2401 if (passwordExpirationTime > currentTime) 2402 { 2403 shouldWarn = ConditionResult.TRUE; 2404 isFirstWarning = ConditionResult.FALSE; 2405 isPasswordExpired = ConditionResult.FALSE; 2406 } 2407 else 2408 { 2409 shouldWarn = ConditionResult.FALSE; 2410 isFirstWarning = ConditionResult.FALSE; 2411 isPasswordExpired = ConditionResult.TRUE; 2412 } 2413 } 2414 else 2415 { 2416 shouldWarn = ConditionResult.TRUE; 2417 isFirstWarning = ConditionResult.TRUE; 2418 isPasswordExpired = ConditionResult.FALSE; 2419 passwordExpirationTime = currentTime + (warningInterval*1000L); 2420 } 2421 } 2422 } 2423 } 2424 else 2425 { 2426 // There will never be a warning, and the user's password may be 2427 // expired. 2428 shouldWarn = ConditionResult.FALSE; 2429 isFirstWarning = ConditionResult.FALSE; 2430 2431 if (currentTime > passwordExpirationTime) 2432 { 2433 isPasswordExpired = ConditionResult.TRUE; 2434 } 2435 else 2436 { 2437 isPasswordExpired = ConditionResult.FALSE; 2438 } 2439 } 2440 } 2441 else 2442 { 2443 mayUseGraceLogin = ConditionResult.FALSE; 2444 shouldWarn = ConditionResult.FALSE; 2445 isFirstWarning = ConditionResult.FALSE; 2446 2447 if (passwordExpirationTime < currentTime) 2448 { 2449 isPasswordExpired = ConditionResult.TRUE; 2450 } 2451 else 2452 { 2453 isPasswordExpired = ConditionResult.FALSE; 2454 } 2455 } 2456 } 2457 2458 if (debugEnabled()) 2459 { 2460 TRACER.debugInfo("Returning password expiration time of %d for user " + 2461 "%s.", passwordExpirationTime, userDNString); 2462 } 2463 2464 return passwordExpirationTime; 2465 } 2466 2467 2468 2469 /** 2470 * Indicates whether the user's password is currently expired. 2471 * 2472 * @return <CODE>true</CODE> if the user's password is currently expired, or 2473 * <CODE>false</CODE> if not. 2474 */ 2475 public boolean isPasswordExpired() 2476 { 2477 if ((isPasswordExpired == null) || 2478 (isPasswordExpired == ConditionResult.UNDEFINED)) 2479 { 2480 getPasswordExpirationTime(); 2481 } 2482 2483 return isPasswordExpired == ConditionResult.TRUE; 2484 } 2485 2486 2487 2488 /** 2489 * Indicates whether the user's last password change was within the minimum 2490 * password age. 2491 * 2492 * @return <CODE>true</CODE> if the password minimum age is nonzero, the 2493 * account is not in force-change mode, and the last password change 2494 * was within the minimum age, or <CODE>false</CODE> otherwise. 2495 */ 2496 public boolean isWithinMinimumAge() 2497 { 2498 // This feature is reponsible for neither a state field nor entry state 2499 // attribute. 2500 int minAge = passwordPolicy.getMinimumPasswordAge(); 2501 if (minAge <= 0) 2502 { 2503 // There is no minimum age, so the user isn't in it. 2504 if (debugEnabled()) 2505 { 2506 TRACER.debugInfo("Returning false because there is no minimum age."); 2507 } 2508 2509 return false; 2510 } 2511 else if ((passwordChangedTime + (minAge*1000L)) < currentTime) 2512 { 2513 // It's been long enough since the user changed their password. 2514 if (debugEnabled()) 2515 { 2516 TRACER.debugInfo("Returning false because the minimum age has " + 2517 "expired."); 2518 } 2519 2520 return false; 2521 } 2522 else if (mustChangePassword()) 2523 { 2524 // The user is in a must-change mode, so the minimum age doesn't apply. 2525 if (debugEnabled()) 2526 { 2527 TRACER.debugInfo("Returning false because the account is in a " + 2528 "must-change state."); 2529 } 2530 2531 return false; 2532 } 2533 else 2534 { 2535 // The user is within the minimum age. 2536 if (debugEnabled()) 2537 { 2538 TRACER.debugInfo("Returning true."); 2539 } 2540 2541 return true; 2542 } 2543 } 2544 2545 2546 2547 /** 2548 * Indicates whether the user may use a grace login if the password is expired 2549 * and there is at least one grace login remaining. Note that this does not 2550 * check to see if the user's password is expired, does not verify that there 2551 * are any remaining grace logins, and does not update the set of grace login 2552 * times. 2553 * 2554 * @return <CODE>true</CODE> if the user may use a grace login if the 2555 * password is expired and there is at least one grace login 2556 * remaining, or <CODE>false</CODE> if the user may not use a grace 2557 * login for some reason. 2558 */ 2559 public boolean mayUseGraceLogin() 2560 { 2561 if ((mayUseGraceLogin == null) || 2562 (mayUseGraceLogin == ConditionResult.UNDEFINED)) 2563 { 2564 getPasswordExpirationTime(); 2565 } 2566 2567 return mayUseGraceLogin == ConditionResult.TRUE; 2568 } 2569 2570 2571 2572 /** 2573 * Indicates whether the user should receive a warning notification that the 2574 * password is about to expire. 2575 * 2576 * @return <CODE>true</CODE> if the user should receive a warning 2577 * notification that the password is about to expire, or 2578 * <CODE>false</CODE> if not. 2579 */ 2580 public boolean shouldWarn() 2581 { 2582 if ((shouldWarn == null) || (shouldWarn == ConditionResult.UNDEFINED)) 2583 { 2584 getPasswordExpirationTime(); 2585 } 2586 2587 return shouldWarn == ConditionResult.TRUE; 2588 } 2589 2590 2591 2592 /** 2593 * Indicates whether the warning that the user should receive would be the 2594 * first warning for the user. 2595 * 2596 * @return <CODE>true</CODE> if the warning that should be sent to the user 2597 * would be the first warning, or <CODE>false</CODE> if not. 2598 */ 2599 public boolean isFirstWarning() 2600 { 2601 if ((isFirstWarning == null) || 2602 (isFirstWarning == ConditionResult.UNDEFINED)) 2603 { 2604 getPasswordExpirationTime(); 2605 } 2606 2607 return isFirstWarning == ConditionResult.TRUE; 2608 } 2609 2610 2611 2612 /** 2613 * Retrieves the length of time in seconds until the user's password expires. 2614 * 2615 * @return The length of time in seconds until the user's password expires, 2616 * 0 if the password is currently expired, or -1 if the password 2617 * should not expire. 2618 */ 2619 public int getSecondsUntilExpiration() 2620 { 2621 long expirationTime = getPasswordExpirationTime(); 2622 if (expirationTime < 0) 2623 { 2624 return -1; 2625 } 2626 else if (expirationTime < currentTime) 2627 { 2628 return 0; 2629 } 2630 else 2631 { 2632 return (int) ((expirationTime - currentTime) / 1000); 2633 } 2634 } 2635 2636 2637 2638 /** 2639 * Retrieves the timestamp for the last required change time that the user 2640 * complied with. 2641 * 2642 * @return The timestamp for the last required change time that the user 2643 * complied with, or -1 if the user's password has not been changed 2644 * in compliance with this configuration. 2645 */ 2646 public long getRequiredChangeTime() 2647 { 2648 if (requiredChangeTime != Long.MIN_VALUE) 2649 { 2650 if (debugEnabled()) 2651 { 2652 TRACER.debugInfo("Returning stored required change time of %d for " + 2653 "user %s", requiredChangeTime, userDNString); 2654 } 2655 2656 return requiredChangeTime; 2657 } 2658 2659 AttributeType type = DirectoryServer.getAttributeType( 2660 OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME, true); 2661 2662 try 2663 { 2664 requiredChangeTime = getGeneralizedTime(type); 2665 } 2666 catch (Exception e) 2667 { 2668 if (debugEnabled()) 2669 { 2670 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2671 } 2672 2673 requiredChangeTime = -1; 2674 if (debugEnabled()) 2675 { 2676 TRACER.debugWarning("Returning %d for user %s because an error " + 2677 "occurred: %s", requiredChangeTime, userDNString, 2678 stackTraceToSingleLineString(e)); 2679 } 2680 2681 return requiredChangeTime; 2682 } 2683 2684 if (debugEnabled()) 2685 { 2686 TRACER.debugInfo("Returning required change time of %d for user %s", 2687 requiredChangeTime, userDNString); 2688 } 2689 2690 return requiredChangeTime; 2691 } 2692 2693 2694 2695 /** 2696 * Updates the user entry with a timestamp indicating that the password has 2697 * been changed in accordance with the require change time. 2698 */ 2699 public void setRequiredChangeTime() 2700 { 2701 long requiredChangeByTimePolicy = passwordPolicy.getRequireChangeByTime(); 2702 if (requiredChangeByTimePolicy > 0) 2703 { 2704 setRequiredChangeTime(requiredChangeByTimePolicy); 2705 } 2706 } 2707 2708 2709 2710 /** 2711 * Updates the user entry with a timestamp indicating that the password has 2712 * been changed in accordance with the require change time. 2713 * 2714 * @param requiredChangeTime The timestamp to use for the required change 2715 * time value. 2716 */ 2717 public void setRequiredChangeTime(long requiredChangeTime) 2718 { 2719 if (debugEnabled()) 2720 { 2721 TRACER.debugInfo("Updating required change time for user %s", 2722 userDNString); 2723 } 2724 2725 if (getRequiredChangeTime() != requiredChangeTime) 2726 { 2727 AttributeType type = DirectoryServer.getAttributeType( 2728 OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME, true); 2729 2730 LinkedHashSet<AttributeValue> values = 2731 new LinkedHashSet<AttributeValue>(1); 2732 String timeValue = GeneralizedTimeSyntax.format(requiredChangeTime); 2733 values.add(new AttributeValue(type, timeValue)); 2734 2735 Attribute a = new Attribute(type, 2736 OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME, 2737 values); 2738 2739 if (updateEntry) 2740 { 2741 ArrayList<Attribute> attrList = new ArrayList<Attribute>(1); 2742 attrList.add(a); 2743 userEntry.putAttribute(type, attrList); 2744 } 2745 else 2746 { 2747 modifications.add(new Modification(ModificationType.REPLACE, a, true)); 2748 } 2749 } 2750 } 2751 2752 2753 2754 /** 2755 * Updates the user entry to remove any timestamp indicating that the password 2756 * has been changed in accordance with the required change time. 2757 */ 2758 public void clearRequiredChangeTime() 2759 { 2760 if (debugEnabled()) 2761 { 2762 TRACER.debugInfo("Clearing required change time for user %s", 2763 userDNString); 2764 } 2765 2766 AttributeType type = DirectoryServer.getAttributeType( 2767 OP_ATTR_PWPOLICY_CHANGED_BY_REQUIRED_TIME, true); 2768 if (updateEntry) 2769 { 2770 userEntry.removeAttribute(type); 2771 } 2772 else 2773 { 2774 modifications.add(new Modification(ModificationType.REPLACE, 2775 new Attribute(type), true)); 2776 } 2777 } 2778 2779 2780 2781 /** 2782 * Retrieves the time that the user was first warned about an upcoming 2783 * expiration. 2784 * 2785 * @return The time that the user was first warned about an upcoming 2786 * expiration, or -1 if the user has not been warned. 2787 */ 2788 public long getWarnedTime() 2789 { 2790 if (warnedTime == Long.MIN_VALUE) 2791 { 2792 AttributeType type = 2793 DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_WARNED_TIME, true); 2794 try 2795 { 2796 warnedTime = getGeneralizedTime(type); 2797 } 2798 catch (Exception e) 2799 { 2800 if (debugEnabled()) 2801 { 2802 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2803 } 2804 2805 if (debugEnabled()) 2806 { 2807 TRACER.debugWarning("Unable to decode the warned time for user %s: " + 2808 "%s", userDNString, stackTraceToSingleLineString(e)); 2809 } 2810 2811 warnedTime = -1; 2812 } 2813 } 2814 2815 2816 if (debugEnabled()) 2817 { 2818 TRACER.debugInfo("Returning a warned time of %d for user %s", 2819 warnedTime, userDNString); 2820 } 2821 2822 return warnedTime; 2823 } 2824 2825 2826 2827 /** 2828 * Updates the user entry to set the warned time to the current time. 2829 */ 2830 public void setWarnedTime() 2831 { 2832 setWarnedTime(currentTime); 2833 } 2834 2835 2836 2837 /** 2838 * Updates the user entry to set the warned time to the specified time. This 2839 * method should generally only be used for testing purposes, since the 2840 * variant that uses the current time is preferred almost everywhere else. 2841 * 2842 * @param warnedTime The value to use for the warned time. 2843 */ 2844 public void setWarnedTime(long warnedTime) 2845 { 2846 long warnTime = getWarnedTime(); 2847 if (warnTime == warnedTime) 2848 { 2849 if (debugEnabled()) 2850 { 2851 TRACER.debugInfo("Not updating warned time for user %s because " + 2852 "the warned time is the same as the specified time.", 2853 userDNString); 2854 } 2855 2856 return; 2857 } 2858 2859 this.warnedTime = warnedTime; 2860 2861 AttributeType type = 2862 DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_WARNED_TIME, true); 2863 LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(1); 2864 values.add(GeneralizedTimeSyntax.createGeneralizedTimeValue(currentTime)); 2865 2866 Attribute a = new Attribute(type, OP_ATTR_PWPOLICY_WARNED_TIME, values); 2867 2868 if (updateEntry) 2869 { 2870 ArrayList<Attribute> attrList = new ArrayList<Attribute>(1); 2871 attrList.add(a); 2872 userEntry.putAttribute(type, attrList); 2873 } 2874 else 2875 { 2876 modifications.add(new Modification(ModificationType.REPLACE, a, true)); 2877 } 2878 2879 if (debugEnabled()) 2880 { 2881 TRACER.debugInfo("Updated the warned time for user %s", userDNString); 2882 } 2883 } 2884 2885 2886 2887 /** 2888 * Updates the user entry to clear the warned time. 2889 */ 2890 public void clearWarnedTime() 2891 { 2892 if (debugEnabled()) 2893 { 2894 TRACER.debugInfo("Clearing warned time for user %s", userDNString); 2895 } 2896 2897 if (getWarnedTime() < 0) 2898 { 2899 return; 2900 } 2901 warnedTime = -1; 2902 2903 AttributeType type = 2904 DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_WARNED_TIME, true); 2905 if (updateEntry) 2906 { 2907 userEntry.removeAttribute(type); 2908 } 2909 else 2910 { 2911 Attribute a = new Attribute(type); 2912 modifications.add(new Modification(ModificationType.REPLACE, a, true)); 2913 } 2914 2915 if (debugEnabled()) 2916 { 2917 TRACER.debugInfo("Cleared the warned time for user %s", userDNString); 2918 } 2919 } 2920 2921 2922 2923 /** 2924 * Retrieves the times that the user has authenticated to the server using a 2925 * grace login. 2926 * 2927 * @return The times that the user has authenticated to the server using a 2928 * grace login. 2929 */ 2930 public List<Long> getGraceLoginTimes() 2931 { 2932 if (graceLoginTimes == null) 2933 { 2934 AttributeType type = DirectoryServer.getAttributeType( 2935 OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME_LC); 2936 if (type == null) 2937 { 2938 type = DirectoryServer.getDefaultAttributeType( 2939 OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME); 2940 } 2941 2942 try 2943 { 2944 graceLoginTimes = getGeneralizedTimes(type); 2945 } 2946 catch (Exception e) 2947 { 2948 if (debugEnabled()) 2949 { 2950 TRACER.debugCaught(DebugLogLevel.ERROR, e); 2951 } 2952 2953 if (debugEnabled()) 2954 { 2955 TRACER.debugWarning("Error while processing grace login times " + 2956 "for user %s: %s", 2957 userDNString, stackTraceToSingleLineString(e)); 2958 } 2959 2960 graceLoginTimes = new ArrayList<Long>(); 2961 2962 if (updateEntry) 2963 { 2964 userEntry.removeAttribute(type); 2965 } 2966 else 2967 { 2968 modifications.add(new Modification(ModificationType.REPLACE, 2969 new Attribute(type), true)); 2970 } 2971 } 2972 } 2973 2974 2975 if (debugEnabled()) 2976 { 2977 TRACER.debugInfo("Returning grace login times for user %s", 2978 userDNString); 2979 } 2980 2981 return graceLoginTimes; 2982 } 2983 2984 2985 2986 /** 2987 * Retrieves the number of grace logins that the user has left. 2988 * 2989 * @return The number of grace logins that the user has left, or -1 if grace 2990 * logins are not allowed. 2991 */ 2992 public int getGraceLoginsRemaining() 2993 { 2994 int maxGraceLogins = passwordPolicy.getGraceLoginCount(); 2995 if (maxGraceLogins <= 0) 2996 { 2997 return -1; 2998 } 2999 3000 List<Long> graceLoginTimes = getGraceLoginTimes(); 3001 return maxGraceLogins - graceLoginTimes.size(); 3002 } 3003 3004 3005 3006 /** 3007 * Updates the set of grace login times for the user to include the current 3008 * time. 3009 */ 3010 public void updateGraceLoginTimes() 3011 { 3012 if (debugEnabled()) 3013 { 3014 TRACER.debugInfo("Updating grace login times for user %s", 3015 userDNString); 3016 } 3017 3018 List<Long> graceTimes = getGraceLoginTimes(); 3019 long highestGraceTime = -1; 3020 for (Long l : graceTimes) 3021 { 3022 highestGraceTime = Math.max(l, highestGraceTime); 3023 } 3024 3025 if (highestGraceTime >= currentTime) 3026 { 3027 highestGraceTime++; 3028 } 3029 else 3030 { 3031 highestGraceTime = currentTime; 3032 } 3033 graceTimes.add(highestGraceTime); // graceTimes == this.graceLoginTimes 3034 3035 AttributeType type = 3036 DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME_LC); 3037 if (type == null) 3038 { 3039 type = DirectoryServer.getDefaultAttributeType( 3040 OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME); 3041 } 3042 3043 if (updateEntry) 3044 { 3045 LinkedHashSet<AttributeValue> values = 3046 new LinkedHashSet<AttributeValue>(graceTimes.size()); 3047 for (Long l : graceTimes) 3048 { 3049 values.add(new AttributeValue(type, GeneralizedTimeSyntax.format(l))); 3050 } 3051 3052 Attribute a = new Attribute(type, OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME, 3053 values); 3054 ArrayList<Attribute> attrList = new ArrayList<Attribute>(1); 3055 attrList.add(a); 3056 3057 userEntry.putAttribute(type, attrList); 3058 } 3059 else 3060 { 3061 LinkedHashSet<AttributeValue> addValues = 3062 new LinkedHashSet<AttributeValue>(1); 3063 addValues.add(new AttributeValue(type, 3064 GeneralizedTimeSyntax.format(highestGraceTime))); 3065 Attribute addAttr = new Attribute(type, OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME, 3066 addValues); 3067 3068 modifications.add(new Modification(ModificationType.ADD, addAttr, true)); 3069 } 3070 } 3071 3072 3073 3074 /** 3075 * Specifies the set of grace login use times for the associated user. If 3076 * the provided list is empty or {@code null}, then the set will be cleared. 3077 * 3078 * @param graceLoginTimes The grace login use times for the associated user. 3079 */ 3080 public void setGraceLoginTimes(List<Long> graceLoginTimes) 3081 { 3082 if ((graceLoginTimes == null) || graceLoginTimes.isEmpty()) 3083 { 3084 clearGraceLoginTimes(); 3085 return; 3086 } 3087 3088 if (debugEnabled()) 3089 { 3090 TRACER.debugInfo("Updating grace login times for user %s", 3091 userDNString); 3092 } 3093 3094 AttributeType type = 3095 DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME_LC, 3096 true); 3097 LinkedHashSet<AttributeValue> values = 3098 new LinkedHashSet<AttributeValue>(graceLoginTimes.size()); 3099 for (Long l : graceLoginTimes) 3100 { 3101 values.add(new AttributeValue(type, GeneralizedTimeSyntax.format(l))); 3102 } 3103 Attribute a = 3104 new Attribute(type, OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME, values); 3105 3106 if (updateEntry) 3107 { 3108 ArrayList<Attribute> attrList = new ArrayList<Attribute>(1); 3109 attrList.add(a); 3110 3111 userEntry.putAttribute(type, attrList); 3112 } 3113 else 3114 { 3115 modifications.add(new Modification(ModificationType.REPLACE, a, true)); 3116 } 3117 } 3118 3119 3120 3121 /** 3122 * Updates the user entry to remove any record of previous grace logins. 3123 */ 3124 public void clearGraceLoginTimes() 3125 { 3126 if (debugEnabled()) 3127 { 3128 TRACER.debugInfo("Clearing grace login times for user %s", 3129 userDNString); 3130 } 3131 3132 List<Long> graceTimes = getGraceLoginTimes(); 3133 if (graceTimes.isEmpty()) 3134 { 3135 return; 3136 } 3137 graceTimes.clear(); // graceTimes == this.graceLoginTimes 3138 3139 AttributeType type = 3140 DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME_LC); 3141 if (type == null) 3142 { 3143 type = DirectoryServer.getDefaultAttributeType( 3144 OP_ATTR_PWPOLICY_GRACE_LOGIN_TIME); 3145 } 3146 3147 if (updateEntry) 3148 { 3149 userEntry.removeAttribute(type); 3150 } 3151 else 3152 { 3153 modifications.add(new Modification(ModificationType.REPLACE, 3154 new Attribute(type), true)); 3155 } 3156 } 3157 3158 3159 3160 /** 3161 * Retrieves a list of the clear-text passwords for the user. If the user 3162 * does not have any passwords in the clear, then the list will be empty. 3163 * 3164 * @return A list of the clear-text passwords for the user. 3165 */ 3166 public List<ByteString> getClearPasswords() 3167 { 3168 LinkedList<ByteString> clearPasswords = new LinkedList<ByteString>(); 3169 3170 List<Attribute> attrList = 3171 userEntry.getAttribute(passwordPolicy.getPasswordAttribute()); 3172 3173 if (attrList == null) 3174 { 3175 return clearPasswords; 3176 } 3177 3178 for (Attribute a : attrList) 3179 { 3180 boolean usesAuthPasswordSyntax = passwordPolicy.usesAuthPasswordSyntax(); 3181 3182 for (AttributeValue v : a.getValues()) 3183 { 3184 try 3185 { 3186 StringBuilder[] pwComponents; 3187 if (usesAuthPasswordSyntax) 3188 { 3189 pwComponents = 3190 AuthPasswordSyntax.decodeAuthPassword(v.getStringValue()); 3191 } 3192 else 3193 { 3194 String[] userPwComponents = 3195 UserPasswordSyntax.decodeUserPassword(v.getStringValue()); 3196 pwComponents = new StringBuilder[userPwComponents.length]; 3197 for (int i = 0; i < userPwComponents.length; ++i) 3198 { 3199 pwComponents[i] = new StringBuilder(userPwComponents[i]); 3200 } 3201 } 3202 3203 String schemeName = pwComponents[0].toString(); 3204 PasswordStorageScheme scheme = (usesAuthPasswordSyntax) 3205 ? DirectoryServer.getAuthPasswordStorageScheme(schemeName) 3206 : DirectoryServer.getPasswordStorageScheme(schemeName); 3207 if (scheme == null) 3208 { 3209 if (debugEnabled()) 3210 { 3211 TRACER.debugWarning("User entry %s contains a password with " + 3212 "scheme %s that is not defined in the server.", 3213 userDNString, schemeName); 3214 } 3215 3216 continue; 3217 } 3218 3219 if (scheme.isReversible()) 3220 { 3221 ByteString clearValue = (usesAuthPasswordSyntax) 3222 ? scheme.getAuthPasswordPlaintextValue( 3223 pwComponents[1].toString(), 3224 pwComponents[2].toString()) 3225 : scheme.getPlaintextValue( 3226 new ASN1OctetString(pwComponents[1].toString())); 3227 clearPasswords.add(clearValue); 3228 } 3229 } 3230 catch (Exception e) 3231 { 3232 if (debugEnabled()) 3233 { 3234 TRACER.debugCaught(DebugLogLevel.ERROR, e); 3235 } 3236 3237 if (debugEnabled()) 3238 { 3239 TRACER.debugWarning("Cannot get clear password value foruser %s: " + 3240 "%s", userDNString, e); 3241 } 3242 } 3243 } 3244 } 3245 3246 return clearPasswords; 3247 } 3248 3249 3250 3251 /** 3252 * Indicates whether the provided password value matches any of the stored 3253 * passwords in the user entry. 3254 * 3255 * @param password The user-provided password to verify. 3256 * 3257 * @return <CODE>true</CODE> if the provided password matches any of the 3258 * stored password values, or <CODE>false</CODE> if not. 3259 */ 3260 public boolean passwordMatches(ByteString password) 3261 { 3262 List<Attribute> attrList = 3263 userEntry.getAttribute(passwordPolicy.getPasswordAttribute()); 3264 if ((attrList == null) || attrList.isEmpty()) 3265 { 3266 if (debugEnabled()) 3267 { 3268 TRACER.debugInfo("Returning false because user %s does not have " + 3269 "any values for password attribute %s", userDNString, 3270 passwordPolicy.getPasswordAttribute().getNameOrOID()); 3271 } 3272 3273 return false; 3274 } 3275 3276 for (Attribute a : attrList) 3277 { 3278 boolean usesAuthPasswordSyntax = passwordPolicy.usesAuthPasswordSyntax(); 3279 3280 for (AttributeValue v : a.getValues()) 3281 { 3282 try 3283 { 3284 StringBuilder[] pwComponents; 3285 if (usesAuthPasswordSyntax) 3286 { 3287 pwComponents = 3288 AuthPasswordSyntax.decodeAuthPassword(v.getStringValue()); 3289 } 3290 else 3291 { 3292 String[] userPwComponents = 3293 UserPasswordSyntax.decodeUserPassword(v.getStringValue()); 3294 pwComponents = new StringBuilder[userPwComponents.length]; 3295 for (int i = 0; i < userPwComponents.length; ++i) 3296 { 3297 pwComponents[i] = new StringBuilder(userPwComponents[i]); 3298 } 3299 } 3300 3301 String schemeName = pwComponents[0].toString(); 3302 PasswordStorageScheme scheme = (usesAuthPasswordSyntax) 3303 ? DirectoryServer.getAuthPasswordStorageScheme(schemeName) 3304 : DirectoryServer.getPasswordStorageScheme(schemeName); 3305 if (scheme == null) 3306 { 3307 if (debugEnabled()) 3308 { 3309 TRACER.debugWarning("User entry %s contains a password with " + 3310 "scheme %s that is not defined in the server.", 3311 userDNString, schemeName); 3312 } 3313 3314 continue; 3315 } 3316 3317 boolean passwordMatches = (usesAuthPasswordSyntax) 3318 ? scheme.authPasswordMatches(password, 3319 pwComponents[1].toString(), 3320 pwComponents[2].toString()) 3321 : scheme.passwordMatches(password, 3322 new ASN1OctetString(pwComponents[1].toString())); 3323 if (passwordMatches) 3324 { 3325 if (debugEnabled()) 3326 { 3327 TRACER.debugInfo("Returning true for user %s because the " + 3328 "provided password matches a value encoded with scheme %s", 3329 userDNString, schemeName); 3330 } 3331 3332 return true; 3333 } 3334 } 3335 catch (Exception e) 3336 { 3337 if (debugEnabled()) 3338 { 3339 TRACER.debugCaught(DebugLogLevel.ERROR, e); 3340 } 3341 3342 if (debugEnabled()) 3343 { 3344 TRACER.debugWarning("An error occurred while attempting to " + 3345 "process a password value for user %s: %s", 3346 userDNString, stackTraceToSingleLineString(e)); 3347 } 3348 } 3349 } 3350 } 3351 3352 // If we've gotten here, then we couldn't find a match. 3353 if (debugEnabled()) 3354 { 3355 TRACER.debugInfo("Returning false because the provided password does " + 3356 "not match any of the stored password values for user %s", 3357 userDNString); 3358 } 3359 3360 return false; 3361 } 3362 3363 3364 3365 /** 3366 * Indicates whether the provided password value is pre-encoded. 3367 * 3368 * @param passwordValue The value for which to make the determination. 3369 * 3370 * @return <CODE>true</CODE> if the provided password value is pre-encoded, 3371 * or <CODE>false</CODE> if it is not. 3372 */ 3373 public boolean passwordIsPreEncoded(ByteString passwordValue) 3374 { 3375 if (passwordPolicy.usesAuthPasswordSyntax()) 3376 { 3377 return AuthPasswordSyntax.isEncoded(passwordValue); 3378 } 3379 else 3380 { 3381 return UserPasswordSyntax.isEncoded(passwordValue); 3382 } 3383 } 3384 3385 3386 3387 /** 3388 * Encodes the provided password using the default storage schemes (using the 3389 * appropriate syntax for the password attribute). 3390 * 3391 * @param password The password to be encoded. 3392 * 3393 * @return The password encoded using the default schemes. 3394 * 3395 * @throws DirectoryException If a problem occurs while attempting to encode 3396 * the password. 3397 */ 3398 public List<ByteString> encodePassword(ByteString password) 3399 throws DirectoryException 3400 { 3401 List<PasswordStorageScheme> schemes = 3402 passwordPolicy.getDefaultStorageSchemes(); 3403 List<ByteString> encodedPasswords = 3404 new ArrayList<ByteString>(schemes.size()); 3405 3406 if (passwordPolicy.usesAuthPasswordSyntax()) 3407 { 3408 for (PasswordStorageScheme s : schemes) 3409 { 3410 encodedPasswords.add(s.encodeAuthPassword(password)); 3411 } 3412 } 3413 else 3414 { 3415 for (PasswordStorageScheme s : schemes) 3416 { 3417 encodedPasswords.add(s.encodePasswordWithScheme(password)); 3418 } 3419 } 3420 3421 return encodedPasswords; 3422 } 3423 3424 3425 3426 /** 3427 * Indicates whether the provided password appears to be acceptable according 3428 * to the password validators. 3429 * 3430 * @param operation The operation that provided the password. 3431 * @param userEntry The user entry in which the password is used. 3432 * @param newPassword The password to be validated. 3433 * @param currentPasswords The set of clear-text current passwords for the 3434 * user (this may be a subset if not all of them are 3435 * available in the clear, or empty if none of them 3436 * are available in the clear). 3437 * @param invalidReason A buffer that may be used to hold the invalid 3438 * reason if the password is rejected. 3439 * 3440 * @return <CODE>true</CODE> if the password is acceptable for use, or 3441 * <CODE>false</CODE> if it is not. 3442 */ 3443 public boolean passwordIsAcceptable(Operation operation, Entry userEntry, 3444 ByteString newPassword, 3445 Set<ByteString> currentPasswords, 3446 MessageBuilder invalidReason) 3447 { 3448 for (DN validatorDN : passwordPolicy.getPasswordValidators().keySet()) 3449 { 3450 PasswordValidator<? extends PasswordValidatorCfg> validator = 3451 passwordPolicy.getPasswordValidators().get(validatorDN); 3452 3453 if (! validator.passwordIsAcceptable(newPassword, currentPasswords, 3454 operation, userEntry, invalidReason)) 3455 { 3456 if (debugEnabled()) 3457 { 3458 TRACER.debugInfo("The password provided for user %s failed " + 3459 "the %s password validator.", 3460 userDNString, validatorDN.toString()); 3461 } 3462 3463 return false; 3464 } 3465 else 3466 { 3467 if (debugEnabled()) 3468 { 3469 TRACER.debugInfo("The password provided for user %s passed " + 3470 "the %s password validator.", 3471 userDNString, validatorDN.toString()); 3472 } 3473 } 3474 } 3475 3476 return true; 3477 } 3478 3479 3480 3481 /** 3482 * Performs any processing that may be necessary to remove deprecated storage 3483 * schemes from the user's entry that match the provided password and 3484 * re-encodes them using the default schemes. 3485 * 3486 * @param password The clear-text password provided by the user. 3487 */ 3488 public void handleDeprecatedStorageSchemes(ByteString password) 3489 { 3490 if (passwordPolicy.getDefaultStorageSchemes().isEmpty()) 3491 { 3492 if (debugEnabled()) 3493 { 3494 TRACER.debugInfo("Doing nothing for user %s because no " + 3495 "deprecated storage schemes have been defined.", userDNString); 3496 } 3497 3498 return; 3499 } 3500 3501 3502 AttributeType type = passwordPolicy.getPasswordAttribute(); 3503 List<Attribute> attrList = userEntry.getAttribute(type); 3504 if ((attrList == null) || attrList.isEmpty()) 3505 { 3506 if (debugEnabled()) 3507 { 3508 TRACER.debugInfo("Doing nothing for entry %s because no password " + 3509 "values were found.", userDNString); 3510 } 3511 3512 return; 3513 } 3514 3515 3516 HashSet<String> existingDefaultSchemes = new HashSet<String>(); 3517 LinkedHashSet<AttributeValue> removedValues = 3518 new LinkedHashSet<AttributeValue>(); 3519 LinkedHashSet<AttributeValue> updatedValues = 3520 new LinkedHashSet<AttributeValue>(); 3521 3522 boolean usesAuthPasswordSyntax = passwordPolicy.usesAuthPasswordSyntax(); 3523 3524 for (Attribute a : attrList) 3525 { 3526 Iterator<AttributeValue> iterator = a.getValues().iterator(); 3527 while (iterator.hasNext()) 3528 { 3529 AttributeValue v = iterator.next(); 3530 3531 try 3532 { 3533 StringBuilder[] pwComponents; 3534 if (usesAuthPasswordSyntax) 3535 { 3536 pwComponents = 3537 AuthPasswordSyntax.decodeAuthPassword(v.getStringValue()); 3538 } 3539 else 3540 { 3541 String[] userPwComponents = 3542 UserPasswordSyntax.decodeUserPassword(v.getStringValue()); 3543 pwComponents = new StringBuilder[userPwComponents.length]; 3544 for (int i = 0; i < userPwComponents.length; ++i) 3545 { 3546 pwComponents[i] = new StringBuilder(userPwComponents[i]); 3547 } 3548 } 3549 3550 String schemeName = pwComponents[0].toString(); 3551 PasswordStorageScheme scheme = (usesAuthPasswordSyntax) 3552 ? DirectoryServer.getAuthPasswordStorageScheme(schemeName) 3553 : DirectoryServer.getPasswordStorageScheme(schemeName); 3554 if (scheme == null) 3555 { 3556 if (debugEnabled()) 3557 { 3558 TRACER.debugWarning("Skipping password value for user %s " + 3559 "because the associated storage scheme %s is not " + 3560 "configured for use.", userDNString, schemeName); 3561 } 3562 3563 continue; 3564 } 3565 3566 boolean passwordMatches = (usesAuthPasswordSyntax) 3567 ? scheme.authPasswordMatches(password, 3568 pwComponents[1].toString(), 3569 pwComponents[2].toString()) 3570 : scheme.passwordMatches(password, 3571 new ASN1OctetString(pwComponents[1].toString())); 3572 if (passwordMatches) 3573 { 3574 if (passwordPolicy.isDefaultStorageScheme(schemeName)) 3575 { 3576 existingDefaultSchemes.add(schemeName); 3577 updatedValues.add(v); 3578 } 3579 else if (passwordPolicy.isDeprecatedStorageScheme(schemeName)) 3580 { 3581 if (debugEnabled()) 3582 { 3583 TRACER.debugInfo("Marking password with scheme %s for " + 3584 "removal from user entry %s.", schemeName, userDNString); 3585 } 3586 3587 iterator.remove(); 3588 removedValues.add(v); 3589 } 3590 else 3591 { 3592 updatedValues.add(v); 3593 } 3594 } 3595 } 3596 catch (Exception e) 3597 { 3598 if (debugEnabled()) 3599 { 3600 TRACER.debugCaught(DebugLogLevel.ERROR, e); 3601 3602 TRACER.debugWarning("Skipping password value for user %s because " + 3603 "an error occurred while attempting to decode it based on " + 3604 "the user password syntax: %s", 3605 userDNString, stackTraceToSingleLineString(e)); 3606 } 3607 } 3608 } 3609 } 3610 3611 if (removedValues.isEmpty()) 3612 { 3613 if (debugEnabled()) 3614 { 3615 TRACER.debugInfo("User entry %s does not have any password values " + 3616 "encoded using deprecated schemes.", userDNString); 3617 } 3618 3619 return; 3620 } 3621 3622 LinkedHashSet<AttributeValue> addedValues = new 3623 LinkedHashSet<AttributeValue>(); 3624 for (PasswordStorageScheme s : 3625 passwordPolicy.getDefaultStorageSchemes()) 3626 { 3627 if (! existingDefaultSchemes.contains( 3628 toLowerCase(s.getStorageSchemeName()))) 3629 { 3630 try 3631 { 3632 ByteString encodedPassword = (usesAuthPasswordSyntax) 3633 ? s.encodeAuthPassword(password) 3634 : s.encodePasswordWithScheme(password); 3635 AttributeValue v = new AttributeValue(type, encodedPassword); 3636 addedValues.add(v); 3637 updatedValues.add(v); 3638 } 3639 catch (Exception e) 3640 { 3641 if (debugEnabled()) 3642 { 3643 TRACER.debugCaught(DebugLogLevel.ERROR, e); 3644 } 3645 3646 if (debugEnabled()) 3647 { 3648 TRACER.debugWarning("Unable to encode password for user %s using " + 3649 "default scheme %s: %s", 3650 userDNString, s.getStorageSchemeName(), 3651 stackTraceToSingleLineString(e)); 3652 } 3653 } 3654 } 3655 } 3656 3657 if (updatedValues.isEmpty()) 3658 { 3659 if (debugEnabled()) 3660 { 3661 TRACER.debugWarning("Not updating user entry %s because removing " + 3662 "deprecated schemes would leave the user without a password.", 3663 userDNString); 3664 } 3665 3666 return; 3667 } 3668 3669 if (updateEntry) 3670 { 3671 ArrayList<Attribute> newList = new ArrayList<Attribute>(1); 3672 newList.add(new Attribute(type, type.getNameOrOID(), updatedValues)); 3673 userEntry.putAttribute(type, newList); 3674 } 3675 else 3676 { 3677 Attribute a = new Attribute(type, type.getNameOrOID(), removedValues); 3678 modifications.add(new Modification(ModificationType.DELETE, a, true)); 3679 3680 if (! addedValues.isEmpty()) 3681 { 3682 Attribute a2 = new Attribute(type, type.getNameOrOID(), addedValues); 3683 modifications.add(new Modification(ModificationType.ADD, a2, true)); 3684 } 3685 } 3686 3687 if (debugEnabled()) 3688 { 3689 TRACER.debugInfo("Updating user entry %s to replace password values " + 3690 "encoded with deprecated schemes with values encoded " + 3691 "with the default schemes.", userDNString); 3692 } 3693 } 3694 3695 3696 3697 /** 3698 * Indicates whether password history information should be matained for this 3699 * user. 3700 * 3701 * @return {@code true} if password history information should be maintained 3702 * for this user, or {@code false} if not. 3703 */ 3704 public boolean maintainHistory() 3705 { 3706 return ((passwordPolicy.getPasswordHistoryCount() > 0) || 3707 (passwordPolicy.getPasswordHistoryDuration() > 0)); 3708 } 3709 3710 3711 3712 /** 3713 * Indicates whether the provided password is equal to any of the current 3714 * passwords, or any of the passwords in the history. 3715 * 3716 * @param password The password for which to make the determination. 3717 * 3718 * @return {@code true} if the provided password is equal to any of the 3719 * current passwords or any of the passwords in the history, or 3720 * {@code false} if not. 3721 */ 3722 public boolean isPasswordInHistory(ByteString password) 3723 { 3724 if (! maintainHistory()) 3725 { 3726 if (debugEnabled()) 3727 { 3728 TRACER.debugInfo("Returning false because password history " + 3729 "checking is disabled."); 3730 } 3731 3732 // Password history checking is disabled, so we don't care if it is in the 3733 // list or not. 3734 return false; 3735 } 3736 3737 3738 // Check to see if the provided password is equal to any of the current 3739 // passwords. If so, then we'll consider it to be in the history. 3740 if (passwordMatches(password)) 3741 { 3742 if (debugEnabled()) 3743 { 3744 TRACER.debugInfo("Returning true because the provided password " + 3745 "is currently in use."); 3746 } 3747 3748 return true; 3749 } 3750 3751 3752 // Get the attribute containing the history and check to see if any of the 3753 // values is equal to the provided password. However, first prune the list 3754 // by size and duration if necessary. 3755 TreeMap<Long,AttributeValue> historyMap = getSortedHistoryValues(null); 3756 3757 int historyCount = passwordPolicy.getPasswordHistoryCount(); 3758 if ((historyCount > 0) && (historyMap.size() > historyCount)) 3759 { 3760 int numToDelete = historyMap.size() - historyCount; 3761 Iterator<Long> iterator = historyMap.keySet().iterator(); 3762 while ((iterator.hasNext()) && (numToDelete > 0)) 3763 { 3764 iterator.next(); 3765 iterator.remove(); 3766 numToDelete--; 3767 } 3768 } 3769 3770 int historyDuration = passwordPolicy.getPasswordHistoryDuration(); 3771 if (historyDuration > 0) 3772 { 3773 long retainDate = currentTime - (1000 * historyDuration); 3774 Iterator<Long> iterator = historyMap.keySet().iterator(); 3775 while (iterator.hasNext()) 3776 { 3777 long historyDate = iterator.next(); 3778 if (historyDate < retainDate) 3779 { 3780 iterator.remove(); 3781 } 3782 else 3783 { 3784 break; 3785 } 3786 } 3787 } 3788 3789 for (AttributeValue v : historyMap.values()) 3790 { 3791 if (historyValueMatches(password, v)) 3792 { 3793 if (debugEnabled()) 3794 { 3795 TRACER.debugInfo("Returning true because the password is in " + 3796 "the history."); 3797 } 3798 3799 return true; 3800 } 3801 } 3802 3803 3804 // If we've gotten here, then the password isn't in the history. 3805 if (debugEnabled()) 3806 { 3807 TRACER.debugInfo("Returning false because the password isn't in the " + 3808 "history."); 3809 } 3810 3811 return false; 3812 } 3813 3814 3815 3816 /** 3817 * Gets a sorted list of the password history values contained in the user's 3818 * entry. The values will be sorted by timestamp. 3819 * 3820 * @param removeAttrs A list into which any values will be placed that could 3821 * not be properly decoded. It may be {@code null} if 3822 * this is not needed. 3823 */ 3824 private TreeMap<Long,AttributeValue> getSortedHistoryValues(List<Attribute> 3825 removeAttrs) 3826 { 3827 TreeMap<Long,AttributeValue> historyMap = 3828 new TreeMap<Long,AttributeValue>(); 3829 AttributeType historyType = 3830 DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_HISTORY_LC, true); 3831 List<Attribute> attrList = userEntry.getAttribute(historyType); 3832 if (attrList != null) 3833 { 3834 for (Attribute a : attrList) 3835 { 3836 for (AttributeValue v : a.getValues()) 3837 { 3838 String histStr = v.getStringValue(); 3839 int hashPos = histStr.indexOf('#'); 3840 if (hashPos <= 0) 3841 { 3842 if (debugEnabled()) 3843 { 3844 TRACER.debugInfo("Found value " + histStr + " in the " + 3845 "history with no timestamp. Marking it " + 3846 "for removal."); 3847 } 3848 3849 LinkedHashSet<AttributeValue> values = 3850 new LinkedHashSet<AttributeValue>(1); 3851 values.add(v); 3852 if (removeAttrs != null) 3853 { 3854 removeAttrs.add(new Attribute(a.getAttributeType(), a.getName(), 3855 values)); 3856 } 3857 } 3858 else 3859 { 3860 try 3861 { 3862 long timestamp = 3863 GeneralizedTimeSyntax.decodeGeneralizedTimeValue( 3864 new ASN1OctetString(histStr.substring(0, hashPos))); 3865 historyMap.put(timestamp, v); 3866 } 3867 catch (Exception e) 3868 { 3869 if (debugEnabled()) 3870 { 3871 TRACER.debugCaught(DebugLogLevel.ERROR, e); 3872 3873 TRACER.debugInfo("Could not decode the timestamp in " + 3874 "history value " + histStr + " -- " + e + 3875 ". Marking it for removal."); 3876 } 3877 3878 LinkedHashSet<AttributeValue> values = 3879 new LinkedHashSet<AttributeValue>(1); 3880 values.add(v); 3881 if (removeAttrs != null) 3882 { 3883 removeAttrs.add(new Attribute(a.getAttributeType(), a.getName(), 3884 values)); 3885 } 3886 } 3887 } 3888 } 3889 } 3890 } 3891 3892 return historyMap; 3893 } 3894 3895 3896 3897 /** 3898 * Indicates whether the provided password matches the given history value. 3899 * 3900 * @param password The clear-text password for which to make the 3901 * determination. 3902 * @param historyValue The encoded history value to compare against the 3903 * clear-text password. 3904 * 3905 * @return {@code true} if the provided password matches the history value, 3906 * or {@code false} if not. 3907 */ 3908 private boolean historyValueMatches(ByteString password, 3909 AttributeValue historyValue) 3910 { 3911 // According to draft-behera-ldap-password-policy, password history values 3912 // should be in the format time#syntaxoid#encodedvalue. In this method, 3913 // we only care about the syntax OID and encoded password. 3914 try 3915 { 3916 String histStr = historyValue.getStringValue(); 3917 int hashPos1 = histStr.indexOf('#'); 3918 if (hashPos1 <= 0) 3919 { 3920 if (debugEnabled()) 3921 { 3922 TRACER.debugInfo("Returning false because the password history " + 3923 "value didn't include any hash characters."); 3924 } 3925 3926 return false; 3927 } 3928 3929 int hashPos2 = histStr.indexOf('#', hashPos1+1); 3930 if (hashPos2 < 0) 3931 { 3932 if (debugEnabled()) 3933 { 3934 TRACER.debugInfo("Returning false because the password history " + 3935 "value only had one hash character."); 3936 } 3937 3938 return false; 3939 } 3940 3941 String syntaxOID = toLowerCase(histStr.substring(hashPos1+1, hashPos2)); 3942 if (syntaxOID.equals(SYNTAX_AUTH_PASSWORD_OID)) 3943 { 3944 StringBuilder[] authPWComponents = 3945 AuthPasswordSyntax.decodeAuthPassword( 3946 histStr.substring(hashPos2+1)); 3947 PasswordStorageScheme scheme = 3948 DirectoryServer.getAuthPasswordStorageScheme( 3949 authPWComponents[0].toString()); 3950 if (scheme.authPasswordMatches(password, authPWComponents[1].toString(), 3951 authPWComponents[2].toString())) 3952 { 3953 if (debugEnabled()) 3954 { 3955 TRACER.debugInfo("Returning true because the auth password " + 3956 "history value matched."); 3957 } 3958 3959 return true; 3960 } 3961 else 3962 { 3963 if (debugEnabled()) 3964 { 3965 TRACER.debugInfo("Returning false because the auth password " + 3966 "history value did not match."); 3967 } 3968 3969 return false; 3970 } 3971 } 3972 else if (syntaxOID.equals(SYNTAX_USER_PASSWORD_OID)) 3973 { 3974 String[] userPWComponents = 3975 UserPasswordSyntax.decodeUserPassword( 3976 histStr.substring(hashPos2+1)); 3977 PasswordStorageScheme scheme = 3978 DirectoryServer.getPasswordStorageScheme(userPWComponents[0]); 3979 if (scheme.passwordMatches(password, 3980 new ASN1OctetString(userPWComponents[1]))) 3981 { 3982 if (debugEnabled()) 3983 { 3984 TRACER.debugInfo("Returning true because the user password " + 3985 "history value matched."); 3986 } 3987 3988 return true; 3989 } 3990 else 3991 { 3992 if (debugEnabled()) 3993 { 3994 TRACER.debugInfo("Returning false because the user password " + 3995 "history value did not match."); 3996 } 3997 3998 return false; 3999 } 4000 } 4001 else 4002 { 4003 if (debugEnabled()) 4004 { 4005 TRACER.debugInfo("Returning false because the syntax OID " + 4006 syntaxOID + " didn't match for either the auth " + 4007 "or user password syntax."); 4008 } 4009 4010 return false; 4011 } 4012 } 4013 catch (Exception e) 4014 { 4015 if (debugEnabled()) 4016 { 4017 TRACER.debugCaught(DebugLogLevel.ERROR, e); 4018 4019 if (debugEnabled()) 4020 { 4021 TRACER.debugInfo("Returning false because of an exception: " + 4022 stackTraceToSingleLineString(e)); 4023 } 4024 } 4025 4026 return false; 4027 } 4028 } 4029 4030 4031 4032 /** 4033 * Updates the password history information for this user by adding all 4034 * current passwords to it. 4035 */ 4036 public void updatePasswordHistory() 4037 { 4038 List<Attribute> attrList = 4039 userEntry.getAttribute(passwordPolicy.getPasswordAttribute()); 4040 if (attrList != null) 4041 { 4042 for (Attribute a : attrList) 4043 { 4044 for (AttributeValue v : a.getValues()) 4045 { 4046 addPasswordToHistory(v.getStringValue()); 4047 } 4048 } 4049 } 4050 } 4051 4052 4053 4054 /** 4055 * Adds the provided password to the password history. If appropriate, one or 4056 * more old passwords may be evicted from the list if the total size would 4057 * exceed the configured count, or if passwords are older than the configured 4058 * duration. 4059 * 4060 * @param encodedPassword The encoded password (in either user password or 4061 * auth password format) to be added to the history. 4062 */ 4063 private void addPasswordToHistory(String encodedPassword) 4064 { 4065 if (! maintainHistory()) 4066 { 4067 if (debugEnabled()) 4068 { 4069 TRACER.debugInfo("Not doing anything because password history " + 4070 "maintenance is disabled."); 4071 } 4072 4073 return; 4074 } 4075 4076 4077 // Get a sorted list of the existing values to see if there are any that 4078 // should be removed. 4079 LinkedList<Attribute> removeAttrs = new LinkedList<Attribute>(); 4080 TreeMap<Long,AttributeValue> historyMap = 4081 getSortedHistoryValues(removeAttrs); 4082 4083 4084 // If there is a maximum number of values to retain and we would be over the 4085 // limit with the new value, then get rid of enough values (oldest first) 4086 // to satisfy the count. 4087 AttributeType historyType = 4088 DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_HISTORY_LC, true); 4089 int historyCount = passwordPolicy.getPasswordHistoryCount(); 4090 if ((historyCount > 0) && (historyMap.size() >= historyCount)) 4091 { 4092 int numToDelete = (historyMap.size() - historyCount) + 1; 4093 LinkedHashSet<AttributeValue> removeValues = 4094 new LinkedHashSet<AttributeValue>(numToDelete); 4095 Iterator<AttributeValue> iterator = historyMap.values().iterator(); 4096 while (iterator.hasNext() && (numToDelete > 0)) 4097 { 4098 AttributeValue v = iterator.next(); 4099 removeValues.add(v); 4100 iterator.remove(); 4101 numToDelete--; 4102 4103 if (debugEnabled()) 4104 { 4105 TRACER.debugInfo("Removing history value " + v.getStringValue() + 4106 " to preserve the history count."); 4107 } 4108 } 4109 4110 if (! removeValues.isEmpty()) 4111 { 4112 removeAttrs.add(new Attribute(historyType, historyType.getPrimaryName(), 4113 removeValues)); 4114 } 4115 } 4116 4117 4118 // If there is a maximum duration, then get rid of any values that would be 4119 // over the duration. 4120 int historyDuration = passwordPolicy.getPasswordHistoryDuration(); 4121 if (historyDuration > 0) 4122 { 4123 long minAgeToKeep = currentTime - (1000L * historyDuration); 4124 Iterator<Long> iterator = historyMap.keySet().iterator(); 4125 LinkedHashSet<AttributeValue> removeValues = 4126 new LinkedHashSet<AttributeValue>(); 4127 while (iterator.hasNext()) 4128 { 4129 long timestamp = iterator.next(); 4130 if (timestamp < minAgeToKeep) 4131 { 4132 AttributeValue v = historyMap.get(timestamp); 4133 removeValues.add(v); 4134 iterator.remove(); 4135 4136 if (debugEnabled()) 4137 { 4138 TRACER.debugInfo("Removing history value " + v.getStringValue() + 4139 " to preserve the history duration."); 4140 } 4141 } 4142 else 4143 { 4144 break; 4145 } 4146 } 4147 4148 if (! removeValues.isEmpty()) 4149 { 4150 removeAttrs.add(new Attribute(historyType, historyType.getPrimaryName(), 4151 removeValues)); 4152 } 4153 } 4154 4155 4156 // At this point, we can add the new value. However, we want to make sure 4157 // that its timestamp (which is the current time) doesn't conflict with any 4158 // value already in the list. If there is a conflict, then simply add one 4159 // to it until we don't have any more conflicts. 4160 long newTimestamp = currentTime; 4161 while (historyMap.containsKey(newTimestamp)) 4162 { 4163 newTimestamp++; 4164 } 4165 String newHistStr = GeneralizedTimeSyntax.format(newTimestamp) + "#" + 4166 passwordPolicy.getPasswordAttribute().getSyntaxOID() + 4167 "#" + encodedPassword; 4168 LinkedHashSet<AttributeValue> newHistValues = 4169 new LinkedHashSet<AttributeValue>(1); 4170 newHistValues.add(new AttributeValue(historyType, newHistStr)); 4171 Attribute newHistAttr = 4172 new Attribute(historyType, historyType.getPrimaryName(), 4173 newHistValues); 4174 4175 if (debugEnabled()) 4176 { 4177 TRACER.debugInfo("Going to add history value " + newHistStr); 4178 } 4179 4180 4181 // Apply the changes, either by adding modifications or by directly updating 4182 // the entry. 4183 if (updateEntry) 4184 { 4185 LinkedList<AttributeValue> valueList = new LinkedList<AttributeValue>(); 4186 for (Attribute a : removeAttrs) 4187 { 4188 userEntry.removeAttribute(a, valueList); 4189 } 4190 4191 userEntry.addAttribute(newHistAttr, valueList); 4192 } 4193 else 4194 { 4195 for (Attribute a : removeAttrs) 4196 { 4197 modifications.add(new Modification(ModificationType.DELETE, a, true)); 4198 } 4199 4200 modifications.add(new Modification(ModificationType.ADD, newHistAttr, 4201 true)); 4202 } 4203 } 4204 4205 4206 4207 /** 4208 * Retrieves the password history state values for the user. This is only 4209 * intended for testing purposes. 4210 * 4211 * @return The password history state values for the user. 4212 */ 4213 public String[] getPasswordHistoryValues() 4214 { 4215 ArrayList<String> historyValues = new ArrayList<String>(); 4216 AttributeType historyType = 4217 DirectoryServer.getAttributeType(OP_ATTR_PWPOLICY_HISTORY_LC, true); 4218 List<Attribute> attrList = userEntry.getAttribute(historyType); 4219 if (attrList != null) 4220 { 4221 for (Attribute a : attrList) 4222 { 4223 for (AttributeValue v : a.getValues()) 4224 { 4225 historyValues.add(v.getStringValue()); 4226 } 4227 } 4228 } 4229 4230 String[] historyArray = new String[historyValues.size()]; 4231 return historyValues.toArray(historyArray); 4232 } 4233 4234 4235 4236 /** 4237 * Clears the password history state information for the user. This is only 4238 * intended for testing purposes. 4239 */ 4240 public void clearPasswordHistory() 4241 { 4242 if (debugEnabled()) 4243 { 4244 TRACER.debugInfo("Clearing password history for user %s", userDNString); 4245 } 4246 4247 AttributeType type = DirectoryServer.getAttributeType( 4248 OP_ATTR_PWPOLICY_HISTORY_LC, true); 4249 if (updateEntry) 4250 { 4251 userEntry.removeAttribute(type); 4252 } 4253 else 4254 { 4255 modifications.add(new Modification(ModificationType.REPLACE, 4256 new Attribute(type), true)); 4257 } 4258 } 4259 4260 4261 4262 /** 4263 * Generates a new password for the user. 4264 * 4265 * @return The new password that has been generated, or <CODE>null</CODE> if 4266 * no password generator has been defined. 4267 * 4268 * @throws DirectoryException If an error occurs while attempting to 4269 * generate the new password. 4270 */ 4271 public ByteString generatePassword() 4272 throws DirectoryException 4273 { 4274 PasswordGenerator generator = passwordPolicy.getPasswordGenerator(); 4275 if (generator == null) 4276 { 4277 if (debugEnabled()) 4278 { 4279 TRACER.debugWarning("Unable to generate a new password for user " + 4280 "%s because no password generator has been defined in the " + 4281 "associated password policy.", userDNString); 4282 } 4283 4284 return null; 4285 } 4286 4287 return generator.generatePassword(userEntry); 4288 } 4289 4290 4291 4292 /** 4293 * Generates an account status notification for this user. 4294 * 4295 * @param notificationType The type for the account status 4296 * notification. 4297 * @param userEntry The entry for the user to which this 4298 * notification applies. 4299 * @param message The human-readable message for the 4300 * notification. 4301 * @param notificationProperties The set of properties for the notification. 4302 */ 4303 public void generateAccountStatusNotification( 4304 AccountStatusNotificationType notificationType, 4305 Entry userEntry, Message message, 4306 Map<AccountStatusNotificationProperty,List<String>> 4307 notificationProperties) 4308 { 4309 generateAccountStatusNotification(new AccountStatusNotification( 4310 notificationType, userEntry, message, notificationProperties)); 4311 } 4312 4313 4314 4315 /** 4316 * Generates an account status notification for this user. 4317 * 4318 * @param notification The account status notification that should be 4319 * generated. 4320 */ 4321 public void generateAccountStatusNotification( 4322 AccountStatusNotification notification) 4323 { 4324 Collection<AccountStatusNotificationHandler> handlers = 4325 passwordPolicy.getAccountStatusNotificationHandlers().values(); 4326 if ((handlers == null) || handlers.isEmpty()) 4327 { 4328 return; 4329 } 4330 4331 for (AccountStatusNotificationHandler handler : handlers) 4332 { 4333 handler.handleStatusNotification(notification); 4334 } 4335 } 4336 4337 4338 4339 /** 4340 * Retrieves the set of modifications that correspond to changes made in 4341 * password policy processing that may need to be applied to the user entry. 4342 * 4343 * @return The set of modifications that correspond to changes made in 4344 * password policy processing that may need to be applied to the user 4345 * entry. 4346 */ 4347 public LinkedList<Modification> getModifications() 4348 { 4349 return modifications; 4350 } 4351 4352 4353 4354 /** 4355 * Performs an internal modification to update the user's entry, if necessary. 4356 * This will do nothing if no modifications are required. 4357 * 4358 * @throws DirectoryException If a problem occurs while processing the 4359 * internal modification. 4360 */ 4361 public void updateUserEntry() 4362 throws DirectoryException 4363 { 4364 // If there are no modifications, then there's nothing to do. 4365 if (modifications.isEmpty()) 4366 { 4367 return; 4368 } 4369 4370 4371 // Convert the set of modifications to a set of LDAP modifications. 4372 ArrayList<RawModification> modList = new ArrayList<RawModification>(); 4373 for (Modification m : modifications) 4374 { 4375 modList.add(RawModification.create(m.getModificationType(), 4376 new LDAPAttribute(m.getAttribute()))); 4377 } 4378 4379 InternalClientConnection conn = 4380 InternalClientConnection.getRootConnection(); 4381 ModifyOperation internalModify = 4382 conn.processModify(new ASN1OctetString(userDNString), modList); 4383 4384 ResultCode resultCode = internalModify.getResultCode(); 4385 if (resultCode != ResultCode.SUCCESS) 4386 { 4387 Message message = ERR_PWPSTATE_CANNOT_UPDATE_USER_ENTRY.get(userDNString, 4388 String.valueOf(internalModify.getErrorMessage())); 4389 4390 // If this is a root user, or if the password policy says that we should 4391 // ignore these problems, then log a warning message. Otherwise, cause 4392 // the bind to fail. 4393 if ((DirectoryServer.isRootDN(userEntry.getDN()) || 4394 (passwordPolicy.getStateUpdateFailurePolicy() == 4395 PasswordPolicyCfgDefn.StateUpdateFailurePolicy.IGNORE))) 4396 { 4397 ErrorLogger.logError(message); 4398 } 4399 else 4400 { 4401 throw new DirectoryException(resultCode, message); 4402 } 4403 } 4404 } 4405 } 4406