Brute force protection: Lockout permanently uses parameters configured under lockout temporarily

Closes #30969

Signed-off-by: Douglas Palmer <dpalmer@redhat.com>
This commit is contained in:
Douglas Palmer 2024-08-27 09:45:50 -07:00 committed by Alexander Schwartz
parent fff5087d30
commit ecbd856176
2 changed files with 49 additions and 2 deletions

View file

@ -79,7 +79,10 @@ public class DefaultBruteForceProtector implements BruteForceProtector {
userLoginFailure.incrementFailures(); userLoginFailure.incrementFailures();
logger.debugv("new num failures: {0}", userLoginFailure.getNumFailures()); logger.debugv("new num failures: {0}", userLoginFailure.getNumFailures());
int waitSeconds = realm.getWaitIncrementSeconds() * (userLoginFailure.getNumFailures() / realm.getFailureFactor()); int waitSeconds = 0;
if(!(realm.isPermanentLockout() && realm.getMaxTemporaryLockouts() == 0)) {
waitSeconds = realm.getWaitIncrementSeconds() * (userLoginFailure.getNumFailures() / realm.getFailureFactor());
}
logger.debugv("waitSeconds: {0}", waitSeconds); logger.debugv("waitSeconds: {0}", waitSeconds);
logger.debugv("deltaTime: {0}", deltaTime); logger.debugv("deltaTime: {0}", deltaTime);
@ -110,7 +113,8 @@ public class DefaultBruteForceProtector implements BruteForceProtector {
return; return;
} }
if(userLoginFailure.getNumTemporaryLockouts() > realm.getMaxTemporaryLockouts()) { if(userLoginFailure.getNumTemporaryLockouts() > realm.getMaxTemporaryLockouts() ||
(realm.getMaxTemporaryLockouts() == 0 && userLoginFailure.getNumFailures() >= realm.getFailureFactor())) {
UserModel user = session.users().getUserById(realm, userId); UserModel user = session.users().getUserById(realm, userId);
if (user == null) { if (user == null) {
return; return;

View file

@ -564,6 +564,49 @@ public class BruteForceTest extends AbstractTestRealmKeycloakTest {
loginInvalidPassword("test-user@localhost"); loginInvalidPassword("test-user@localhost");
loginInvalidPassword("test-user@localhost", false); loginInvalidPassword("test-user@localhost", false);
// As of now, there are two events: USER_DISABLED_BY_PERMANENT_LOCKOUT and LOGIN_ERROR but Order is not
// guarantee though since the brute force detector is running separately "in its own thread" named
// "Brute Force Protector".
List<EventRepresentation> actualEvents = Arrays.asList(events.poll(), events.poll(), events.poll());
assertIsContained(events.expect(EventType.USER_DISABLED_BY_PERMANENT_LOCKOUT).client((String) null).detail(Details.REASON, "brute_force_attack detected"), actualEvents);
assertIsContained(events.expect(EventType.LOGIN_ERROR).error(Errors.INVALID_USER_CREDENTIALS), actualEvents);
// assert
expectPermanentlyDisabled();
UserRepresentation user = adminClient.realm("test").users().search("test-user@localhost", 0, 1).get(0);
assertFalse(user.isEnabled());
assertUserDisabledReason(BruteForceProtector.DISABLED_BY_PERMANENT_LOCKOUT);
user.setEnabled(true);
updateUser(user);
assertUserDisabledReason(null);
} finally {
realm.setPermanentLockout(false);
testRealm().update(realm);
UserRepresentation user = adminClient.realm("test").users().search("test-user@localhost", 0, 1).get(0);
user.setEnabled(true);
updateUser(user);
}
}
// https://github.com/keycloak/keycloak/issues/30969
@Test
public void testPermanentLockoutWithTempLockoutParamsSet()
{
RealmRepresentation realm = testRealm().toRepresentation();
try {
// arrange
realm.setWaitIncrementSeconds(0);
realm.setPermanentLockout(true);
realm.setMaxTemporaryLockouts(0);
realm.setQuickLoginCheckMilliSeconds(0L);
testRealm().update(realm);
// act
loginInvalidPassword("test-user@localhost");
loginInvalidPassword("test-user@localhost", false);
// As of now, there are two events: USER_DISABLED_BY_PERMANENT_LOCKOUT and LOGIN_ERROR but Order is not // As of now, there are two events: USER_DISABLED_BY_PERMANENT_LOCKOUT and LOGIN_ERROR but Order is not
// guarantee though since the brute force detector is running separately "in its own thread" named // guarantee though since the brute force detector is running separately "in its own thread" named
// "Brute Force Protector". // "Brute Force Protector".