Failure reset time is applied to Permanent Lockout

Closes #28821

Signed-off-by: Douglas Palmer <dpalmer@redhat.com>
This commit is contained in:
Douglas Palmer 2024-04-16 12:05:22 -07:00 committed by Marek Posolda
parent b08c644601
commit ed22530d16
2 changed files with 76 additions and 1 deletions

View file

@ -70,7 +70,7 @@ public class DefaultBruteForceProtector implements BruteForceProtector {
}
userLoginFailure.setLastFailure(failureTime);
if (deltaTime > 0) {
if (!(realm.isPermanentLockout() && realm.getMaxTemporaryLockouts() == 0) && deltaTime > 0) {
// if last failure was more than MAX_DELTA clear failures
if (deltaTime > (long) realm.getMaxDeltaTimeSeconds() * 1000L) {
userLoginFailure.clearFailures();

View file

@ -27,6 +27,7 @@ import org.keycloak.OAuth2Constants;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventType;
import org.keycloak.executors.ExecutorsProvider;
import org.keycloak.models.Constants;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.TimeBasedOTP;
@ -51,8 +52,11 @@ import org.keycloak.testsuite.util.RealmRepUtil;
import org.keycloak.testsuite.util.UserBuilder;
import jakarta.mail.internet.MimeMessage;
import java.net.MalformedURLException;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
@ -402,6 +406,77 @@ public class BruteForceTest extends AbstractTestRealmKeycloakTest {
loginSuccess();
}
@Test
public void testFailureResetForTemporaryLockout() throws Exception {
RealmRepresentation realm = testRealm().toRepresentation();
try {
realm.setMaxDeltaTimeSeconds(5);
testRealm().update(realm);
long numExecutors = getNumExecutors();
loginInvalidPassword();
//Wait for brute force executor to process the login and then wait for delta time
waitForExecutors(numExecutors+1);
testingClient.testing().setTimeOffset(Collections.singletonMap("offset", String.valueOf(5)));
loginInvalidPassword();
loginSuccess();
} finally {
realm.setMaxDeltaTimeSeconds(20);
testRealm().update(realm);
}
}
@Test
public void testNoFailureResetForPermanentLockout() throws Exception {
RealmRepresentation realm = testRealm().toRepresentation();
try {
realm.setMaxDeltaTimeSeconds(5);
realm.setPermanentLockout(true);
testRealm().update(realm);
long numExecutors = getNumExecutors();
loginInvalidPassword();
//Wait for brute force executor to process the login and then wait for delta time
waitForExecutors(numExecutors+1);
testingClient.testing().setTimeOffset(Collections.singletonMap("offset", String.valueOf(5)));
loginInvalidPassword();
expectPermanentlyDisabled();
} finally {
realm.setPermanentLockout(false);
realm.setMaxDeltaTimeSeconds(20);
testRealm().update(realm);
UserRepresentation user = adminClient.realm("test").users().search("test-user@localhost", 0, 1).get(0);
user.setEnabled(true);
updateUser(user);
}
}
private long getNumExecutors() {
String numExecutors = testingClient.server().fetchString(session -> {
ExecutorsProvider provider = session.getProvider(ExecutorsProvider.class);
ExecutorService executor = provider.getExecutor("bruteforce");
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
return threadPoolExecutor.getTaskCount();
});
return Long.valueOf(numExecutors);
}
private void waitForExecutors(long numExecutors) {
testingClient.server().run(session -> {
ExecutorsProvider provider = session.getProvider(ExecutorsProvider.class);
ExecutorService executor = provider.getExecutor("bruteforce");
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
while(!threadPoolExecutor.getQueue().isEmpty()) {
try {
Thread.sleep(1000);
} catch (Exception e) {}
}
assertEquals(numExecutors, threadPoolExecutor.getCompletedTaskCount());
});
}
@Test
public void testWait() throws Exception {
loginSuccess();