Fix brute force detection for LDAP read-only users
Closes #28579 Signed-off-by: devjos <github_11837948@feido.de>
This commit is contained in:
parent
ce8e925c1a
commit
cccddc0810
2 changed files with 65 additions and 1 deletions
|
@ -29,6 +29,7 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.models.UserLoginFailureModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.services.ServicesLogger;
|
||||
import org.keycloak.storage.ReadOnlyException;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
|
@ -215,7 +216,11 @@ public class DefaultBruteForceProtector implements Runnable, BruteForceProtector
|
|||
}
|
||||
logger.debugv("user {0} locked permanently due to too many login attempts", user.getUsername());
|
||||
user.setEnabled(false);
|
||||
try {
|
||||
user.setSingleAttribute(DISABLED_REASON, DISABLED_BY_PERMANENT_LOCKOUT);
|
||||
}catch (ReadOnlyException e){
|
||||
logger.debug("Cannot set disabled reason on read only user");
|
||||
}
|
||||
// Send event
|
||||
sendEvent(session, realm, userLoginFailure, EventType.USER_DISABLED_BY_PERMANENT_LOCKOUT);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
|
||||
package org.keycloak.testsuite.federation.ldap;
|
||||
|
||||
import java.util.Map;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Assert;
|
||||
import org.junit.ClassRule;
|
||||
|
@ -26,17 +28,21 @@ import org.junit.Test;
|
|||
import org.junit.runners.MethodSorters;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.credential.OTPCredentialModel;
|
||||
import org.keycloak.models.utils.TimeBasedOTP;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.storage.StorageId;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.storage.ldap.LDAPStorageProvider;
|
||||
import org.keycloak.storage.ldap.idm.model.LDAPObject;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.pages.LoginConfigTotpPage;
|
||||
|
@ -48,6 +54,7 @@ import jakarta.ws.rs.ClientErrorException;
|
|||
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
|
@ -155,6 +162,58 @@ public class LDAPReadOnlyTest extends AbstractLDAPTest {
|
|||
user.update(userRepresentation);
|
||||
}
|
||||
|
||||
// issue #28580
|
||||
@Test
|
||||
public void testReadOnlyUserGetsPermanentlyLocked(){
|
||||
int failureFactor = 2;
|
||||
RealmRepresentation realm = testRealm().toRepresentation();
|
||||
try {
|
||||
// Set permanent lockout for the test
|
||||
realm.setBruteForceProtected(true);
|
||||
realm.setPermanentLockout(true);
|
||||
realm.setFailureFactor(failureFactor);
|
||||
testRealm().update(realm);
|
||||
|
||||
UserRepresentation user = adminClient.realm("test").users().search("johnkeycloak", 0, 1).get(0);
|
||||
assertTrue(user.isEnabled());
|
||||
|
||||
// Lock user (permanently) and make sure the number of failures matches failure factor
|
||||
loginInvalidPassword("johnkeycloak");
|
||||
loginInvalidPassword("johnkeycloak");
|
||||
assertUserNumberOfFailures(user.getId(), failureFactor);
|
||||
|
||||
// Make sure user is now disabled
|
||||
user = adminClient.realm("test").users().search("johnkeycloak", 0, 1).get(0);
|
||||
assertFalse(user.isEnabled());
|
||||
|
||||
events.clear();
|
||||
} finally {
|
||||
realm.setBruteForceProtected(false);
|
||||
realm.setPermanentLockout(false);
|
||||
realm.setFailureFactor(30);
|
||||
testRealm().update(realm);
|
||||
UserRepresentation user = adminClient.realm("test").users().search("johnkeycloak", 0, 1).get(0);
|
||||
user.setEnabled(true);
|
||||
updateUser(user);
|
||||
}
|
||||
}
|
||||
|
||||
public void loginInvalidPassword(String username) {
|
||||
loginPage.open();
|
||||
loginPage.login(username, "invalid");
|
||||
|
||||
loginPage.assertCurrent();
|
||||
|
||||
Assert.assertEquals("Invalid username or password.", loginPage.getInputError());
|
||||
|
||||
events.clear();
|
||||
}
|
||||
|
||||
private void assertUserNumberOfFailures(String userId, Integer numberOfFailures) {
|
||||
Map<String, Object> userAttackInfo = adminClient.realm("test").attackDetection().bruteForceUserStatus(userId);
|
||||
MatcherAssert.assertThat((Integer) userAttackInfo.get("numFailures"), is(numberOfFailures));
|
||||
}
|
||||
|
||||
private void setTotpRequirementExecutionForRealm(AuthenticationExecutionModel.Requirement requirement) {
|
||||
adminClient.realm("test").flows().getExecutions("browser").
|
||||
stream().filter(execution -> execution.getDisplayName().equals("Browser - Conditional OTP"))
|
||||
|
|
Loading…
Reference in a new issue