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