/* * Copyright 2016 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.keycloak.testsuite.admin.authentication; import org.junit.Assert; import org.junit.Test; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation; import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.util.AdminEventPaths; import org.keycloak.testsuite.util.AssertAdminEvents; import javax.ws.rs.BadRequestException; import javax.ws.rs.NotFoundException; import javax.ws.rs.core.Response; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.ws.rs.core.Response.Status; import static org.hamcrest.Matchers.*; import static org.keycloak.testsuite.util.Matchers.*; /** * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a> */ public class FlowTest extends AbstractAuthenticationTest { // KEYCLOAK-3681: Delete top flow doesn't delete all subflows @Test public void testRemoveSubflows() { createFlow(newFlow("Foo", "Foo flow", "generic", true, false)); addFlowToParent("Foo", "child"); addFlowToParent("child", "grandchild"); List<AuthenticationFlowRepresentation> flows = authMgmtResource.getFlows(); AuthenticationFlowRepresentation found = findFlowByAlias("Foo", flows); authMgmtResource.deleteFlow(found.getId()); assertAdminEvents.clear(); createFlow(newFlow("Foo", "Foo flow", "generic", true, false)); addFlowToParent("Foo", "child"); // Under the old code, this would throw an error because "grandchild" // was left in the database addFlowToParent("child", "grandchild"); } private void addFlowToParent(String parentAlias, String childAlias) { Map<String, String> data = new HashMap<>(); data.put("alias", childAlias); data.put("type", "generic"); data.put("description", childAlias + " flow"); authMgmtResource.addExecutionFlow(parentAlias, data); } @Test public void testAddRemoveFlow() { // test that built-in flow cannot be deleted List<AuthenticationFlowRepresentation> flows = authMgmtResource.getFlows(); for (AuthenticationFlowRepresentation flow : flows) { try { authMgmtResource.deleteFlow(flow.getId()); Assert.fail("deleteFlow should fail for built in flow"); } catch (BadRequestException e) { break; } } // try create new flow using alias of already existing flow Response response = authMgmtResource.createFlow(newFlow("browser", "Browser flow", "basic-flow", true, false)); try { Assert.assertEquals("createFlow using the alias of existing flow should fail", 409, response.getStatus()); } finally { response.close(); } // try create flow without alias response = authMgmtResource.createFlow(newFlow(null, "Browser flow", "basic-flow", true, false)); try { Assert.assertEquals("createFlow using the alias of existing flow should fail", 409, response.getStatus()); } finally { response.close(); } // create new flow that should succeed AuthenticationFlowRepresentation newFlow = newFlow("browser-2", "Browser flow", "basic-flow", true, false); createFlow(newFlow); // check that new flow is returned in a children list flows = authMgmtResource.getFlows(); AuthenticationFlowRepresentation found = findFlowByAlias("browser-2", flows); Assert.assertNotNull("created flow visible in parent", found); compareFlows(newFlow, found); // check lookup flow with unexistent ID try { authMgmtResource.getFlow("id-123-notExistent"); Assert.fail("Not expected to find unexistent flow"); } catch (NotFoundException nfe) { // Expected } // check that new flow is returned individually AuthenticationFlowRepresentation found2 = authMgmtResource.getFlow(found.getId()); Assert.assertNotNull("created flow visible directly", found2); compareFlows(newFlow, found2); // add execution flow to some parent flow Map<String, String> data = new HashMap<>(); data.put("alias", "SomeFlow"); data.put("type", "basic-flow"); data.put("description", "Test flow"); data.put("provider", "registration-page-form"); // inexistent parent flow - should fail try { authMgmtResource.addExecutionFlow("inexistent-parent-flow-alias", data); Assert.fail("addExecutionFlow for inexistent parent should have failed"); } catch (Exception expected) { // Expected } // already existent flow - should fail try { data.put("alias", "browser"); authMgmtResource.addExecutionFlow("browser-2", data); Assert.fail("addExecutionFlow should have failed as browser flow already exists"); } catch (Exception expected) { // Expected } // Successfully add flow data.put("alias", "SomeFlow"); authMgmtResource.addExecutionFlow("browser-2", data); assertAdminEvents.assertEvent(REALM_NAME, OperationType.CREATE, AdminEventPaths.authAddExecutionFlowPath("browser-2"), data, ResourceType.AUTH_EXECUTION_FLOW); // check that new flow is returned in a children list flows = authMgmtResource.getFlows(); found2 = findFlowByAlias("browser-2", flows); Assert.assertNotNull("created flow visible in parent", found2); List<AuthenticationExecutionExportRepresentation> execs = found2.getAuthenticationExecutions(); Assert.assertNotNull(execs); Assert.assertEquals("Size one", 1, execs.size()); AuthenticationExecutionExportRepresentation expected = new AuthenticationExecutionExportRepresentation(); expected.setFlowAlias("SomeFlow"); expected.setUserSetupAllowed(false); expected.setAuthenticator("registration-page-form"); expected.setAutheticatorFlow(true); expected.setRequirement("DISABLED"); expected.setPriority(0); compareExecution(expected, execs.get(0)); // delete non-built-in flow authMgmtResource.deleteFlow(found.getId()); assertAdminEvents.assertEvent(REALM_NAME, OperationType.DELETE, AdminEventPaths.authFlowPath(found.getId()), ResourceType.AUTH_FLOW); // check the deleted flow is no longer returned flows = authMgmtResource.getFlows(); found = findFlowByAlias("browser-2", flows); Assert.assertNull("flow deleted", found); // Check deleting flow second time will fail try { authMgmtResource.deleteFlow("id-123-notExistent"); Assert.fail("Not expected to delete flow, which doesn't exists"); } catch (NotFoundException nfe) { // Expected } } @Test public void testCopyFlow() { HashMap<String, String> params = new HashMap<>(); params.put("newName", "clients"); // copy using existing alias as new name Response response = authMgmtResource.copy("browser", params); try { Assert.assertThat("Copy flow using the new alias of existing flow should fail", response, statusCodeIs(Status.CONFLICT)); Assert.assertThat("Copy flow using the new alias of existing flow should fail", response, body(containsString("already exists"))); Assert.assertThat("Copy flow using the new alias of existing flow should fail", response, body(containsString("flow alias"))); } finally { response.close(); } // copy non-existing flow params.clear(); response = authMgmtResource.copy("non-existent", params); try { Assert.assertThat("Copy non-existing flow", response, statusCodeIs(Status.NOT_FOUND)); } finally { response.close(); } // 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, ResourceType.AUTH_FLOW); try { Assert.assertThat("Copy flow", response, statusCodeIs(Status.CREATED)); } finally { response.close(); } // compare original flow with a copy - fields should be the same except id, alias, and builtIn List<AuthenticationFlowRepresentation> flows = authMgmtResource.getFlows(); AuthenticationFlowRepresentation browser = findFlowByAlias("browser", flows); AuthenticationFlowRepresentation copyOfBrowser = findFlowByAlias("Copy of browser", flows); Assert.assertNotNull(browser); Assert.assertNotNull(copyOfBrowser); // adjust expected values before comparing browser.setAlias("Copy of browser"); browser.setBuiltIn(false); browser.getAuthenticationExecutions().get(3).setFlowAlias("Copy of browser forms"); compareFlows(browser, copyOfBrowser); // get new flow directly and compare copyOfBrowser = authMgmtResource.getFlow(copyOfBrowser.getId()); Assert.assertNotNull(copyOfBrowser); compareFlows(browser, copyOfBrowser); } @Test // KEYCLOAK-2580 public void addExecutionFlow() { HashMap<String, String> params = new HashMap<>(); params.put("newName", "parent"); Response response = authMgmtResource.copy("browser", params); Assert.assertEquals(201, response.getStatus()); response.close(); assertAdminEvents.assertEvent(REALM_NAME, OperationType.CREATE, AdminEventPaths.authCopyFlowPath("browser"), params, ResourceType.AUTH_FLOW); params = new HashMap<>(); params.put("alias", "child"); params.put("description", "Description"); params.put("provider", "registration-page-form"); params.put("type", "basic-flow"); authMgmtResource.addExecutionFlow("parent", params); assertAdminEvents.assertEvent(REALM_NAME, OperationType.CREATE, AdminEventPaths.authAddExecutionFlowPath("parent"), params, ResourceType.AUTH_EXECUTION_FLOW); } }