Inconsistency in User enabled status in Rest query results.

Closes #39549 #28713

Signed-off-by: Douglas Palmer <dpalmer@redhat.com>
This commit is contained in:
Douglas Palmer
2025-05-13 11:28:20 -07:00
committed by Marek Posolda
parent 39699e7d3a
commit 64cb66f451
3 changed files with 22 additions and 4 deletions

View File

@@ -22,6 +22,10 @@ It has been a while since discussions started about any activity around the Inst
and any objection from the community about deprecating it for removal. For more details, see
https://github.com/keycloak/keycloak/issues/37967[Deprecate for removal the Instagram social broker].
=== Inconsistent user endpoints harmonized
In previous releases there was an inconsistency in the REST endpoint result of getting a user (`GET /admin/realms/{realm}/users/{user-id}`) and searching for a user (`GET /admin/realms/{realm}/users`). When BruteForce is enabled and a user was temporarily locked out the former endpoint would return enabled=false while the latter would return enabled=true. If the user was updated and enabled was false due to temporary lockout then the user would be disabled permanently. Both endpoints now return enabled=true when a user is temporarily locked out. To check whether a user is temporarily locked out the BruteForceUserResource endpoint should be utilised (`GET /admin/realms/{realm}/attack-detection/brute-force/users/{userId}`).
=== Deprecated password policy Recovery Codes Warning Threshold
In relation to supported Recovery codes, we deprecated the password policy `Recovery Codes Warning Threshold`. This password policy might be removed in the future major version of {project_name}.

View File

@@ -354,9 +354,6 @@ public class UserResource {
rep.setFederatedIdentities(reps);
}
if (session.getProvider(BruteForceProtector.class).isTemporarilyDisabled(session, realm, user)) {
rep.setEnabled(false);
}
rep.setAccess(auth.users().getAccess(user));
if (!userProfileMetadata) {

View File

@@ -539,6 +539,19 @@ public class BruteForceTest extends AbstractChangeImportedUserPasswordsTest {
expectTemporarilyDisabled("user2", userId);
clearAllUserFailures();
}
@Test
public void testUserDisabledTemporaryLockout() throws Exception {
String userId = adminClient.realm("test").users().search("test-user@localhost", null, null, null, 0, 1).get(0).getId();
loginInvalidPassword();
loginInvalidPassword();
expectTemporarilyDisabled();
assertTrue(testRealm().users().get(userId).toRepresentation().isEnabled());
assertTrue(testRealm().users().search("test-user@localhost", true).get(0).isEnabled());
assertEquals(Boolean.TRUE, testRealm().attackDetection().bruteForceUserStatus(userId).get("disabled"));
}
@Test
public void testBrowserMissingPassword() throws Exception {
@@ -819,7 +832,9 @@ public class BruteForceTest extends AbstractChangeImportedUserPasswordsTest {
assertTrue(passwordUpdatePage.isCurrent());
UserRepresentation userRepresentation = testRealm().users().get(userId).toRepresentation();
assertFalse(userRepresentation.isEnabled());
assertTrue(userRepresentation.isEnabled());
Map<String, Object> bruteForceStatus = testRealm().attackDetection().bruteForceUserStatus(userId);
assertEquals(Boolean.TRUE, bruteForceStatus.get("disabled"));
updatePasswordPage.updatePasswords(getPassword("user2"), getPassword("user2"));
@@ -828,6 +843,8 @@ public class BruteForceTest extends AbstractChangeImportedUserPasswordsTest {
userRepresentation = testRealm().users().get(userId).toRepresentation();
assertTrue(userRepresentation.isEnabled());
bruteForceStatus = testRealm().attackDetection().bruteForceUserStatus(userId);
assertEquals(Boolean.FALSE, bruteForceStatus.get("disabled"));
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());