Failure reset time is applied to Permanent Lockout
Closes #28821 Signed-off-by: Douglas Palmer <dpalmer@redhat.com>
This commit is contained in:
parent
b08c644601
commit
ed22530d16
2 changed files with 76 additions and 1 deletions
|
@ -70,7 +70,7 @@ public class DefaultBruteForceProtector implements BruteForceProtector {
|
||||||
}
|
}
|
||||||
userLoginFailure.setLastFailure(failureTime);
|
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 last failure was more than MAX_DELTA clear failures
|
||||||
if (deltaTime > (long) realm.getMaxDeltaTimeSeconds() * 1000L) {
|
if (deltaTime > (long) realm.getMaxDeltaTimeSeconds() * 1000L) {
|
||||||
userLoginFailure.clearFailures();
|
userLoginFailure.clearFailures();
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
|
import org.keycloak.executors.ExecutorsProvider;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.utils.TimeBasedOTP;
|
import org.keycloak.models.utils.TimeBasedOTP;
|
||||||
|
@ -51,8 +52,11 @@ import org.keycloak.testsuite.util.RealmRepUtil;
|
||||||
import org.keycloak.testsuite.util.UserBuilder;
|
import org.keycloak.testsuite.util.UserBuilder;
|
||||||
|
|
||||||
import jakarta.mail.internet.MimeMessage;
|
import jakarta.mail.internet.MimeMessage;
|
||||||
|
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
@ -402,6 +406,77 @@ public class BruteForceTest extends AbstractTestRealmKeycloakTest {
|
||||||
loginSuccess();
|
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
|
@Test
|
||||||
public void testWait() throws Exception {
|
public void testWait() throws Exception {
|
||||||
loginSuccess();
|
loginSuccess();
|
||||||
|
|
Loading…
Reference in a new issue