From b8416dfa3eac9a309cb52492235b2bee8b19775a Mon Sep 17 00:00:00 2001 From: mposolda Date: Mon, 11 Dec 2017 22:16:11 +0100 Subject: [PATCH] KEYCLOAK-5981 Test Impersonation works when authenticationSession exists --- .../rest/TestingResourceProvider.java | 16 +++ .../rest/TestingResourceProviderFactory.java | 5 +- .../client/KeycloakTestingClient.java | 4 + .../client/resources/TestingResource.java | 5 + .../testsuite/admin/ImpersonationTest.java | 100 ++++++++++++++---- 5 files changed, 111 insertions(+), 19 deletions(-) diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java index d20d09f436..7b76083ec0 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java @@ -19,6 +19,7 @@ package org.keycloak.testsuite.rest; import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.BadRequestException; +import org.jboss.resteasy.spi.HttpRequest; import org.keycloak.common.util.Time; import org.keycloak.component.ComponentModel; import org.keycloak.events.Event; @@ -48,6 +49,7 @@ import org.keycloak.representations.idm.AuthDetailsRepresentation; import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.RealmManager; import org.keycloak.services.resource.RealmResourceProvider; import org.keycloak.services.scheduled.ClearExpiredUserSessions; @@ -78,6 +80,8 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Cookie; import javax.ws.rs.core.Response; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -96,6 +100,9 @@ public class TestingResourceProvider implements RealmResourceProvider { private final KeycloakSession session; private final Map suspendedTimerTasks; + @Context + private HttpRequest request; + @Override public Object getResource() { return this; @@ -549,6 +556,15 @@ public class TestingResourceProvider implements RealmResourceProvider { return details; } + @GET + @Path("/get-sso-cookie") + @Produces(MediaType.APPLICATION_JSON) + public String getSSOCookieValue() { + Map cookies = request.getHttpHeaders().getCookies(); + return cookies.get(AuthenticationManager.KEYCLOAK_IDENTITY_COOKIE).getValue(); + } + + @Path("/cache/{cache}") public TestCacheResource getCacheResource(@PathParam("cache") String cacheName) { return new TestCacheResource(session, cacheName); diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProviderFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProviderFactory.java index d796c2fab9..e1a1f30c64 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProviderFactory.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProviderFactory.java @@ -20,6 +20,7 @@ package org.keycloak.testsuite.rest; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.Config.Scope; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; @@ -36,7 +37,9 @@ public class TestingResourceProviderFactory implements RealmResourceProviderFact @Override public RealmResourceProvider create(KeycloakSession session) { - return new TestingResourceProvider(session, suspendedTimerTasks); + TestingResourceProvider testProvider = new TestingResourceProvider(session, suspendedTimerTasks); + ResteasyProviderFactory.getInstance().injectProperties(testProvider); + return testProvider; } @Override diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java index 7c61206b28..57b9afce0a 100755 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/KeycloakTestingClient.java @@ -56,6 +56,10 @@ public class KeycloakTestingClient { return new KeycloakTestingClient(serverUrl, null); } + public static KeycloakTestingClient getInstance(String serverUrl, ResteasyClient resteasyClient) { + return new KeycloakTestingClient(serverUrl, resteasyClient); + } + public TestingResource testing() { return target.path("/realms/master").proxy(TestingResource.class); } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java index 8b551f06bd..dc6bfaffd8 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java @@ -171,6 +171,11 @@ public interface TestingResource { @Consumes(MediaType.APPLICATION_JSON) void onAdminEvent(final AdminEventRepresentation rep, @QueryParam("includeRepresentation") boolean includeRepresentation); + @GET + @Path("/get-sso-cookie") + @Produces(MediaType.APPLICATION_JSON) + String getSSOCookieValue(); + @POST @Path("/remove-user-session") @Produces(MediaType.APPLICATION_JSON) diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java index 72421cc25a..c1e0f493a3 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java @@ -17,13 +17,18 @@ package org.keycloak.testsuite.admin; +import org.jboss.arquillian.graphene.page.Page; +import org.jboss.resteasy.client.jaxrs.ResteasyClient; +import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.keycloak.Config; import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.KeycloakBuilder; import org.keycloak.admin.client.resource.ClientResource; +import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.UserResource; import org.keycloak.events.Details; import org.keycloak.events.EventType; @@ -34,12 +39,17 @@ import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RoleRepresentation; +import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.arquillian.AuthServerTestEnricher; import org.keycloak.testsuite.auth.page.AuthRealm; +import org.keycloak.testsuite.client.KeycloakTestingClient; +import org.keycloak.testsuite.pages.AppPage; +import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.util.ClientBuilder; import org.keycloak.testsuite.util.CredentialBuilder; +import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.RealmBuilder; import org.keycloak.testsuite.util.UserBuilder; @@ -50,6 +60,7 @@ import java.util.List; import java.util.Map; import org.junit.Assume; import org.junit.BeforeClass; +import org.openqa.selenium.Cookie; /** * Tests Undertow Adapter @@ -61,6 +72,12 @@ public class ImpersonationTest extends AbstractKeycloakTest { @Rule public AssertEvents events = new AssertEvents(this); + @Page + protected AppPage appPage; + + @Page + protected LoginPage loginPage; + private String impersonatedUserId; @Override @@ -147,25 +164,66 @@ public class ImpersonationTest extends AbstractKeycloakTest { } - protected void testSuccessfulImpersonation(String admin, String adminRealm) { + // KEYCLOAK-5981 + @Test + public void testImpersonationWorksWhenAuthenticationSessionExists() throws Exception { + // Create test client + RealmResource realm = adminClient.realms().realm("test"); + Response resp = realm.clients().create(ClientBuilder.create().clientId("test-app").addRedirectUri(OAuthClient.APP_ROOT + "/*").build()); + resp.close(); - Keycloak client = login(admin, adminRealm); + // Open the URL for the client (will redirect to Keycloak server AuthorizationEndpoint and create authenticationSession) + String loginFormUrl = oauth.getLoginFormUrl(); + driver.navigate().to(loginFormUrl); + loginPage.assertCurrent(); + + // Impersonate and get SSO cookie. Setup that cookie for webDriver + String ssoCookie = testSuccessfulImpersonation("realm-admin", "test"); + driver.manage().addCookie(new Cookie(AuthenticationManager.KEYCLOAK_IDENTITY_COOKIE, ssoCookie)); + + // Open the URL again - should be directly redirected to the app due the SSO login + driver.navigate().to(loginFormUrl); + appPage.assertCurrent(); + + // Remove test client + ApiUtil.findClientByClientId(realm, "test-app").remove(); + } + + + // Return the SSO cookie from the impersonated session + protected String testSuccessfulImpersonation(String admin, String adminRealm) { + ResteasyClient resteasyClient = new ResteasyClientBuilder().connectionPoolSize(10).build(); + + // Login adminClient + Keycloak client = login(admin, adminRealm, resteasyClient); try { - Map data = client.realms().realm("test").users().get(impersonatedUserId).impersonate(); - Assert.assertNotNull(data); - Assert.assertNotNull(data.get("redirect")); + // Impersonate + impersonate(client, admin, adminRealm); - events.expect(EventType.IMPERSONATE) - .session(AssertEvents.isUUID()) - .user(impersonatedUserId) - .detail(Details.IMPERSONATOR, admin) - .detail(Details.IMPERSONATOR_REALM, adminRealm) - .client((String) null).assertEvent(); + // Get the SSO cookie. Needs to use same RestEasyClient used by adminClient to be able to see the cookies + KeycloakTestingClient testingClient = KeycloakTestingClient.getInstance(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth", resteasyClient); + String kcIdentity = testingClient.testing("test").getSSOCookieValue(); + Assert.assertNotNull(kcIdentity); + + return kcIdentity; } finally { - client.close(); + resteasyClient.close(); } } + private void impersonate(Keycloak adminClient, String admin, String adminRealm) { + Map data = adminClient.realms().realm("test").users().get(impersonatedUserId).impersonate(); + Assert.assertNotNull(data); + Assert.assertNotNull(data.get("redirect")); + + events.expect(EventType.IMPERSONATE) + .session(AssertEvents.isUUID()) + .user(impersonatedUserId) + .detail(Details.IMPERSONATOR, admin) + .detail(Details.IMPERSONATOR_REALM, adminRealm) + .client((String) null).assertEvent(); + } + protected void testForbiddenImpersonation(String admin, String adminRealm) { Keycloak client = createAdminClient(adminRealm, establishClientId(adminRealm), admin); try { @@ -178,24 +236,30 @@ public class ImpersonationTest extends AbstractKeycloakTest { } Keycloak createAdminClient(String realm, String clientId, String username) { - return createAdminClient(realm, clientId, username, null); + return createAdminClient(realm, clientId, username, null, null); } String establishClientId(String realm) { return realm.equals("master") ? Constants.ADMIN_CLI_CLIENT_ID : "myclient"; } - Keycloak createAdminClient(String realm, String clientId, String username, String password) { + Keycloak createAdminClient(String realm, String clientId, String username, String password, ResteasyClient resteasyClient) { if (password == null) { password = username.equals("admin") ? "admin" : "password"; } - return Keycloak.getInstance(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth", - realm, username, password, clientId); + + return KeycloakBuilder.builder().serverUrl(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth") + .realm(realm) + .username(username) + .password(password) + .clientId(clientId) + .resteasyClient(resteasyClient) + .build(); } - private Keycloak login(String username, String realm) { + private Keycloak login(String username, String realm, ResteasyClient resteasyClient) { String clientId = establishClientId(realm); - Keycloak client = createAdminClient(realm, clientId, username); + Keycloak client = createAdminClient(realm, clientId, username, null, resteasyClient); client.tokenManager().grantToken(); // only poll for LOGIN event if realm is not master