Check alias is unique for authenticator config when it is created
Closes #31727 Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
parent
dcf1d83199
commit
2004467749
4 changed files with 111 additions and 38 deletions
|
@ -461,6 +461,10 @@ public class AuthenticationManagementResource {
|
|||
|
||||
if (config.getAlias() != null) {
|
||||
config.setAlias(newName + " " + config.getAlias());
|
||||
if (configManager.getAuthenticatorConfigByAlias(realm, config.getAlias()) != null) {
|
||||
logger.warnf("Authentication execution configuration [%s] already exists", config.getAlias());
|
||||
throw new IllegalStateException("Authentication execution configuration " + config.getAlias() + " already exists.");
|
||||
}
|
||||
}
|
||||
|
||||
AuthenticatorConfigModel newConfig = realm.addAuthenticatorConfig(config);
|
||||
|
@ -1025,24 +1029,41 @@ public class AuthenticationManagementResource {
|
|||
public Response newExecutionConfig(@Parameter(description = "Execution id") @PathParam("executionId") String execution, @Parameter(description = "JSON with new configuration") AuthenticatorConfigRepresentation json) {
|
||||
auth.realm().requireManageRealm();
|
||||
|
||||
if (json.getAlias() == null || json.getAlias().isEmpty()) {
|
||||
throw ErrorResponse.exists("Failed to create authentication execution configuration with empty alias name");
|
||||
}
|
||||
|
||||
ReservedCharValidator.validate(json.getAlias());
|
||||
|
||||
AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(execution);
|
||||
if (model == null) {
|
||||
session.getTransactionManager().setRollbackOnly();
|
||||
throw new NotFoundException("Illegal execution");
|
||||
}
|
||||
|
||||
// retrieve the previous configuration if assigned
|
||||
AuthenticatorConfigModel prevConfig = null;
|
||||
if (model.getAuthenticatorConfig() != null) {
|
||||
prevConfig = realm.getAuthenticatorConfigById(model.getAuthenticatorConfig());
|
||||
}
|
||||
|
||||
AuthenticatorConfigModel otherConfig = realm.getAuthenticatorConfigByAlias(json.getAlias());
|
||||
if (otherConfig != null && (prevConfig == null || !prevConfig.getId().equals(otherConfig.getId()))) {
|
||||
throw ErrorResponse.exists("Authentication execution configuration " + json.getAlias() + " already exists");
|
||||
}
|
||||
|
||||
// remove the previous config
|
||||
if (prevConfig != null) {
|
||||
realm.removeAuthenticatorConfig(prevConfig);
|
||||
}
|
||||
|
||||
AuthenticatorConfigModel config = RepresentationToModel.toModel(json);
|
||||
if (config.getAlias() == null) {
|
||||
throw ErrorResponse.error("Alias missing", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
config = realm.addAuthenticatorConfig(config);
|
||||
model.setAuthenticatorConfig(config.getId());
|
||||
realm.updateAuthenticatorExecution(model);
|
||||
|
||||
json.setId(config.getId());
|
||||
adminEvent.operation(OperationType.CREATE).resource(ResourceType.AUTH_EXECUTION).resourcePath(session.getContext().getUri()).representation(json).success();
|
||||
adminEvent.operation(OperationType.CREATE).resource(ResourceType.AUTHENTICATOR_CONFIG).resourcePath(session.getContext().getUri()).representation(json).success();
|
||||
return Response.created(session.getContext().getUri().getAbsolutePathBuilder().path(config.getId()).build()).build();
|
||||
}
|
||||
|
||||
|
@ -1529,6 +1550,14 @@ public class AuthenticationManagementResource {
|
|||
public Response createAuthenticatorConfig(@Parameter(description = "JSON describing new authenticator configuration") AuthenticatorConfigRepresentation rep) {
|
||||
auth.realm().requireManageRealm();
|
||||
|
||||
if (rep.getAlias() == null || rep.getAlias().isEmpty()) {
|
||||
throw ErrorResponse.exists("Failed to create authentication execution configuration with empty alias name");
|
||||
}
|
||||
|
||||
if (realm.getAuthenticatorConfigByAlias(rep.getAlias()) != null) {
|
||||
throw ErrorResponse.exists("Authentication execution configuration " + rep.getAlias() + " already exists");
|
||||
}
|
||||
|
||||
ReservedCharValidator.validate(rep.getAlias());
|
||||
|
||||
AuthenticatorConfigModel config = realm.addAuthenticatorConfig(RepresentationToModel.toModel(rep));
|
||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.testsuite.admin.authentication;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.authentication.authenticators.broker.IdpCreateUserIfUniqueAuthenticatorFactory;
|
||||
import org.keycloak.authentication.authenticators.broker.IdpDetectExistingBrokerUserAuthenticatorFactory;
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
import org.keycloak.events.admin.ResourceType;
|
||||
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
|
||||
|
@ -30,34 +31,42 @@ import org.keycloak.testsuite.Assert;
|
|||
import org.keycloak.testsuite.admin.ApiUtil;
|
||||
import org.keycloak.testsuite.util.AdminEventPaths;
|
||||
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class AuthenticatorConfigTest extends AbstractAuthenticationTest {
|
||||
|
||||
private String flowId;
|
||||
private String executionId;
|
||||
private String executionId2;
|
||||
|
||||
@Before
|
||||
public void beforeConfigTest() {
|
||||
AuthenticationFlowRepresentation flowRep = newFlow("firstBrokerLogin2", "firstBrokerLogin2", "basic-flow", true, false);
|
||||
createFlow(flowRep);
|
||||
flowId = createFlow(flowRep);
|
||||
|
||||
HashMap<String, Object> params = new HashMap<>();
|
||||
params.put("provider", IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID);
|
||||
authMgmtResource.addExecution("firstBrokerLogin2", params);
|
||||
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authAddExecutionPath("firstBrokerLogin2"), params, ResourceType.AUTH_EXECUTION);
|
||||
params.put("provider", IdpDetectExistingBrokerUserAuthenticatorFactory.PROVIDER_ID);
|
||||
authMgmtResource.addExecution("firstBrokerLogin2", params);
|
||||
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authAddExecutionPath("firstBrokerLogin2"), params, ResourceType.AUTH_EXECUTION);
|
||||
|
||||
List<AuthenticationExecutionInfoRepresentation> executionReps = authMgmtResource.getExecutions("firstBrokerLogin2");
|
||||
AuthenticationExecutionInfoRepresentation exec = findExecutionByProvider(IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID, executionReps);
|
||||
Assert.assertNotNull(exec);
|
||||
executionId = exec.getId();
|
||||
exec = findExecutionByProvider(IdpDetectExistingBrokerUserAuthenticatorFactory.PROVIDER_ID, executionReps);
|
||||
Assert.assertNotNull(exec);
|
||||
executionId2 = exec.getId();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -72,9 +81,9 @@ public class AuthenticatorConfigTest extends AbstractAuthenticationTest {
|
|||
AuthenticatorConfigRepresentation cfg = newConfig("foo", IdpCreateUserIfUniqueAuthenticatorFactory.REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION, "true");
|
||||
|
||||
// Attempt to create config for non-existent execution
|
||||
Response response = authMgmtResource.newExecutionConfig("exec-id-doesnt-exists", cfg);
|
||||
try (Response response = authMgmtResource.newExecutionConfig("exec-id-doesnt-exists", cfg)) {
|
||||
Assert.assertEquals(404, response.getStatus());
|
||||
response.close();
|
||||
}
|
||||
|
||||
// Create config success
|
||||
String cfgId = createConfig(executionId, cfg);
|
||||
|
@ -102,18 +111,14 @@ public class AuthenticatorConfigTest extends AbstractAuthenticationTest {
|
|||
public void testUpdateConfig() {
|
||||
AuthenticatorConfigRepresentation cfg = newConfig("foo", IdpCreateUserIfUniqueAuthenticatorFactory.REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION, "true");
|
||||
String cfgId = createConfig(executionId, cfg);
|
||||
AuthenticatorConfigRepresentation cfgRep = authMgmtResource.getAuthenticatorConfig(cfgId);
|
||||
final AuthenticatorConfigRepresentation cfgRepNonExistent = authMgmtResource.getAuthenticatorConfig(cfgId);
|
||||
|
||||
// Try to update not existent config
|
||||
try {
|
||||
authMgmtResource.updateAuthenticatorConfig("not-existent", cfgRep);
|
||||
Assert.fail("Config didn't found");
|
||||
} catch (NotFoundException nfe) {
|
||||
// Expected
|
||||
}
|
||||
NotFoundException nfe = Assert.assertThrows(NotFoundException.class, () -> authMgmtResource.updateAuthenticatorConfig("not-existent", cfgRepNonExistent));
|
||||
Assert.assertEquals(404, nfe.getResponse().getStatus());
|
||||
|
||||
// Assert nothing changed
|
||||
cfgRep = authMgmtResource.getAuthenticatorConfig(cfgId);
|
||||
AuthenticatorConfigRepresentation cfgRep = authMgmtResource.getAuthenticatorConfig(cfgId);
|
||||
assertConfig(cfgRep, cfgId, "foo", IdpCreateUserIfUniqueAuthenticatorFactory.REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION, "true");
|
||||
|
||||
// Update success
|
||||
|
@ -141,26 +146,17 @@ public class AuthenticatorConfigTest extends AbstractAuthenticationTest {
|
|||
IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID, authMgmtResource.getExecutions("firstBrokerLogin2"));
|
||||
Assert.assertEquals(cfgRep.getId(), execution.getAuthenticationConfig());
|
||||
|
||||
|
||||
// Test remove not-existent
|
||||
try {
|
||||
authMgmtResource.removeAuthenticatorConfig("not-existent");
|
||||
Assert.fail("Config didn't found");
|
||||
} catch (NotFoundException nfe) {
|
||||
// Expected
|
||||
}
|
||||
NotFoundException nfe = Assert.assertThrows(NotFoundException.class, () -> authMgmtResource.removeAuthenticatorConfig("not-existent"));
|
||||
Assert.assertEquals(404, nfe.getResponse().getStatus());
|
||||
|
||||
// Test remove our config
|
||||
authMgmtResource.removeAuthenticatorConfig(cfgId);
|
||||
assertAdminEvents.assertEvent(testRealmId, OperationType.DELETE, AdminEventPaths.authExecutionConfigPath(cfgId), ResourceType.AUTHENTICATOR_CONFIG);
|
||||
|
||||
// Assert config not found
|
||||
try {
|
||||
authMgmtResource.getAuthenticatorConfig(cfgRep.getId());
|
||||
Assert.fail("Not expected to find config");
|
||||
} catch (NotFoundException nfe) {
|
||||
// Expected
|
||||
}
|
||||
nfe = Assert.assertThrows(NotFoundException.class, () -> authMgmtResource.getAuthenticatorConfig(cfgRep.getId()));
|
||||
Assert.assertEquals(404, nfe.getResponse().getStatus());
|
||||
|
||||
// Assert execution doesn't have our config
|
||||
execution = findExecutionByProvider(
|
||||
|
@ -178,14 +174,62 @@ public class AuthenticatorConfigTest extends AbstractAuthenticationTest {
|
|||
Assert.assertTrue(description.getProperties().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDuplicateAuthenticatorConfigAlias() {
|
||||
// create a config for step1
|
||||
AuthenticatorConfigRepresentation config1 = new AuthenticatorConfigRepresentation();
|
||||
config1.setAlias("test-config-1");
|
||||
config1.setConfig(Map.of("key", "value"));
|
||||
String config1Id = createConfig(executionId, config1);
|
||||
|
||||
// create the same config name for step2, should fail
|
||||
try (Response response = authMgmtResource.newExecutionConfig(executionId2, config1)) {
|
||||
Assert.assertEquals(409, response.getStatus());
|
||||
}
|
||||
|
||||
// create a config for step2
|
||||
AuthenticatorConfigRepresentation config2 = new AuthenticatorConfigRepresentation();
|
||||
config2.setAlias("test-config-2");
|
||||
config2.setConfig(Map.of("key", "value"));
|
||||
String config2Id = createConfig(executionId, config2);
|
||||
|
||||
// create a new config for step1, config1 should be removed
|
||||
AuthenticatorConfigRepresentation config3 = new AuthenticatorConfigRepresentation();
|
||||
config3.setAlias("test-config-1-modified");
|
||||
config3.setConfig(Map.of("key", "value"));
|
||||
String tmpConfig3Id = createConfig(executionId, config3);
|
||||
NotFoundException nfe = Assert.assertThrows(NotFoundException.class, () -> authMgmtResource.getAuthenticatorConfig(config1Id));
|
||||
Assert.assertEquals(404, nfe.getResponse().getStatus());
|
||||
|
||||
// create a new config with thew same name but that overwrites the previous one
|
||||
String config3Id = createConfig(executionId, config3);
|
||||
nfe = Assert.assertThrows(NotFoundException.class, () -> authMgmtResource.getAuthenticatorConfig(tmpConfig3Id));
|
||||
Assert.assertEquals(404, nfe.getResponse().getStatus());
|
||||
|
||||
// delete execution step1, config3 should be removed
|
||||
authMgmtResource.removeExecution(executionId);
|
||||
assertAdminEvents.assertEvent(testRealmId, OperationType.DELETE, AdminEventPaths.authExecutionPath(executionId), ResourceType.AUTH_EXECUTION);
|
||||
nfe = Assert.assertThrows(NotFoundException.class, () -> authMgmtResource.getAuthenticatorConfig(config3Id));
|
||||
Assert.assertEquals(404, nfe.getResponse().getStatus());
|
||||
|
||||
// remove flow, config and exec for step2 should be removed
|
||||
authMgmtResource.deleteFlow(flowId);
|
||||
assertAdminEvents.assertEvent(testRealmId, OperationType.DELETE, AdminEventPaths.authFlowPath(flowId), ResourceType.AUTH_FLOW);
|
||||
nfe = Assert.assertThrows(NotFoundException.class, () -> authMgmtResource.getExecution(executionId));
|
||||
Assert.assertEquals(404, nfe.getResponse().getStatus());
|
||||
nfe = Assert.assertThrows(NotFoundException.class, () -> authMgmtResource.getAuthenticatorConfig(config2Id));
|
||||
Assert.assertEquals(404, nfe.getResponse().getStatus());
|
||||
}
|
||||
|
||||
private String createConfig(String executionId, AuthenticatorConfigRepresentation cfg) {
|
||||
Response resp = authMgmtResource.newExecutionConfig(executionId, cfg);
|
||||
try (Response resp = authMgmtResource.newExecutionConfig(executionId, cfg)) {
|
||||
Assert.assertEquals(201, resp.getStatus());
|
||||
String cfgId = ApiUtil.getCreatedId(resp);
|
||||
Assert.assertNotNull(cfgId);
|
||||
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authAddExecutionConfigPath(executionId), cfg, ResourceType.AUTH_EXECUTION);
|
||||
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authAddExecutionConfigPath(executionId), cfg, ResourceType.AUTHENTICATOR_CONFIG);
|
||||
return cfgId;
|
||||
}
|
||||
}
|
||||
|
||||
private AuthenticatorConfigRepresentation newConfig(String alias, String cfgKey, String cfgValue) {
|
||||
AuthenticatorConfigRepresentation cfg = new AuthenticatorConfigRepresentation();
|
||||
|
|
|
@ -736,7 +736,7 @@ public class FlowTest extends AbstractAuthenticationTest {
|
|||
|
||||
try (Response response = authMgmtResource.newExecutionConfig(executionWithConfig.getId(), executionConfig)) {
|
||||
getCleanup().addAuthenticationConfigId(ApiUtil.getCreatedId(response));
|
||||
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authAddExecutionConfigPath(executionWithConfig.getId()), executionConfig, ResourceType.AUTH_EXECUTION);
|
||||
assertAdminEvents.assertEvent(testRealmId, OperationType.CREATE, AdminEventPaths.authAddExecutionConfigPath(executionWithConfig.getId()), executionConfig, ResourceType.AUTHENTICATOR_CONFIG);
|
||||
}
|
||||
|
||||
String newFlowName = "Duplicated of " + DefaultAuthenticationFlows.BROWSER_FLOW;
|
||||
|
|
|
@ -395,7 +395,7 @@ public class AuthenticationMethodReferenceTest extends AbstractOIDCScopeTest{
|
|||
if (execution.getAuthenticationConfig() == null){
|
||||
// create config if it doesn't exist
|
||||
AuthenticatorConfigRepresentation config = new AuthenticatorConfigRepresentation();
|
||||
config.setAlias("test");
|
||||
config.setAlias(KeycloakModelUtils.generateId());
|
||||
config.setConfig(new HashMap<>(){{
|
||||
put(AMR_VALUE_KEY, amrValue);
|
||||
put(AMR_MAX_AGE_KEY, maxAge.toString());
|
||||
|
|
Loading…
Reference in a new issue