From fafaf2c4902ab170f9a7462da18795a12d83d24f Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Sun, 12 Jul 2015 10:12:19 -0400 Subject: [PATCH 1/2] test impersonate --- .../java/org/keycloak/events/Details.java | 2 + .../java/org/keycloak/events/EventType.java | 3 +- .../models/ImpersonationConstants.java | 1 + .../resources/admin/UsersResource.java | 11 + .../testsuite/admin/ImpersonationTest.java | 212 ++++++++++++++++++ 5 files changed, 228 insertions(+), 1 deletion(-) create mode 100755 testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java diff --git a/events/api/src/main/java/org/keycloak/events/Details.java b/events/api/src/main/java/org/keycloak/events/Details.java index 73d98ffb70..38ff34078a 100755 --- a/events/api/src/main/java/org/keycloak/events/Details.java +++ b/events/api/src/main/java/org/keycloak/events/Details.java @@ -32,5 +32,7 @@ public interface Details { String CONSENT_VALUE_NO_CONSENT_REQUIRED = "no_consent_required"; // No consent is required by client String CONSENT_VALUE_CONSENT_GRANTED = "consent_granted"; // Consent granted by user String CONSENT_VALUE_PERSISTED_CONSENT = "persistent_consent"; // Persistent consent used (was already granted by user before) + String IMPERSONATOR_REALM = "impersonator_realm"; + String IMPERSONATOR = "impersonator"; } diff --git a/events/api/src/main/java/org/keycloak/events/EventType.java b/events/api/src/main/java/org/keycloak/events/EventType.java index dc40379d82..be72f0623b 100755 --- a/events/api/src/main/java/org/keycloak/events/EventType.java +++ b/events/api/src/main/java/org/keycloak/events/EventType.java @@ -62,7 +62,8 @@ public enum EventType { IDENTITY_PROVIDER_RETRIEVE_TOKEN(false), IDENTITY_PROVIDER_RETRIEVE_TOKEN_ERROR(false), IDENTITY_PROVIDER_ACCCOUNT_LINKING(false), - IDENTITY_PROVIDER_ACCCOUNT_LINKING_ERROR(false); + IDENTITY_PROVIDER_ACCCOUNT_LINKING_ERROR(false), + IMPERSONATE(true); private boolean saveByDefault; diff --git a/model/api/src/main/java/org/keycloak/models/ImpersonationConstants.java b/model/api/src/main/java/org/keycloak/models/ImpersonationConstants.java index d051f0180b..a5cb38500e 100755 --- a/model/api/src/main/java/org/keycloak/models/ImpersonationConstants.java +++ b/model/api/src/main/java/org/keycloak/models/ImpersonationConstants.java @@ -10,6 +10,7 @@ import org.keycloak.models.utils.KeycloakModelUtils; public class ImpersonationConstants { public static String IMPERSONATION_ROLE = "impersonation"; + public static void setupMasterRealmRole(RealmProvider model, RealmModel realm) { RealmModel adminRealm; RoleModel adminRole; diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java index e84d5ae0ce..43f50e1135 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java @@ -8,6 +8,9 @@ import org.keycloak.ClientConnection; import org.keycloak.authentication.RequiredActionProvider; import org.keycloak.email.EmailException; import org.keycloak.email.EmailProvider; +import org.keycloak.events.Details; +import org.keycloak.events.EventBuilder; +import org.keycloak.events.EventType; import org.keycloak.events.admin.OperationType; import org.keycloak.models.ClientModel; import org.keycloak.models.ClientSessionModel; @@ -293,12 +296,20 @@ public class UsersResource { AuthenticationManager.expireRememberMeCookie(realm, uriInfo, clientConnection); AuthenticationManager.backchannelLogout(session, authenticatedRealm, userSession, uriInfo, clientConnection, headers, true); } + EventBuilder event = new EventBuilder(realm, session, clientConnection); + UserSessionModel userSession = session.sessions().createUserSession(realm, user, user.getUsername(), clientConnection.getRemoteAddr(), "impersonate", false, null, null); AuthenticationManager.createLoginCookie(realm, userSession.getUser(), userSession, uriInfo, clientConnection); URI redirect = AccountService.accountServiceApplicationPage(uriInfo).build(realm.getName()); Map result = new HashMap<>(); result.put("sameRealm", sameRealm); result.put("redirect", redirect.toString()); + event.event(EventType.IMPERSONATE) + .session(userSession) + .user(user) + .detail(Details.IMPERSONATOR_REALM,authenticatedRealm.getName()) + .detail(Details.IMPERSONATOR, auth.getAuth().getUser().getUsername()).success(); + return result; } 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 new file mode 100755 index 0000000000..a73d9cebc2 --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java @@ -0,0 +1,212 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012, Red Hat, Inc., and individual contributors + * as indicated by the @author tags. See the copyright.txt file in the + * distribution for a full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +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.GenericType; +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)); + 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 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_CONSOLE_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, 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(); + } + +} From b3211fa8c261495e17f10ee0d8b7be028f12b471 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Sun, 12 Jul 2015 10:24:15 -0400 Subject: [PATCH 2/2] tooltip --- .../theme/base/admin/resources/partials/user-detail.html | 2 +- .../theme/base/admin/resources/partials/user-list.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html index 10a2e720d2..270865f12e 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html @@ -119,7 +119,7 @@
- +
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-list.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-list.html index dc2c4ac7e7..a846fe8b5b 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-list.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-list.html @@ -50,7 +50,7 @@ {{user.lastName}} {{user.firstName}} {{user.email}} - + Please enter a search, or click on view all users