This commit is contained in:
Oleg Zimakov 2026-05-23 12:28:23 -07:00 committed by GitHub
commit 16df2b6d92
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 94 additions and 14 deletions

View file

@ -95,7 +95,6 @@ import org.keycloak.policy.PasswordPolicyNotMetException;
import org.keycloak.protocol.oid4vc.resources.admin.UserVerifiableCredentialResource;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.utils.RedirectUtils;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.ErrorRepresentation;
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
@ -306,18 +305,14 @@ public class UserResource {
List<String> reqActions = rep.getRequiredActions();
if (reqActions != null) {
session.getKeycloakSessionFactory()
.getProviderFactoriesStream(RequiredActionProvider.class)
.map(ProviderFactory::getId)
.distinct()
.sorted()
.forEach(action -> {
if (reqActions.contains(action)) {
user.addRequiredAction(action);
} else if (removeMissingRequiredActions) {
user.removeRequiredAction(action);
}
});
if (removeMissingRequiredActions) {
user.getRequiredActionsStream().toList().forEach(user::removeRequiredAction);
}
reqActions.stream()
.filter(action -> session.getKeycloakSessionFactory()
.getProviderFactory(RequiredActionProvider.class, action) != null)
.forEach(user::addRequiredAction);
}
List<CredentialRepresentation> credentials = rep.getCredentials();

View file

@ -1,13 +1,19 @@
package org.keycloak.tests.admin.user;
import java.util.List;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
import org.keycloak.representations.idm.RequiredActionProviderSimpleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
import org.keycloak.testframework.events.AdminEventAssertion;
import org.keycloak.testframework.server.KeycloakServerConfig;
import org.keycloak.testframework.server.KeycloakServerConfigBuilder;
import org.keycloak.tests.providers.actions.DummyRequiredActionFactory;
import org.keycloak.tests.suites.DatabaseTest;
import org.keycloak.tests.utils.admin.AdminEventPaths;
@ -17,7 +23,7 @@ import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@KeycloakIntegrationTest
@KeycloakIntegrationTest(config = UserRequiredActionsTest.CustomProvidersServerConfig.class)
public class UserRequiredActionsTest extends AbstractUserTest {
@Test
@ -77,4 +83,83 @@ public class UserRequiredActionsTest extends AbstractUserTest {
managedRealm.admin().flows().updateRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD.toString(), updatePasswordReqAction);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.UPDATE, AdminEventPaths.authRequiredActionPath(UserModel.RequiredAction.UPDATE_PASSWORD.toString()), updatePasswordReqAction, ResourceType.REQUIRED_ACTION);
}
@Test
@DatabaseTest
public void removeCustomRequiredAction() {
registerDummyRequiredAction();
String id = createUser();
UserResource user = managedRealm.admin().users().get(id);
// Add the custom required action to the user
UserRepresentation userRep = user.toRepresentation();
userRep.getRequiredActions().add(DummyRequiredActionFactory.PROVIDER_ID);
updateUser(user, userRep);
userRep = user.toRepresentation();
assertEquals(1, userRep.getRequiredActions().size());
assertEquals(DummyRequiredActionFactory.PROVIDER_ID, userRep.getRequiredActions().get(0));
// Remove the custom required action by sending an empty list
userRep.getRequiredActions().clear();
updateUser(user, userRep);
userRep = user.toRepresentation();
assertTrue(userRep.getRequiredActions().isEmpty(),
"Custom required action should be removed but was still present: " + userRep.getRequiredActions());
}
@Test
@DatabaseTest
public void removeCustomRequiredActionKeepBuiltIn() {
registerDummyRequiredAction();
String id = createUser();
UserResource user = managedRealm.admin().users().get(id);
// Add both a built-in and custom required action
UserRepresentation userRep = user.toRepresentation();
userRep.getRequiredActions().add(UserModel.RequiredAction.UPDATE_PASSWORD.toString());
userRep.getRequiredActions().add(DummyRequiredActionFactory.PROVIDER_ID);
updateUser(user, userRep);
userRep = user.toRepresentation();
assertEquals(2, userRep.getRequiredActions().size());
// Remove only the custom action, keep the built-in one
userRep.setRequiredActions(List.of(UserModel.RequiredAction.UPDATE_PASSWORD.toString()));
updateUser(user, userRep);
userRep = user.toRepresentation();
assertEquals(1, userRep.getRequiredActions().size());
assertEquals(UserModel.RequiredAction.UPDATE_PASSWORD.toString(), userRep.getRequiredActions().get(0));
}
private void registerDummyRequiredAction() {
RequiredActionProviderSimpleRepresentation action = managedRealm.admin().flows().getUnregisteredRequiredActions()
.stream()
.filter(a -> a.getProviderId().equals(DummyRequiredActionFactory.PROVIDER_ID))
.findFirst()
.orElseThrow(() -> new AssertionError("Dummy required action not found"));
managedRealm.admin().flows().registerRequiredAction(action);
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE,
AdminEventPaths.authMgmtBasePath() + "/register-required-action", action, ResourceType.REQUIRED_ACTION);
managedRealm.cleanup().add(r -> {
try {
r.flows().removeRequiredAction(DummyRequiredActionFactory.PROVIDER_ID);
} catch (jakarta.ws.rs.NotFoundException ignored) {
}
});
}
public static class CustomProvidersServerConfig implements KeycloakServerConfig {
@Override
public KeycloakServerConfigBuilder configure(KeycloakServerConfigBuilder config) {
return config.dependency("org.keycloak.tests", "keycloak-tests-custom-providers");
}
}
}