Skip to content

Commit 288bada

Browse files
authored
Merge pull request #181 from orchitech/fix-account-lockout
Fix account lockout
2 parents c1cb148 + 7cfae8a commit 288bada

File tree

2 files changed

+74
-20
lines changed

2 files changed

+74
-20
lines changed

openam-core/src/main/java/com/sun/identity/common/AccountLockoutInfo.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
*
2525
* $Id: AccountLockoutInfo.java,v 1.3 2008/06/25 05:42:24 qcheng Exp $
2626
*
27+
* Portions Copyrighted 2025 Wren Security.
2728
*/
2829

2930
package com.sun.identity.common;
@@ -47,6 +48,8 @@ public class AccountLockoutInfo {
4748

4849
private long actualLockoutDuration=0;
4950

51+
private int noOfTimesLocked;
52+
5053
/**
5154
* Returns the current failure count stored in this object.
5255
*
@@ -177,4 +180,21 @@ public void setUserToken(String token) {
177180
public String getUserToken() {
178181
return userToken;
179182
}
183+
184+
/**
185+
* Gets the number of times the account was locked.
186+
* <p>
187+
* This counter has to be reset to 0 after successful login.
188+
*/
189+
public int getNoOfTimesLocked() {
190+
return noOfTimesLocked;
191+
}
192+
193+
/**
194+
* Sets the number of times the account was locked.
195+
*/
196+
public void setNoOfTimesLocked(int noOfTimesLocked) {
197+
this.noOfTimesLocked = noOfTimesLocked;
198+
}
199+
180200
}

openam-core/src/main/java/com/sun/identity/common/ISAccountLockout.java

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,20 @@
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
*/
3030
package com.sun.identity.common;
3131

32-
import static org.forgerock.openam.utils.Time.*;
32+
import static org.forgerock.openam.utils.Time.currentTimeMillis;
3333

3434
import com.iplanet.am.util.AMSendMail;
35-
import javax.mail.MessagingException;
3635
import com.iplanet.sso.SSOException;
37-
import com.sun.identity.authentication.spi.AMAuthCallBackImpl;
3836
import com.sun.identity.authentication.spi.AMAuthCallBackException;
37+
import com.sun.identity.authentication.spi.AMAuthCallBackImpl;
3938
import com.sun.identity.idm.AMIdentity;
4039
import com.sun.identity.idm.IdRepoException;
4140
import com.sun.identity.shared.debug.Debug;
4241
import com.sun.identity.shared.debug.DebugLevel;
43-
import com.sun.identity.shared.debug.IDebug;
4442
import java.text.MessageFormat;
4543
import java.util.Collections;
4644
import java.util.HashMap;
@@ -50,6 +48,8 @@
5048
import java.util.ResourceBundle;
5149
import java.util.Set;
5250
import java.util.StringTokenizer;
51+
import javax.mail.MessagingException;
52+
import org.forgerock.util.Reject;
5353

5454
public 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
* &lt;InvalidPassword>
739-
* &lt;InvalidCount>failureLockoutCount&lt;/LockoutCount>
740-
* &lt;LastInvalidAt>failureLockoutDuration&lt;/LockoutDuration>
741-
* &lt;LockedoutAt>failureLockoutTime&lt;/LockoutTime>
742-
* &lt;/InvalidPassword>
743-
*
762+
* &lt;InvalidCount>invalidCount&lt;/LockoutCount>
763+
* &lt;LastInvalidAt>lastFailed&lt;/LockoutDuration>
764+
* &lt;LockedoutAt>lockedOutAt&lt;/LockoutTime>
765+
* &lt;ActualLockoutDuration>actualLockoutDuration&lt;/ActualLockoutDuration>
766+
* &lt;NoOfTimesLocked>noOfTimesLocked&lt;/NoOfTimesLocked>
767+
* &lt;/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

Comments
 (0)