2525 * $Id: ISAccountLockout.java,v 1.15 2009/03/07 08:01:50 veiming Exp $
2626 *
2727 * Portions Copyrighted 2011-2017 ForgeRock AS.
28- * Portions Copyrighted 2023 Wren Security
28+ * Portions Copyrighted 2023-2025 Wren Security.
2929 */
3030package com .sun .identity .common ;
3131
32- import static org .forgerock .openam .utils .Time .* ;
32+ import static org .forgerock .openam .utils .Time .currentTimeMillis ;
3333
3434import com .iplanet .am .util .AMSendMail ;
35- import javax .mail .MessagingException ;
3635import com .iplanet .sso .SSOException ;
37- import com .sun .identity .authentication .spi .AMAuthCallBackImpl ;
3836import com .sun .identity .authentication .spi .AMAuthCallBackException ;
37+ import com .sun .identity .authentication .spi .AMAuthCallBackImpl ;
3938import com .sun .identity .idm .AMIdentity ;
4039import com .sun .identity .idm .IdRepoException ;
4140import com .sun .identity .shared .debug .Debug ;
4241import com .sun .identity .shared .debug .DebugLevel ;
43- import com .sun .identity .shared .debug .IDebug ;
4442import java .text .MessageFormat ;
4543import java .util .Collections ;
4644import java .util .HashMap ;
5048import java .util .ResourceBundle ;
5149import java .util .Set ;
5250import java .util .StringTokenizer ;
51+ import javax .mail .MessagingException ;
52+ import org .forgerock .util .Reject ;
5353
5454public class ISAccountLockout {
5555 private static final String USER_STATUS_ATTR ="inetuserstatus" ;
@@ -77,6 +77,8 @@ public class ISAccountLockout {
7777 "<ActualLockoutDuration>" ;
7878 private static final String ACTUAL_LOCKOUT_DURATION_END =
7979 "</ActualLockoutDuration>" ;
80+ private static final String NO_OF_TIMES_LOCKED_BEGIN ="<NoOfTimesLocked>" ;
81+ private static final String NO_OF_TIMES_LOCKED_END ="</NoOfTimesLocked>" ;
8082 private static final String END_XML ="</InvalidPassword>" ;
8183
8284 private boolean failureLockoutMode = false ;
@@ -229,9 +231,11 @@ public int invalidPasswd(String userDN, String userName,
229231 failCount = 1 ;
230232 }
231233
232- if ((lastFailTime == 0L && failCount == 1 || (lastFailTime + failureLockoutTime ) > now )
233- && failCount == failureLockoutCount ) {
234+ if (isFailCountExceeded (failCount , lastFailTime , now )) {
234235 lockedAt = now ;
236+ acInfo .setActualLockoutDuration (
237+ calculateLockoutDuration (acInfo , failureLockoutDuration , failureLockoutMultiplier ));
238+ acInfo .setNoOfTimesLocked (acInfo .getNoOfTimesLocked () + 1 );
235239 }
236240 if (debug .messageEnabled ()) {
237241 debug .message ("ISAccountLockout.invalidPasswd:failCount:" + failCount );
@@ -241,7 +245,7 @@ public int invalidPasswd(String userDN, String userName,
241245 Map attrMap = new HashMap ();
242246 Set invalidAttempts = new HashSet ();
243247 String invalidXML = createInvalidAttemptsXML (
244- failCount , now , lockedAt , acInfo .getActualLockoutDuration ());
248+ failCount , now , lockedAt , acInfo .getActualLockoutDuration (), acInfo . getNoOfTimesLocked () );
245249 invalidAttempts .add (invalidXML );
246250
247251 if (debug .messageEnabled ()) {
@@ -266,7 +270,7 @@ public int invalidPasswd(String userDN, String userName,
266270 acInfo .setLastFailTime (now );
267271 acInfo .setFailCount (failCount );
268272 acInfo .setLockoutAt (lockedAt );
269- if (lockedAt > 0 ) {
273+ if (acInfo . getLockoutAt () + acInfo . getActualLockoutDuration () > now ) {
270274 acInfo .setLockout (true );
271275 }
272276 acInfo .setUserToken (userName );
@@ -301,7 +305,7 @@ public int invalidPasswd(String userDN, String userName,
301305 setWarningCount (failCount ,failureLockoutCount );
302306 return userWarningCount ;
303307 }
304-
308+
305309 public AccountLockoutInfo getAcInfo (String userDN , AMIdentity amIdentity ) {
306310 AccountLockoutInfo acInfo = null ;
307311 if (storeInvalidAttemptsInDS ) {
@@ -324,6 +328,7 @@ public AccountLockoutInfo getAcInfo(String userDN, AMIdentity amIdentity) {
324328 long last_failed = 0 ;
325329 long locked_out_at = 0 ;
326330 long actual_lockout_duration = failureLockoutDuration ;
331+ int noOfTimesLocked = 0 ;
327332
328333 if ((xmlFromDS != null ) && (xmlFromDS .length () !=0 ) &&
329334 (xmlFromDS .indexOf (BEGIN_XML ) != -1 )
@@ -346,15 +351,18 @@ public AccountLockoutInfo getAcInfo(String userDN, AMIdentity amIdentity) {
346351 } else {
347352 actual_lockout_duration = failureLockoutDuration ;
348353 }
354+ String noOfTimesLockedStr = getElement (xmlFromDS , NO_OF_TIMES_LOCKED_BEGIN , NO_OF_TIMES_LOCKED_END );
355+ noOfTimesLocked = Integer .parseInt (noOfTimesLockedStr == null ? "0" : noOfTimesLockedStr );
349356 }
350357
351358 acInfo .setLastFailTime (last_failed );
352359 acInfo .setFailCount (invalid_attempts );
353360 acInfo .setLockoutAt (locked_out_at );
354361 acInfo .setActualLockoutDuration (actual_lockout_duration );
355- if (locked_out_at > 0 ) {
362+ if (locked_out_at + actual_lockout_duration > currentTimeMillis () ) {
356363 acInfo .setLockout (true );
357364 }
365+ acInfo .setNoOfTimesLocked (noOfTimesLocked );
358366
359367 setWarningCount (invalid_attempts ,failureLockoutCount );
360368 acInfo .setWarningCount (userWarningCount );
@@ -711,7 +719,7 @@ public void resetLockoutAttempts(
711719 Map attrMap = new HashMap ();
712720 Set invalidAttempts = new HashSet ();
713721 String invalidXML = createInvalidAttemptsXML (0 ,0 ,0 ,
714- actualLockoutDuration );
722+ actualLockoutDuration , 0 );
715723 invalidAttempts .add (invalidXML );
716724 attrMap .put (invalidAttemptsDataAttrName , invalidAttempts );
717725 setLockoutObjectClass (amIdentity );
@@ -732,19 +740,35 @@ public void resetLockoutAttempts(
732740 acInfo .setActualLockoutDuration (actualLockoutDuration );
733741
734742 }
735-
743+
744+ /**
745+ * Checks whether the given {@code failCount} exceeds the {@link #failureLockoutCount}.
746+ * The {@link #failureLockoutTime} interval is taken into account.
747+ */
748+ private boolean isFailCountExceeded (int failCount , long lastFailTime , long timestamp ) {
749+ Reject .ifFalse (failureLockoutTime > 0 , "Failure lockout time must be greater than 0." );
750+ Reject .ifFalse (failureLockoutCount > 0 , "Failure lockout count must be greater than 0." );
751+
752+ if ((lastFailTime == 0L && failCount == 1 ) || (lastFailTime + failureLockoutTime ) > timestamp ) {
753+ return failCount % failureLockoutCount == 0 ;
754+ }
755+ return false ;
756+ }
757+
736758 /**
737759 * Returns XML to be stored in data store the format is like this
760+ * <pre>
738761 * <InvalidPassword>
739- * <InvalidCount>failureLockoutCount</LockoutCount>
740- * <LastInvalidAt>failureLockoutDuration</LockoutDuration>
741- * <LockedoutAt>failureLockoutTime</LockoutTime>
742- * </InvalidPassword>
743- *
762+ * <InvalidCount>invalidCount</LockoutCount>
763+ * <LastInvalidAt>lastFailed</LockoutDuration>
764+ * <LockedoutAt>lockedOutAt</LockoutTime>
765+ * <ActualLockoutDuration>actualLockoutDuration</ActualLockoutDuration>
766+ * <NoOfTimesLocked>noOfTimesLocked</NoOfTimesLocked>
767+ * </InvalidPassword>
744768 */
745769 private static String createInvalidAttemptsXML (
746770 int invalidCount , long lastFailed , long lockedOutAt ,
747- long actualLockoutDuration ) {
771+ long actualLockoutDuration , int noOfTimesLocked ) {
748772 StringBuilder xmlBuffer = new StringBuilder (150 );
749773 xmlBuffer .append (BEGIN_XML ).append (INVALID_PASS_COUNT_BEGIN )
750774 .append (String .valueOf (invalidCount )).append (INVALID_PASS_COUNT_END )
@@ -754,6 +778,7 @@ private static String createInvalidAttemptsXML(
754778 .append (ACTUAL_LOCKOUT_DURATION_BEGIN )
755779 .append (String .valueOf (actualLockoutDuration ))
756780 .append (ACTUAL_LOCKOUT_DURATION_END )
781+ .append (NO_OF_TIMES_LOCKED_BEGIN ).append (noOfTimesLocked ).append (NO_OF_TIMES_LOCKED_END )
757782 .append (END_XML );
758783 return xmlBuffer .toString ();
759784 }
@@ -775,5 +800,14 @@ private static String getElement(
775800 }
776801 return (answer );
777802 }
778-
803+
804+ private static long calculateLockoutDuration (AccountLockoutInfo acInfo , long lockoutDuration , int lockoutMultiplier ) {
805+ if (lockoutMultiplier < 1 ) {
806+ debug .warning ("Failure lockout multiplier is lower than 1. Using 1 instead." );
807+ lockoutMultiplier = 1 ;
808+ }
809+ double durationMultiplier = Math .pow (lockoutMultiplier , acInfo .getNoOfTimesLocked ());
810+ return (long ) durationMultiplier * lockoutDuration ;
811+ }
812+
779813}
0 commit comments