diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java index 84bd64cf80..3fd7712d8b 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java @@ -129,7 +129,7 @@ public abstract class AbstractKeycloakTest { private boolean resetTimeOffset; @Before - public void beforeAbstractKeycloakTest() { + public void beforeAbstractKeycloakTest() throws Exception { adminClient = Keycloak.getInstance(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth", MASTER, ADMIN, ADMIN, Constants.ADMIN_CLI_CLIENT_ID); deleteMeOAuthClient = new DeleteMeOAuthClient(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth"); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AssertEvents.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AssertEvents.java index 6df6511a5b..6a4a55492f 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AssertEvents.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AssertEvents.java @@ -64,7 +64,6 @@ public class AssertEvents { private RealmRepresentation realmRep; private AbstractKeycloakTest context; private PublicKey realmPublicKey; - private UserRepresentation defaultUser; public AssertEvents(AbstractKeycloakTest ctx) throws Exception { context = ctx; @@ -74,11 +73,6 @@ public class AssertEvents { String pubKeyString = realmRep.getPublicKey(); realmPublicKey = PemUtils.decodePublicKey(pubKeyString); - defaultUser = getUser(DEFAULT_USERNAME); - if (defaultUser == null) { - throw new RuntimeException("Default user does not exist: " + DEFAULT_USERNAME + ". Make sure to add it to your test realm."); - } - defaultEventsQueueUri = getAuthServerEventsQueueUri(); } @@ -192,7 +186,7 @@ public class AssertEvents { return new ExpectedEvent() .realm(realmRep.getId()) .client(DEFAULT_CLIENT_ID) - .user(defaultUser.getId()) + .user(defaultUserId()) .ipAddress(DEFAULT_IP_ADDRESS) .session((String) null) .event(event); @@ -357,6 +351,34 @@ public class AssertEvents { }; } + public Matcher defaultUserId() { + return new TypeSafeMatcher() { + private String userId; + + @Override + protected boolean matchesSafely(String item) { + return item.equals(getUserId()); + } + + @Override + public void describeTo(Description description) { + description.appendText(getUserId()); + } + + private String getUserId() { + if (userId == null) { + UserRepresentation user = getUser(DEFAULT_USERNAME); + if (user == null) { + throw new RuntimeException("Default user does not exist: " + DEFAULT_USERNAME + ". Make sure to add it to your test realm."); + } + userId = user.getId(); + } + return userId; + } + + }; + } + private EventRepresentation fetchNextEvent() { CloseableHttpClient httpclient = HttpClients.createDefault(); try { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ApiUtil.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ApiUtil.java index 0f8ee1f2ae..a60926eb67 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ApiUtil.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ApiUtil.java @@ -19,6 +19,7 @@ package org.keycloak.testsuite.admin; import org.jboss.logging.Logger; import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.admin.client.resource.RoleResource; import org.keycloak.admin.client.resource.UserResource; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; @@ -85,6 +86,10 @@ public class ApiUtil { return null; } + public static RoleResource findClientRoleByName(ClientResource client, String role) { + return client.roles().get(role); + } + public static UserRepresentation findUserByUsername(RealmResource realm, String username) { UserRepresentation user = null; List ur = realm.users().search(username, null, null); 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 new file mode 100755 index 0000000000..afe11a3f26 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java @@ -0,0 +1,206 @@ +/* + * 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; + +import org.junit.Assert; +import org.junit.Test; +import org.keycloak.Config; +import org.keycloak.admin.client.resource.ClientResource; +import org.keycloak.admin.client.resource.UserResource; +import org.keycloak.events.Details; +import org.keycloak.events.EventType; +import org.keycloak.models.AdminRoles; +import org.keycloak.models.Constants; +import org.keycloak.models.ImpersonationConstants; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.RoleRepresentation; +import org.keycloak.services.resources.admin.AdminRoot; +import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.AssertEvents; +import org.keycloak.testsuite.util.ClientBuilder; +import org.keycloak.testsuite.util.CredentialBuilder; +import org.keycloak.testsuite.util.OAuthClient.AccessTokenResponse; +import org.keycloak.testsuite.util.RealmBuilder; +import org.keycloak.testsuite.util.UserBuilder; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientRequestContext; +import javax.ws.rs.client.ClientRequestFilter; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriBuilder; +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * Tests Undertow Adapter + * + * @author Bill Burke + */ +public class ImpersonationTest extends AbstractKeycloakTest { + + private AssertEvents events; + + private String impersonatedUserId; + + @Override + public void beforeAbstractKeycloakTest() throws Exception { + super.beforeAbstractKeycloakTest(); + events = new AssertEvents(this); + } + + @Override + public void addTestRealms(List testRealms) { + RealmBuilder realm = RealmBuilder.create().name("test").testEventListener(); + + realm.client(ClientBuilder.create().clientId("myclient").publicClient().directAccessGrants()); + + impersonatedUserId = KeycloakModelUtils.generateId(); + + realm.user(UserBuilder.create().id(impersonatedUserId).username("test-user@localhost")); + realm.user(UserBuilder.create().username("realm-admin").password("password").role(Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.REALM_ADMIN)); + realm.user(UserBuilder.create().username("impersonator").password("password").role(Constants.REALM_MANAGEMENT_CLIENT_ID, ImpersonationConstants.IMPERSONATION_ROLE).role(Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.VIEW_USERS)); + realm.user(UserBuilder.create().username("bad-impersonator").password("password").role(Constants.REALM_MANAGEMENT_CLIENT_ID, AdminRoles.MANAGE_USERS)); + + testRealms.add(realm.build()); + } + + private String createAdminToken(String username, String realm) { + try { + String password = username.equals("admin") ? "admin" : "password"; + String clientId = realm.equals("master") ? Constants.ADMIN_CLI_CLIENT_ID : "myclient"; + AccessTokenResponse tokenResponse = oauthClient.doGrantAccessTokenRequest(realm, username, password, null, clientId, null); + if (tokenResponse.getStatusCode() != 200) { + throw new RuntimeException("Failed to get token: " + tokenResponse.getErrorDescription()); + } + events.clear(); + return tokenResponse.getAccessToken(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + public void testImpersonateByMasterAdmin() { + // test that composite is set up right for impersonation role + testSuccessfulImpersonation("admin", Config.getAdminRealm()); + } + + @Test + public void testImpersonateByMasterImpersonator() { + Response response = adminClient.realm("master").users().create(UserBuilder.create().username("master-impersonator").build()); + String userId = ApiUtil.getCreatedId(response); + response.close(); + + UserResource user = adminClient.realm("master").users().get(userId); + user.resetPassword(CredentialBuilder.create().password("password").build()); + + ClientResource testRealmClient = ApiUtil.findClientResourceByClientId(adminClient.realm("master"), "test-realm"); + + List roles = new LinkedList<>(); + roles.add(ApiUtil.findClientRoleByName(testRealmClient, AdminRoles.VIEW_USERS).toRepresentation()); + roles.add(ApiUtil.findClientRoleByName(testRealmClient, ImpersonationConstants.IMPERSONATION_ROLE).toRepresentation()); + + user.roles().clientLevel(testRealmClient.toRepresentation().getId()).add(roles); + + testSuccessfulImpersonation("master-impersonator", Config.getAdminRealm()); + + adminClient.realm("master").users().get(userId).remove(); + } + + @Test + public void testImpersonateByTestImpersonator() { + testSuccessfulImpersonation("impersonator", "test"); + } + + @Test + public void testImpersonateByTestAdmin() { + // test that composite is set up right for impersonation role + testSuccessfulImpersonation("realm-admin", "test"); + } + + @Test + public void testImpersonateByTestBadImpersonator() { + testForbiddenImpersonation("bad-impersonator", "test"); + } + + @Test + public void testImpersonateByMastertBadImpersonator() { + Response response = adminClient.realm("master").users().create(UserBuilder.create().username("master-bad-impersonator").build()); + String userId = ApiUtil.getCreatedId(response); + response.close(); + adminClient.realm("master").users().get(userId).resetPassword(CredentialBuilder.create().password("password").build()); + + testForbiddenImpersonation("master-bad-impersonator", Config.getAdminRealm()); + + adminClient.realm("master").users().get(userId).remove(); + } + + + + protected void testSuccessfulImpersonation(String admin, String adminRealm) { + Client client = createClient(admin, adminRealm); + WebTarget impersonate = createImpersonateTarget(client); + Map data = impersonate.request().post(null, Map.class); + Assert.assertNotNull(data); + Assert.assertNotNull(data.get("redirect")); + + // TODO Events not working + events.expect(EventType.IMPERSONATE) + .session(AssertEvents.isUUID()) + .user(impersonatedUserId) + .detail(Details.IMPERSONATOR, admin) + .detail(Details.IMPERSONATOR_REALM, adminRealm) + .client((String) null).assertEvent(); + + client.close(); + } + + protected void testForbiddenImpersonation(String admin, String adminRealm) { + Client client = createClient(admin, adminRealm); + WebTarget impersonate = createImpersonateTarget(client); + Response response = impersonate.request().post(null); + response.close(); + client.close(); + } + + + protected WebTarget createImpersonateTarget(Client client) { + UriBuilder authBase = UriBuilder.fromUri(getAuthServerRoot()); + WebTarget adminRealms = client.target(AdminRoot.realmsUrl(authBase)); + WebTarget realmTarget = adminRealms.path("test"); + return realmTarget.path("users").path(impersonatedUserId).path("impersonation"); + } + + protected Client createClient(String admin, String adminRealm) { + String token = createAdminToken(admin, adminRealm); + final String authHeader = "Bearer " + token; + ClientRequestFilter authFilter = new ClientRequestFilter() { + @Override + public void filter(ClientRequestContext requestContext) throws IOException { + requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader); + } + }; + return javax.ws.rs.client.ClientBuilder.newBuilder().register(authFilter).build(); + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ClientBuilder.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ClientBuilder.java new file mode 100644 index 0000000000..a0b3bf7a4e --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/ClientBuilder.java @@ -0,0 +1,66 @@ +/* + * 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.util; + +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.UserRepresentation; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +/** + * @author Stian Thorgersen + */ +public class ClientBuilder { + + private ClientRepresentation rep = new ClientRepresentation(); + + public static ClientBuilder create() { + return new ClientBuilder(); + } + + private ClientBuilder() { + rep.setEnabled(true); + } + + public ClientBuilder id(String id) { + rep.setId(id); + return this; + } + + public ClientBuilder clientId(String clientId) { + rep.setClientId(clientId); + return this; + } + + public ClientBuilder publicClient() { + rep.setPublicClient(true); + return this; + } + + public ClientBuilder directAccessGrants() { + rep.setDirectAccessGrantsEnabled(true); + return this; + } + + public ClientRepresentation build() { + return rep; + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/CredentialBuilder.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/CredentialBuilder.java new file mode 100644 index 0000000000..038f4fbcb4 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/CredentialBuilder.java @@ -0,0 +1,51 @@ +/* + * 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.util; + +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.representations.idm.UserRepresentation; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +/** + * @author Stian Thorgersen + */ +public class CredentialBuilder { + + private CredentialRepresentation rep = new CredentialRepresentation(); + + public static CredentialBuilder create() { + return new CredentialBuilder(); + } + + private CredentialBuilder() { + } + + public CredentialBuilder password(String password) { + rep.setType(CredentialRepresentation.PASSWORD); + rep.setValue(password); + return this; + } + + public CredentialRepresentation build() { + return rep; + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RealmBuilder.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RealmBuilder.java new file mode 100644 index 0000000000..8859f038e9 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RealmBuilder.java @@ -0,0 +1,83 @@ +/* + * 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.util; + +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; + +import java.util.LinkedList; + +/** + * @author Stian Thorgersen + */ +public class RealmBuilder { + + private RealmRepresentation rep = new RealmRepresentation(); + + public static RealmBuilder create() { + return new RealmBuilder(); + } + + private RealmBuilder() { + rep.setEnabled(true); + } + + public RealmBuilder name(String name) { + rep.setRealm(name); + return this; + } + + public RealmBuilder testEventListener() { + if (rep.getEventsListeners() == null) { + rep.setEventsListeners(new LinkedList()); + } + + rep.getEventsListeners().add("event-queue"); + return this; + } + + public RealmBuilder client(ClientBuilder client) { + return client(client.build()); + } + + public RealmBuilder client(ClientRepresentation client) { + if (rep.getClients() == null) { + rep.setClients(new LinkedList()); + } + rep.getClients().add(client); + return this; + } + + public RealmBuilder user(UserBuilder user) { + return user(user.build()); + } + + public RealmBuilder user(UserRepresentation user) { + if (rep.getUsers() == null) { + rep.setUsers(new LinkedList()); + } + rep.getUsers().add(user); + return this; + } + + public RealmRepresentation build() { + return rep; + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RoleBuilder.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RoleBuilder.java new file mode 100644 index 0000000000..d46d1602b6 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/RoleBuilder.java @@ -0,0 +1,50 @@ +/* + * 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.util; + +import org.keycloak.representations.idm.RoleRepresentation; + +/** + * @author Stian Thorgersen + */ +public class RoleBuilder { + + private RoleRepresentation rep = new RoleRepresentation(); + + public static RoleBuilder create() { + return new RoleBuilder(); + } + + private RoleBuilder() { + } + + public RoleBuilder id(String id) { + rep.setId(id); + return this; + } + + public RoleBuilder name(String name) { + rep.setName(name); + return this; + } + + public RoleRepresentation build() { + return rep; + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/UserBuilder.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/UserBuilder.java new file mode 100644 index 0000000000..e976a4591a --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/UserBuilder.java @@ -0,0 +1,87 @@ +/* + * 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.util; + +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.representations.idm.UserRepresentation; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +/** + * @author Stian Thorgersen + */ +public class UserBuilder { + + private UserRepresentation rep = new UserRepresentation(); + + public static UserBuilder create() { + return new UserBuilder(); + } + + private UserBuilder() { + rep.setEnabled(true); + } + + public UserBuilder id(String id) { + rep.setId(id); + return this; + } + + public UserBuilder username(String username) { + rep.setUsername(username); + return this; + } + + public UserBuilder password(String password) { + if (rep.getCredentials() == null) { + rep.setCredentials(new LinkedList()); + } + + CredentialRepresentation credential = new CredentialRepresentation(); + credential.setType(CredentialRepresentation.PASSWORD); + credential.setValue(password); + + rep.getCredentials().add(credential); + return this; + } + + public UserBuilder role(String role) { + if (rep.getRealmRoles() == null) { + rep.setRealmRoles(new LinkedList()); + } + return this; + } + + public UserBuilder role(String client, String role) { + if (rep.getClientRoles() == null) { + rep.setClientRoles(new HashMap>()); + } + if (rep.getClientRoles().get(client) == null) { + rep.getClientRoles().put(client, new LinkedList()); + } + rep.getClientRoles().get(client).add(role); + return this; + } + + public UserRepresentation build() { + return rep; + } + +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java deleted file mode 100755 index 35f3849a14..0000000000 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * 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; - -import org.junit.Assert; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.keycloak.Config; -import org.keycloak.events.Details; -import org.keycloak.events.EventType; -import org.keycloak.models.AdminRoles; -import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientSessionModel; -import org.keycloak.models.Constants; -import org.keycloak.models.ImpersonationConstants; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.UserSessionModel; -import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.protocol.oidc.OIDCLoginProtocol; -import org.keycloak.protocol.oidc.TokenManager; -import org.keycloak.representations.AccessToken; -import org.keycloak.services.managers.RealmManager; -import org.keycloak.services.resources.admin.AdminRoot; -import org.keycloak.testsuite.AssertEvents; -import org.keycloak.testsuite.rule.KeycloakRule; - -import javax.ws.rs.client.Client; -import javax.ws.rs.client.ClientBuilder; -import javax.ws.rs.client.ClientRequestContext; -import javax.ws.rs.client.ClientRequestFilter; -import javax.ws.rs.client.WebTarget; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriBuilder; -import java.io.IOException; -import java.util.Map; - -/** - * Tests Undertow Adapter - * - * @author Bill Burke - */ -public class ImpersonationTest { - - static String impersonatedUserId; - - @ClassRule - public static KeycloakRule keycloakRule = new KeycloakRule( new KeycloakRule.KeycloakSetup() { - - @Override - public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { - impersonatedUserId = manager.getSession().users().getUserByUsername("test-user@localhost", appRealm).getId(); - { - UserModel masterImpersonator = manager.getSession().users().addUser(adminstrationRealm, "master-impersonator"); - masterImpersonator.setEnabled(true); - ClientModel adminRealmClient = adminstrationRealm.getClientByClientId(KeycloakModelUtils.getMasterRealmAdminApplicationClientId(appRealm.getName())); - RoleModel masterManageUsersRole = adminRealmClient.getRole(AdminRoles.MANAGE_USERS); - masterImpersonator.grantRole(masterManageUsersRole); - - RoleModel masterImpersonatorRole = adminRealmClient.getRole(ImpersonationConstants.IMPERSONATION_ROLE); - masterImpersonator.grantRole(masterImpersonatorRole); - } - { - UserModel masterBadImpersonator = manager.getSession().users().addUser(adminstrationRealm, "master-bad-impersonator"); - masterBadImpersonator.setEnabled(true); - } - - { - UserModel impersonator = manager.getSession().users().addUser(appRealm, "impersonator"); - impersonator.setEnabled(true); - ClientModel appRealmClient = appRealm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); - - RoleModel masterManageUsersRole = appRealmClient.getRole(AdminRoles.MANAGE_USERS); - impersonator.grantRole(masterManageUsersRole); - - RoleModel realmImpersonatorRole = appRealmClient.getRole(ImpersonationConstants.IMPERSONATION_ROLE); - impersonator.grantRole(realmImpersonatorRole); - } - { - UserModel impersonator = manager.getSession().users().addUser(appRealm, "realm-admin"); - impersonator.setEnabled(true); - ClientModel appRealmClient = appRealm.getClientByClientId(Constants.REALM_MANAGEMENT_CLIENT_ID); - RoleModel realmImpersonatorRole = appRealmClient.getRole(AdminRoles.REALM_ADMIN); - impersonator.grantRole(realmImpersonatorRole); - } - { - UserModel badimpersonator = manager.getSession().users().addUser(appRealm, "bad-impersonator"); - badimpersonator.setEnabled(true); - } - } - }); - - @Rule - public AssertEvents events = new AssertEvents(keycloakRule); - - - private static String createAdminToken(String username, String realm) { - KeycloakSession session = keycloakRule.startSession(); - try { - RealmManager manager = new RealmManager(session); - - RealmModel adminRealm = manager.getRealm(realm); - ClientModel adminConsole = adminRealm.getClientByClientId(Constants.ADMIN_CLI_CLIENT_ID); - TokenManager tm = new TokenManager(); - UserModel admin = session.users().getUserByUsername(username, adminRealm); - ClientSessionModel clientSession = session.sessions().createClientSession(adminRealm, adminConsole); - clientSession.setNote(OIDCLoginProtocol.ISSUER, "http://localhost:8081/auth/realms/" + realm); - UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false, null, null); - AccessToken token = tm.createClientAccessToken(session, tm.getAccess(null, true, adminConsole, admin), adminRealm, adminConsole, admin, userSession, clientSession); - return tm.encodeToken(adminRealm, token); - } finally { - keycloakRule.stopSession(session, true); - } - } - - @Test - public void testImpersonateByMasterAdmin() { - // test that composite is set up right for impersonation role - testSuccessfulImpersonation("admin", Config.getAdminRealm()); - } - - @Test - public void testImpersonateByMasterImpersonator() { - testSuccessfulImpersonation("master-impersonator", Config.getAdminRealm()); - } - - @Test - public void testImpersonateByTestImpersonator() { - testSuccessfulImpersonation("impersonator", "test"); - } - - @Test - public void testImpersonateByTestAdmin() { - // test that composite is set up right for impersonation role - testSuccessfulImpersonation("realm-admin", "test"); - } - - @Test - public void testImpersonateByTestBadImpersonator() { - testForbiddenImpersonation("bad-impersonator", "test"); - } - - @Test - public void testImpersonateByMastertBadImpersonator() { - testForbiddenImpersonation("master-bad-impersonator", Config.getAdminRealm()); - } - - - - protected void testSuccessfulImpersonation(String admin, String adminRealm) { - Client client = createClient(admin, adminRealm); - WebTarget impersonate = createImpersonateTarget(client); - Map data = impersonate.request().post(null, Map.class); - 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(); - - client.close(); - } - - protected void testForbiddenImpersonation(String admin, String adminRealm) { - Client client = createClient(admin, adminRealm); - WebTarget impersonate = createImpersonateTarget(client); - Response response = impersonate.request().post(null); - response.close(); - client.close(); - } - - - protected WebTarget createImpersonateTarget(Client client) { - UriBuilder authBase = UriBuilder.fromUri("http://localhost:8081/auth"); - WebTarget adminRealms = client.target(AdminRoot.realmsUrl(authBase)); - WebTarget realmTarget = adminRealms.path("test"); - return realmTarget.path("users").path(impersonatedUserId).path("impersonation"); - } - - protected Client createClient(String admin, String adminRealm) { - String token = createAdminToken(admin, adminRealm); - final String authHeader = "Bearer " + token; - ClientRequestFilter authFilter = new ClientRequestFilter() { - @Override - public void filter(ClientRequestContext requestContext) throws IOException { - requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader); - } - }; - return ClientBuilder.newBuilder().register(authFilter).build(); - } - -}