/* * 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.forms; import org.jboss.arquillian.graphene.page.Page; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.keycloak.OAuth2Constants; import org.keycloak.admin.client.resource.AuthenticationManagementResource; import org.keycloak.authentication.AuthenticationFlow; import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.representations.AccessToken; import org.keycloak.representations.RefreshToken; import org.keycloak.representations.idm.AuthenticationExecutionRepresentation; import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.pages.AppPage; import org.keycloak.testsuite.pages.AppPage.RequestType; import org.keycloak.testsuite.pages.ErrorPage; import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.LoginPasswordUpdatePage; import org.keycloak.testsuite.pages.RegisterPage; import org.keycloak.testsuite.pages.TermsAndConditionsPage; import org.keycloak.testsuite.rest.representation.AuthenticatorState; import org.keycloak.testsuite.util.AdminEventPaths; import org.keycloak.testsuite.util.ClientBuilder; import org.keycloak.testsuite.util.ExecutionBuilder; import org.keycloak.testsuite.util.FlowBuilder; import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.RealmRepUtil; import org.keycloak.testsuite.util.UserBuilder; import javax.ws.rs.core.Response; import java.util.HashMap; import java.util.List; import java.util.Map; import static org.junit.Assert.assertEquals; import static org.keycloak.testsuite.util.Matchers.statusCodeIs; /** * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. */ public class CustomFlowTest extends AbstractFlowTest { @Override public void configureTestRealm(RealmRepresentation testRealm) { UserRepresentation user = UserBuilder.create() .username("login-test") .email("login@test.com") .enabled(true) .build(); testRealm.getUsers().add(user); // Set passthrough clientAuthenticator for our clients ClientRepresentation dummyClient = ClientBuilder.create() .clientId("dummy-client") .name("dummy-client") .authenticatorType(PassThroughClientAuthenticator.PROVIDER_ID) .directAccessGrants() .build(); testRealm.getClients().add(dummyClient); ClientRepresentation testApp = RealmRepUtil.findClientByClientId(testRealm, "test-app"); testApp.setClientAuthenticatorType(PassThroughClientAuthenticator.PROVIDER_ID); testApp.setDirectAccessGrantsEnabled(true); } @Before public void configureFlows() { userId = findUser("login-test").getId(); // Do this just once per class if (testContext.isInitialized()) { return; } AuthenticationFlowRepresentation flow = FlowBuilder.create() .alias("dummy") .description("dummy pass through flow") .providerId("basic-flow") .topLevel(true) .builtIn(false) .build(); testRealm().flows().createFlow(flow); RealmRepresentation realm = testRealm().toRepresentation(); realm.setBrowserFlow(flow.getAlias()); realm.setDirectGrantFlow(flow.getAlias()); testRealm().update(realm); // refresh flow to find its id flow = findFlowByAlias(flow.getAlias()); AuthenticationExecutionRepresentation execution = ExecutionBuilder.create() .parentFlow(flow.getId()) .requirement(AuthenticationExecutionModel.Requirement.REQUIRED.toString()) .authenticator(PassThroughAuthenticator.PROVIDER_ID) .priority(10) .authenticatorFlow(false) .build(); testRealm().flows().addExecution(execution); flow = FlowBuilder.create() .alias("dummy registration") .description("dummy pass through registration") .providerId("basic-flow") .topLevel(true) .builtIn(false) .build(); testRealm().flows().createFlow(flow); setRegistrationFlow(flow); // refresh flow to find its id flow = findFlowByAlias(flow.getAlias()); execution = ExecutionBuilder.create() .parentFlow(flow.getId()) .requirement(AuthenticationExecutionModel.Requirement.REQUIRED.toString()) .authenticator(PassThroughRegistration.PROVIDER_ID) .priority(10) .authenticatorFlow(false) .build(); testRealm().flows().addExecution(execution); AuthenticationFlowRepresentation clientFlow = FlowBuilder.create() .alias("client-dummy") .description("dummy pass through flow") .providerId(AuthenticationFlow.CLIENT_FLOW) .topLevel(true) .builtIn(false) .build(); testRealm().flows().createFlow(clientFlow); realm = testRealm().toRepresentation(); realm.setClientAuthenticationFlow(clientFlow.getAlias()); testRealm().update(realm); // refresh flow to find its id clientFlow = findFlowByAlias(clientFlow.getAlias()); execution = ExecutionBuilder.create() .parentFlow(clientFlow.getId()) .requirement(AuthenticationExecutionModel.Requirement.REQUIRED.toString()) .authenticator(PassThroughClientAuthenticator.PROVIDER_ID) .priority(10) .authenticatorFlow(false) .build(); testRealm().flows().addExecution(execution); testContext.setInitialized(true); } @Rule public AssertEvents events = new AssertEvents(this); @Page protected AppPage appPage; @Page protected LoginPage loginPage; @Page protected ErrorPage errorPage; @Page protected TermsAndConditionsPage termsPage; @Page protected LoginPasswordUpdatePage updatePasswordPage; @Page protected RegisterPage registerPage; private static String userId; /** * KEYCLOAK-3506 * * @throws Exception */ @Test public void testRequiredAfterAlternative() throws Exception { AuthenticationManagementResource authMgmtResource = testRealm().flows(); Map<String, String> params = new HashMap(); String flowAlias = "Browser Flow With Extra"; params.put("newName", flowAlias); Response response = authMgmtResource.copy("browser", params); String flowId = null; try { Assert.assertThat("Copy flow", response, statusCodeIs(Response.Status.CREATED)); AuthenticationFlowRepresentation newFlow = findFlowByAlias(flowAlias); flowId = newFlow.getId(); } finally { response.close(); } AuthenticationExecutionRepresentation execution = ExecutionBuilder.create() .parentFlow(flowId) .requirement(AuthenticationExecutionModel.Requirement.REQUIRED.toString()) .authenticator(ClickThroughAuthenticator.PROVIDER_ID) .priority(10) .authenticatorFlow(false) .build(); testRealm().flows().addExecution(execution); RealmRepresentation rep = testRealm().toRepresentation(); rep.setBrowserFlow(flowAlias); testRealm().update(rep); rep = testRealm().toRepresentation(); Assert.assertEquals(flowAlias, rep.getBrowserFlow()); loginPage.open(); String url = driver.getCurrentUrl(); // test to make sure we aren't skipping anything loginPage.login("test-user@localhost", "bad-password"); Assert.assertTrue(loginPage.isCurrent()); loginPage.login("test-user@localhost", "password"); Assert.assertTrue(termsPage.isCurrent()); // Revert dummy flow rep.setBrowserFlow("dummy"); testRealm().update(rep); } @Test public void loginSuccess() { AuthenticatorState state = new AuthenticatorState(); state.setUsername("login-test"); state.setClientId("test-app"); testingClient.testing().updateAuthenticator(state); oauth.openLoginForm(); Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent(); } @Test public void grantTest() throws Exception { AuthenticatorState state = new AuthenticatorState(); state.setUsername("login-test"); state.setClientId("test-app"); testingClient.testing().updateAuthenticator(state); grantAccessToken("test-app", "login-test"); } @Test public void clientAuthTest() throws Exception { AuthenticatorState state = new AuthenticatorState(); state.setClientId("dummy-client"); state.setUsername("login-test"); testingClient.testing().updateAuthenticator(state); grantAccessToken("dummy-client", "login-test"); state.setClientId("test-app"); testingClient.testing().updateAuthenticator(state); grantAccessToken("test-app", "login-test"); state.setClientId("unknown"); testingClient.testing().updateAuthenticator(state); OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "test-user", "password"); assertEquals(400, response.getStatusCode()); assertEquals("unauthorized_client", response.getError()); events.expectLogin() .client((String) null) .user((String) null) .session((String) null) .removeDetail(Details.CODE_ID) .removeDetail(Details.REDIRECT_URI) .removeDetail(Details.CONSENT) .error(Errors.INVALID_CLIENT_CREDENTIALS) .assertEvent(); state.setClientId("test-app"); testingClient.testing().updateAuthenticator(state); } private void grantAccessToken(String clientId, String login) throws Exception { OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", login, "password"); assertEquals(200, response.getStatusCode()); AccessToken accessToken = oauth.verifyToken(response.getAccessToken()); RefreshToken refreshToken = oauth.verifyRefreshToken(response.getRefreshToken()); events.expectLogin() .client(clientId) .user(userId) .session(accessToken.getSessionState()) .detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD) .detail(Details.TOKEN_ID, accessToken.getId()) .detail(Details.REFRESH_TOKEN_ID, refreshToken.getId()) .detail(Details.USERNAME, login) .detail(Details.CLIENT_AUTH_METHOD, PassThroughClientAuthenticator.PROVIDER_ID) .removeDetail(Details.CODE_ID) .removeDetail(Details.REDIRECT_URI) .removeDetail(Details.CONSENT) .assertEvent(); assertEquals(accessToken.getSessionState(), refreshToken.getSessionState()); OAuthClient.AccessTokenResponse refreshedResponse = oauth.doRefreshTokenRequest(response.getRefreshToken(), "password"); AccessToken refreshedAccessToken = oauth.verifyToken(refreshedResponse.getAccessToken()); RefreshToken refreshedRefreshToken = oauth.verifyRefreshToken(refreshedResponse.getRefreshToken()); assertEquals(accessToken.getSessionState(), refreshedAccessToken.getSessionState()); assertEquals(accessToken.getSessionState(), refreshedRefreshToken.getSessionState()); events.expectRefresh(refreshToken.getId(), refreshToken.getSessionState()) .user(userId) .client(clientId) .detail(Details.CLIENT_AUTH_METHOD, PassThroughClientAuthenticator.PROVIDER_ID) .assertEvent(); } }