diff --git a/core/src/main/java/org/keycloak/representations/idm/RequiredActionProviderSimpleRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RequiredActionProviderSimpleRepresentation.java index 1e0a60d078..492204dd28 100644 --- a/core/src/main/java/org/keycloak/representations/idm/RequiredActionProviderSimpleRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/RequiredActionProviderSimpleRepresentation.java @@ -25,9 +25,18 @@ package org.keycloak.representations.idm; */ public class RequiredActionProviderSimpleRepresentation { + private String id; private String name; private String providerId; + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + public String getName() { return name; } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java index a720a327c9..b481a757ca 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java @@ -28,6 +28,7 @@ import org.keycloak.authentication.FormAction; import org.keycloak.authentication.FormAuthenticator; import org.keycloak.authentication.RequiredActionFactory; import org.keycloak.authentication.RequiredActionProvider; +import org.keycloak.events.admin.OperationType; import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.AuthenticationFlowModel; import org.keycloak.models.AuthenticatorConfigModel; @@ -212,7 +213,10 @@ public class AuthenticationManagementResource { return ErrorResponse.exists("Flow " + flow.getAlias() + " already exists"); } - realm.addAuthenticationFlow(RepresentationToModel.toModel(flow)); + AuthenticationFlowModel createdModel = realm.addAuthenticationFlow(RepresentationToModel.toModel(flow)); + + flow.setId(createdModel.getId()); + adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, createdModel.getId()).representation(flow).success(); return Response.status(201).build(); } @@ -263,6 +267,9 @@ public class AuthenticationManagementResource { realm.removeAuthenticatorExecution(execution); } realm.removeAuthenticationFlow(flow); + + // Use just one event for top-level flow. Using separate events won't work properly for flows of depth 2 or bigger + adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success(); } /** @@ -299,6 +306,9 @@ public class AuthenticationManagementResource { copy = realm.addAuthenticationFlow(copy); copy(newName, flow, copy); + data.put("id", copy.getId()); + adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(data).success(); + return Response.status(201).build(); } @@ -362,8 +372,10 @@ public class AuthenticationManagementResource { execution.setAuthenticatorFlow(true); execution.setAuthenticator(provider); execution.setPriority(getNextPriority(parentFlow)); + execution = realm.addAuthenticatorExecution(execution); - realm.addAuthenticatorExecution(execution); + data.put("id", execution.getId()); + adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(data).success(); } private int getNextPriority(AuthenticationFlowModel parentFlow) { @@ -413,7 +425,10 @@ public class AuthenticationManagementResource { execution.setAuthenticator(provider); execution.setPriority(getNextPriority(parentFlow)); - realm.addAuthenticatorExecution(execution); + execution = realm.addAuthenticatorExecution(execution); + + data.put("id", execution.getId()); + adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(data).success(); } /** @@ -519,6 +534,7 @@ public class AuthenticationManagementResource { if (!model.getRequirement().name().equals(rep.getRequirement())) { model.setRequirement(AuthenticationExecutionModel.Requirement.valueOf(rep.getRequirement())); realm.updateAuthenticatorExecution(model); + adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success(); } } @@ -541,6 +557,8 @@ public class AuthenticationManagementResource { } model.setPriority(getNextPriority(parentFlow)); model = realm.addAuthenticatorExecution(model); + + adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, model.getId()).representation(execution).success(); return Response.created(uriInfo.getAbsolutePathBuilder().path(model.getId()).build()).build(); } @@ -592,6 +610,8 @@ public class AuthenticationManagementResource { realm.updateAuthenticatorExecution(previous); model.setPriority(tmp); realm.updateAuthenticatorExecution(model); + + adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).success(); } public List getSortedExecutions(AuthenticationFlowModel parentFlow) { @@ -635,6 +655,8 @@ public class AuthenticationManagementResource { realm.updateAuthenticatorExecution(model); next.setPriority(tmp); realm.updateAuthenticatorExecution(next); + + adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).success(); } @@ -666,6 +688,8 @@ public class AuthenticationManagementResource { } realm.removeAuthenticatorExecution(model); + + adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success(); } @@ -693,6 +717,9 @@ public class AuthenticationManagementResource { config = realm.addAuthenticatorConfig(config); model.setAuthenticatorConfig(config.getId()); realm.updateAuthenticatorExecution(model); + + json.setId(config.getId()); + adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(json).success(); return Response.created(uriInfo.getAbsolutePathBuilder().path(config.getId()).build()).build(); } @@ -772,7 +799,10 @@ public class AuthenticationManagementResource { requiredAction.setProviderId(providerId); requiredAction.setDefaultAction(false); requiredAction.setEnabled(true); - realm.addRequiredActionProvider(requiredAction); + requiredAction = realm.addRequiredActionProvider(requiredAction); + + data.put("id", requiredAction.getId()); + adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(data).success(); } @@ -850,6 +880,8 @@ public class AuthenticationManagementResource { update.setEnabled(rep.isEnabled()); update.setConfig(rep.getConfig()); realm.updateRequiredActionProvider(update); + + adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success(); } /** @@ -866,6 +898,8 @@ public class AuthenticationManagementResource { throw new NotFoundException("Failed to find required action."); } realm.removeRequiredActionProvider(model); + + adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success(); } /** @@ -947,6 +981,7 @@ public class AuthenticationManagementResource { auth.requireManage(); AuthenticatorConfigModel config = realm.addAuthenticatorConfig(RepresentationToModel.toModel(rep)); + adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, config.getId()).representation(rep).success(); return Response.created(uriInfo.getAbsolutePathBuilder().path(config.getId()).build()).build(); } @@ -995,6 +1030,8 @@ public class AuthenticationManagementResource { } realm.removeAuthenticatorConfig(config); + + adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success(); } /** @@ -1017,5 +1054,6 @@ public class AuthenticationManagementResource { exists.setAlias(rep.getAlias()); exists.setConfig(rep.getConfig()); realm.updateAuthenticatorConfig(exists); + adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success(); } } \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AbstractAuthenticationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AbstractAuthenticationTest.java index 50b11ac168..d77c76ce96 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AbstractAuthenticationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AbstractAuthenticationTest.java @@ -19,19 +19,26 @@ package org.keycloak.testsuite.admin.authentication; import org.junit.Assert; import org.junit.Before; +import org.junit.Rule; import org.keycloak.admin.client.resource.AuthenticationManagementResource; import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.events.admin.OperationType; import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation; import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation; import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.representations.idm.AuthenticatorConfigRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.util.AdminEventPaths; +import org.keycloak.testsuite.util.AssertAdminEvents; +import org.keycloak.testsuite.util.RealmBuilder; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; +import javax.ws.rs.core.Response; + /** * @author Marko Strukelj @@ -48,6 +55,9 @@ public abstract class AbstractAuthenticationTest extends AbstractKeycloakTest { RealmResource realmResource; AuthenticationManagementResource authMgmtResource; + @Rule + public AssertAdminEvents assertAdminEvents = new AssertAdminEvents(this); + @Before public void before() { realmResource = adminClient.realms().realm(REALM_NAME); @@ -56,9 +66,8 @@ public abstract class AbstractAuthenticationTest extends AbstractKeycloakTest { @Override public void addTestRealms(List testRealms) { - RealmRepresentation testRealmRep = new RealmRepresentation(); - testRealmRep.setRealm(REALM_NAME); - testRealmRep.setEnabled(true); + RealmRepresentation testRealmRep = RealmBuilder.create().name(REALM_NAME).testEventListener().build(); + testRealmRep.setId(REALM_NAME); testRealms.add(testRealmRep); } @@ -182,4 +191,11 @@ public abstract class AbstractAuthenticationTest extends AbstractKeycloakTest { config.setConfig(params); return config; } + + void createFlow(AuthenticationFlowRepresentation flowRep) { + Response response = authMgmtResource.createFlow(flowRep); + org.keycloak.testsuite.Assert.assertEquals(201, response.getStatus()); + response.close(); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.CREATE, AssertAdminEvents.isExpectedPrefixFollowedByUuid(AdminEventPaths.authFlowsPath()), flowRep); + } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AuthenticatorConfigTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AuthenticatorConfigTest.java index c5f7ed1c80..eb1cda5a5d 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AuthenticatorConfigTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/AuthenticatorConfigTest.java @@ -28,11 +28,14 @@ import org.junit.Before; import org.junit.Test; import org.keycloak.authentication.authenticators.broker.IdpCreateUserIfUniqueAuthenticator; import org.keycloak.authentication.authenticators.broker.IdpCreateUserIfUniqueAuthenticatorFactory; +import org.keycloak.events.admin.OperationType; import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation; import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.representations.idm.AuthenticatorConfigRepresentation; import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.admin.ApiUtil; +import org.keycloak.testsuite.util.AdminEventPaths; +import org.keycloak.testsuite.util.AssertAdminEvents; /** * @author Marek Posolda @@ -43,13 +46,13 @@ public class AuthenticatorConfigTest extends AbstractAuthenticationTest { @Before public void beforeConfigTest() { - Response response = authMgmtResource.createFlow(newFlow("firstBrokerLogin2", "firstBrokerLogin2", "basic-flow", true, false)); - Assert.assertEquals(201, response.getStatus()); - response.close(); + AuthenticationFlowRepresentation flowRep = newFlow("firstBrokerLogin2", "firstBrokerLogin2", "basic-flow", true, false); + createFlow(flowRep); HashMap params = new HashMap<>(); params.put("provider", IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID); authMgmtResource.addExecution("firstBrokerLogin2", params); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.CREATE, AdminEventPaths.authAddExecutionPath("firstBrokerLogin2"), params); List executionReps = authMgmtResource.getExecutions("firstBrokerLogin2"); AuthenticationExecutionInfoRepresentation exec = findExecutionByProvider(IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID, executionReps); @@ -57,12 +60,6 @@ public class AuthenticatorConfigTest extends AbstractAuthenticationTest { executionId = exec.getId(); } - @Override - public void afterAbstractKeycloakTest() { - AuthenticationFlowRepresentation flowRep = findFlowByAlias("firstBrokerLogin2", authMgmtResource.getFlows()); - authMgmtResource.deleteFlow(flowRep.getId()); - } - @Test public void testCreateConfig() { @@ -82,6 +79,7 @@ public class AuthenticatorConfigTest extends AbstractAuthenticationTest { // Cleanup authMgmtResource.removeAuthenticatorConfig(cfgId); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.DELETE, AdminEventPaths.authExecutionConfigPath(cfgId)); } @@ -107,6 +105,7 @@ public class AuthenticatorConfigTest extends AbstractAuthenticationTest { cfgRep.setAlias("foo2"); cfgRep.getConfig().put("configKey2", "configValue2"); authMgmtResource.updateAuthenticatorConfig(cfgRep.getId(), cfgRep); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.UPDATE, AdminEventPaths.authExecutionConfigPath(cfgId), cfgRep); // Assert updated cfgRep = authMgmtResource.getAuthenticatorConfig(cfgRep.getId()); @@ -137,7 +136,8 @@ public class AuthenticatorConfigTest extends AbstractAuthenticationTest { } // Test remove our config - authMgmtResource.removeAuthenticatorConfig(cfgRep.getId()); + authMgmtResource.removeAuthenticatorConfig(cfgId); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.DELETE, AdminEventPaths.authExecutionConfigPath(cfgId)); // Assert config not found try { @@ -159,6 +159,7 @@ public class AuthenticatorConfigTest extends AbstractAuthenticationTest { Assert.assertEquals(201, resp.getStatus()); String cfgId = ApiUtil.getCreatedId(resp); Assert.assertNotNull(cfgId); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.CREATE, AdminEventPaths.authAddExecutionConfigPath(executionId), cfg); return cfgId; } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java index be0b9c44b5..ae19ff6179 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ExecutionTest.java @@ -21,10 +21,12 @@ import org.junit.Assert; import org.junit.Test; import org.keycloak.authentication.AuthenticationFlow; import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator; -import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.events.admin.OperationType; import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation; import org.keycloak.representations.idm.AuthenticationExecutionRepresentation; import org.keycloak.representations.idm.AuthenticationFlowRepresentation; +import org.keycloak.testsuite.util.AdminEventPaths; +import org.keycloak.testsuite.util.AssertAdminEvents; import javax.ws.rs.BadRequestException; import javax.ws.rs.NotFoundException; @@ -60,8 +62,9 @@ public class ExecutionTest extends AbstractAuthenticationTest { } // copy built-in flow so we get a new editable flow - params.put("newName", "Copy of browser"); + params.put("newName", "Copy-of-browser"); Response response = authMgmtResource.copy("browser", params); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.CREATE, AdminEventPaths.authCopyFlowPath("browser"), params); try { Assert.assertEquals("Copy flow", 201, response.getStatus()); } finally { @@ -71,17 +74,19 @@ public class ExecutionTest extends AbstractAuthenticationTest { // add execution using inexistent provider params.put("provider", "test-execution"); try { - authMgmtResource.addExecution("Copy of browser", params); + authMgmtResource.addExecution("CopyOfBrowser", params); Assert.fail("add execution with inexistent provider should fail"); } catch(BadRequestException expected) { + // Expected } // add execution - should succeed params.put("provider", "idp-review-profile"); - authMgmtResource.addExecution("Copy of browser", params); + authMgmtResource.addExecution("Copy-of-browser", params); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.CREATE, AdminEventPaths.authAddExecutionPath("Copy-of-browser"), params); // check execution was added - List executionReps = authMgmtResource.getExecutions("Copy of browser"); + List executionReps = authMgmtResource.getExecutions("Copy-of-browser"); AuthenticationExecutionInfoRepresentation exec = findExecutionByProvider("idp-review-profile", executionReps); Assert.assertNotNull("idp-review-profile added", exec); @@ -92,9 +97,10 @@ public class ExecutionTest extends AbstractAuthenticationTest { // remove execution authMgmtResource.removeExecution(exec.getId()); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.DELETE, AdminEventPaths.authExecutionPath(exec.getId())); // check execution was removed - executionReps = authMgmtResource.getExecutions("Copy of browser"); + executionReps = authMgmtResource.getExecutions("Copy-of-browser"); exec = findExecutionByProvider("idp-review-profile", executionReps); Assert.assertNull("idp-review-profile removed", exec); @@ -102,6 +108,7 @@ public class ExecutionTest extends AbstractAuthenticationTest { // delete auth-cookie authMgmtResource.removeExecution(authCookieExec.getId()); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.DELETE, AdminEventPaths.authExecutionPath(authCookieExec.getId())); AuthenticationExecutionRepresentation rep = new AuthenticationExecutionRepresentation(); rep.setPriority(10); @@ -135,13 +142,14 @@ public class ExecutionTest extends AbstractAuthenticationTest { response.close(); } - // get Copy of browser flow id, and set it on execution + // get Copy-of-browser flow id, and set it on execution List flows = authMgmtResource.getFlows(); - AuthenticationFlowRepresentation flow = findFlowByAlias("Copy of browser", flows); + AuthenticationFlowRepresentation flow = findFlowByAlias("Copy-of-browser", flows); rep.setParentFlow(flow.getId()); // add execution - should succeed response = authMgmtResource.addExecution(rep); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.CREATE, AssertAdminEvents.isExpectedPrefixFollowedByUuid(AdminEventPaths.authMgmtBasePath() + "/executions"), rep); try { Assert.assertEquals("added execution", 201, response.getStatus()); } finally { @@ -149,7 +157,7 @@ public class ExecutionTest extends AbstractAuthenticationTest { } // check execution was added - List executions = authMgmtResource.getExecutions("Copy of browser"); + List executions = authMgmtResource.getExecutions("Copy-of-browser"); exec = findExecutionByProvider("auth-cookie", executions); Assert.assertNotNull("auth-cookie added", exec); @@ -170,6 +178,7 @@ public class ExecutionTest extends AbstractAuthenticationTest { // switch from DISABLED to ALTERNATIVE exec.setRequirement(DISABLED); authMgmtResource.updateExecutions("browser", exec); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.UPDATE, AdminEventPaths.authUpdateExecutionPath("browser"), exec); // make sure the change is visible executionReps = authMgmtResource.getExecutions("browser"); @@ -183,14 +192,13 @@ public class ExecutionTest extends AbstractAuthenticationTest { public void testClientFlowExecutions() { // Create client flow AuthenticationFlowRepresentation clientFlow = newFlow("new-client-flow", "desc", AuthenticationFlow.CLIENT_FLOW, true, false); - Response response = authMgmtResource.createFlow(clientFlow); - Assert.assertEquals(201, response.getStatus()); - response.close(); + createFlow(clientFlow); // Add execution to it Map executionData = new HashMap<>(); executionData.put("provider", ClientIdAndSecretAuthenticator.PROVIDER_ID); authMgmtResource.addExecution("new-client-flow", executionData); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.CREATE, AdminEventPaths.authAddExecutionPath("new-client-flow"), executionData); // Check executions of not-existent flow - SHOULD FAIL try { @@ -226,6 +234,7 @@ public class ExecutionTest extends AbstractAuthenticationTest { // Update success executionRep.setRequirement(ALTERNATIVE); authMgmtResource.updateExecutions("new-client-flow", executionRep); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.UPDATE, AdminEventPaths.authUpdateExecutionPath("new-client-flow"), executionRep); // Check updated executionRep = findExecutionByProvider(ClientIdAndSecretAuthenticator.PROVIDER_ID, authMgmtResource.getExecutions("new-client-flow")); @@ -241,7 +250,10 @@ public class ExecutionTest extends AbstractAuthenticationTest { // Successfuly remove execution and flow authMgmtResource.removeExecution(executionRep.getId()); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.DELETE, AdminEventPaths.authExecutionPath(executionRep.getId())); + AuthenticationFlowRepresentation rep = findFlowByAlias("new-client-flow", authMgmtResource.getFlows()); authMgmtResource.deleteFlow(rep.getId()); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.DELETE, AdminEventPaths.authFlowPath(rep.getId())); } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/FlowTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/FlowTest.java index 80d17bb52b..7e2f4a8d17 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/FlowTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/FlowTest.java @@ -19,8 +19,10 @@ package org.keycloak.testsuite.admin.authentication; import org.junit.Assert; import org.junit.Test; +import org.keycloak.events.admin.OperationType; import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation; import org.keycloak.representations.idm.AuthenticationFlowRepresentation; +import org.keycloak.testsuite.util.AdminEventPaths; import javax.ws.rs.BadRequestException; import javax.ws.rs.NotFoundException; @@ -67,12 +69,7 @@ public class FlowTest extends AbstractAuthenticationTest { // create new flow that should succeed AuthenticationFlowRepresentation newFlow = newFlow("browser-2", "Browser flow", "basic-flow", true, false); - response = authMgmtResource.createFlow(newFlow); - try { - Assert.assertEquals("createFlow success", 201, response.getStatus()); - } finally { - response.close(); - } + createFlow(newFlow); // check that new flow is returned in a children list flows = authMgmtResource.getFlows(); @@ -122,6 +119,7 @@ public class FlowTest extends AbstractAuthenticationTest { // Successfully add flow data.put("alias", "SomeFlow"); authMgmtResource.addExecutionFlow("browser-2", data); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.CREATE, AdminEventPaths.authAddExecutionFlowPath("browser-2"), data); // check that new flow is returned in a children list flows = authMgmtResource.getFlows(); @@ -143,6 +141,7 @@ public class FlowTest extends AbstractAuthenticationTest { // delete non-built-in flow authMgmtResource.deleteFlow(found.getId()); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.DELETE, AdminEventPaths.authFlowPath(found.getId())); // check the deleted flow is no longer returned flows = authMgmtResource.getFlows(); @@ -185,6 +184,7 @@ public class FlowTest extends AbstractAuthenticationTest { // copy that should succeed params.put("newName", "Copy of browser"); response = authMgmtResource.copy("browser", params); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.CREATE, AdminEventPaths.authCopyFlowPath("browser"), params); try { Assert.assertEquals("Copy flow", 201, response.getStatus()); } finally { @@ -219,6 +219,7 @@ public class FlowTest extends AbstractAuthenticationTest { Response response = authMgmtResource.copy("browser", params); Assert.assertEquals(201, response.getStatus()); response.close(); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.CREATE, AdminEventPaths.authCopyFlowPath("browser"), params); params = new HashMap<>(); params.put("alias", "child"); @@ -227,6 +228,7 @@ public class FlowTest extends AbstractAuthenticationTest { params.put("type", "basic-flow"); authMgmtResource.addExecutionFlow("parent", params); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.CREATE, AdminEventPaths.authAddExecutionFlowPath("parent"), params); } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/RegistrationFlowTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/RegistrationFlowTest.java index bf2babe261..d7e25b6401 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/RegistrationFlowTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/RegistrationFlowTest.java @@ -25,6 +25,9 @@ import javax.ws.rs.core.Response; import org.junit.Assert; import org.junit.Test; +import org.keycloak.events.admin.OperationType; +import org.keycloak.representations.idm.AuthenticationFlowRepresentation; +import org.keycloak.testsuite.util.AdminEventPaths; /** * @author Marek Posolda @@ -34,13 +37,8 @@ public class RegistrationFlowTest extends AbstractAuthenticationTest { @Test public void testAddExecution() { // Add registration flow 2 - Response response = authMgmtResource.createFlow(newFlow("registration2", "RegistrationFlow2", "basic-flow", true, false)); - try { - Assert.assertEquals("createFlow success", 201, response.getStatus()); - } finally { - response.close(); - } - + AuthenticationFlowRepresentation flowRep = newFlow("registration2", "RegistrationFlow2", "basic-flow", true, false); + createFlow(flowRep); // add registration execution form flow Map data = new HashMap<>(); @@ -49,6 +47,7 @@ public class RegistrationFlowTest extends AbstractAuthenticationTest { data.put("description", "registrationForm2 flow"); data.put("provider", "registration-page-form"); authMgmtResource.addExecutionFlow("registration2", data); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.CREATE, AdminEventPaths.authAddExecutionFlowPath("registration2"), data); // Should fail to add execution under top level flow Map data2 = new HashMap<>(); @@ -63,9 +62,9 @@ public class RegistrationFlowTest extends AbstractAuthenticationTest { // Should success to add execution under form flow authMgmtResource.addExecution("registrationForm2", data2); - + assertAdminEvents.assertEvent(REALM_NAME, OperationType.CREATE, AdminEventPaths.authAddExecutionPath("registrationForm2"), data2); } - // TODO: More coverage... And hopefully more type-safety instead of passing generic maps + // TODO: More type-safety instead of passing generic maps } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/RequiredActionsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/RequiredActionsTest.java index 7418cb1bc9..8d45830bee 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/RequiredActionsTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/RequiredActionsTest.java @@ -19,9 +19,11 @@ package org.keycloak.testsuite.admin.authentication; import org.junit.Assert; import org.junit.Test; +import org.keycloak.events.admin.OperationType; import org.keycloak.representations.idm.RequiredActionProviderRepresentation; import org.keycloak.representations.idm.RequiredActionProviderSimpleRepresentation; import org.keycloak.testsuite.actions.DummyRequiredActionFactory; +import org.keycloak.testsuite.util.AdminEventPaths; import java.util.ArrayList; import java.util.Collections; @@ -59,6 +61,7 @@ public class RequiredActionsTest extends AbstractAuthenticationTest { forUpdate.setConfig(Collections.emptyMap()); authMgmtResource.updateRequiredAction(forUpdate.getAlias(), forUpdate); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.UPDATE, AdminEventPaths.authRequiredActionPath(forUpdate.getAlias())); result = authMgmtResource.getRequiredActions(); RequiredActionProviderRepresentation updated = findRequiredActionByAlias(forUpdate.getAlias(), result); @@ -78,6 +81,7 @@ public class RequiredActionsTest extends AbstractAuthenticationTest { // Register it authMgmtResource.registerRequiredAction(action); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.CREATE, AdminEventPaths.authMgmtBasePath() + "/register-required-action", action); // Try to find not-existent action - should fail try { @@ -103,6 +107,7 @@ public class RequiredActionsTest extends AbstractAuthenticationTest { // Update (set it as defaultAction) rep.setDefaultAction(true); authMgmtResource.updateRequiredAction(DummyRequiredActionFactory.PROVIDER_ID, rep); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.UPDATE, AdminEventPaths.authRequiredActionPath(rep.getAlias()), rep); compareRequiredAction(rep, newRequiredAction(DummyRequiredActionFactory.PROVIDER_ID, "Dummy Action", true, true, Collections.emptyMap())); @@ -116,6 +121,7 @@ public class RequiredActionsTest extends AbstractAuthenticationTest { // Remove success authMgmtResource.removeRequiredAction(DummyRequiredActionFactory.PROVIDER_ID); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.DELETE, AdminEventPaths.authRequiredActionPath(rep.getAlias())); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ShiftExecutionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ShiftExecutionTest.java index 9ce4f536e5..ba8ae09197 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ShiftExecutionTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ShiftExecutionTest.java @@ -19,7 +19,9 @@ package org.keycloak.testsuite.admin.authentication; import org.junit.Assert; import org.junit.Test; +import org.keycloak.events.admin.OperationType; import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation; +import org.keycloak.testsuite.util.AdminEventPaths; import javax.ws.rs.NotFoundException; import javax.ws.rs.BadRequestException; @@ -39,6 +41,7 @@ public class ShiftExecutionTest extends AbstractAuthenticationTest { HashMap params = new HashMap<>(); params.put("newName", "Copy of browser"); Response response = authMgmtResource.copy("browser", params); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.CREATE, AdminEventPaths.authCopyFlowPath("browser"), params); try { Assert.assertEquals("Copy flow", 201, response.getStatus()); } finally { @@ -61,6 +64,7 @@ public class ShiftExecutionTest extends AbstractAuthenticationTest { // shift last execution up authMgmtResource.raisePriority(last.getId()); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.UPDATE, AdminEventPaths.authRaiseExecutionPath(last.getId())); List executions2 = authMgmtResource.getExecutions("Copy of browser"); @@ -80,6 +84,7 @@ public class ShiftExecutionTest extends AbstractAuthenticationTest { // shift one before last down authMgmtResource.lowerPriority(oneButLast2.getId()); + assertAdminEvents.assertEvent(REALM_NAME, OperationType.UPDATE, AdminEventPaths.authLowerExecutionPath(oneButLast2.getId())); executions2 = authMgmtResource.getExecutions("Copy of browser"); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/AdminEventPaths.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/AdminEventPaths.java index 7ff548a653..1b15400125 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/AdminEventPaths.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/AdminEventPaths.java @@ -21,6 +21,7 @@ import java.net.URI; import javax.ws.rs.core.UriBuilder; +import org.keycloak.admin.client.resource.AuthenticationManagementResource; import org.keycloak.admin.client.resource.ClientAttributeCertificateResource; import org.keycloak.admin.client.resource.ClientInitialAccessResource; import org.keycloak.admin.client.resource.ClientResource; @@ -353,4 +354,83 @@ public class AdminEventPaths { } + // AUTHENTICATION FLOWS + + public static String authMgmtBasePath() { + URI uri = UriBuilder.fromUri("").path(RealmResource.class, "flows") + .build(); + return uri.toString(); + } + + public static String authFlowsPath() { + URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "getFlows") + .build(); + return uri.toString(); + } + + public static String authFlowPath(String flowId) { + URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "getFlow") + .build(flowId); + return uri.toString(); + } + + public static String authCopyFlowPath(String flowAlias) { + URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "copy") + .build(flowAlias); + return uri.toString(); + } + + public static String authAddExecutionFlowPath(String flowAlias) { + URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "addExecutionFlow") + .build(flowAlias); + return uri.toString(); + } + + public static String authAddExecutionPath(String flowAlias) { + return authFlowPath(flowAlias) + "/executions/execution"; + } + + public static String authUpdateExecutionPath(String flowAlias) { + URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "updateExecutions") + .build(flowAlias); + return uri.toString(); + } + + public static String authExecutionPath(String executionId) { + URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "removeExecution") + .build(executionId); + return uri.toString(); + } + + public static String authAddExecutionConfigPath(String executionId) { + URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "newExecutionConfig") + .build(executionId); + return uri.toString(); + } + + public static String authExecutionConfigPath(String configId) { + URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "getAuthenticatorConfig") + .build(configId); + return uri.toString(); + } + + public static String authRaiseExecutionPath(String executionId) { + URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "raisePriority") + .build(executionId); + return uri.toString(); + } + + public static String authLowerExecutionPath(String executionId) { + URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "lowerPriority") + .build(executionId); + return uri.toString(); + } + + public static String authRequiredActionPath(String requiredActionAlias) { + URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "getRequiredAction") + .build(requiredActionAlias); + return uri.toString(); + } + + } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/AssertAdminEvents.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/AssertAdminEvents.java index 0383a45ea0..83b6f1415b 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/AssertAdminEvents.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/AssertAdminEvents.java @@ -19,11 +19,14 @@ package org.keycloak.testsuite.util; import java.io.IOException; import java.lang.reflect.Method; +import java.util.Map; import javax.ws.rs.core.Response; +import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.Matchers; +import org.hamcrest.TypeSafeMatcher; import org.junit.rules.TestRule; import org.junit.runners.model.Statement; import org.keycloak.common.util.ObjectUtil; @@ -205,12 +208,27 @@ public class AssertAdminEvents implements TestRule { try { Object actualRep = JsonSerialization.readValue(actual.getRepresentation(), expectedRep.getClass()); - for (Method method : Reflections.getAllDeclaredMethods(expectedRep.getClass())) { - if (method.getName().startsWith("get") || method.getName().startsWith("is")) { - Object expectedValue = Reflections.invokeMethod(method, expectedRep); + if (expectedRep instanceof Map) { + // Special comparing of representations of type map. All of "expected" must be available on "actual" + Map expectedRepMap = (Map) expectedRep; + Map actualRepMap = (Map) actualRep; + + for (Map.Entry entry : expectedRepMap.entrySet()) { + Object expectedValue = entry.getValue(); if (expectedValue != null) { - Object actualValue = Reflections.invokeMethod(method, actualRep); - Assert.assertEquals("Property " + method.getName() + " of representation not equal.", expectedValue, actualValue); + Object actualValue = actualRepMap.get(entry.getKey()); + Assert.assertEquals("Map item with key '" + entry.getKey() + "' not equal.", expectedValue, actualValue); + } + } + } else { + // Reflection-baseed comparing for other types + for (Method method : Reflections.getAllDeclaredMethods(expectedRep.getClass())) { + if (method.getName().startsWith("get") || method.getName().startsWith("is")) { + Object expectedValue = Reflections.invokeMethod(method, expectedRep); + if (expectedValue != null) { + Object actualValue = Reflections.invokeMethod(method, actualRep); + Assert.assertEquals("Property method '" + method.getName() + "' of representation not equal.", expectedValue, actualValue); + } } } } @@ -241,5 +259,22 @@ public class AssertAdminEvents implements TestRule { } } + public static Matcher isExpectedPrefixFollowedByUuid(final String prefix) { + return new TypeSafeMatcher() { + + @Override + protected boolean matchesSafely(String item) { + int expectedLength = prefix.length() + 1 + org.keycloak.models.utils.KeycloakModelUtils.generateId().length(); + return item.startsWith(prefix) && expectedLength == item.length(); + } + + @Override + public void describeTo(Description description) { + description.appendText("resourcePath in the format like \"" + prefix + "/\""); + } + + }; + } + }