Merge pull request #1442 from patriot1burke/master

impersonation in admin console
This commit is contained in:
Bill Burke 2015-07-11 11:36:01 -04:00
commit aca799b28c
19 changed files with 125 additions and 289 deletions

View file

@ -79,6 +79,9 @@ module.controller('GlobalCtrl', function($scope, $http, Auth, WhoAmI, Current, $
get manageEvents() {
return getAccess('manage-events');
},
get impersonation() {
return getAccess('impersonation');
}
}

View file

@ -210,7 +210,7 @@ module.controller('UserConsentsCtrl', function($scope, realm, user, userConsents
});
module.controller('UserListCtrl', function($scope, realm, User) {
module.controller('UserListCtrl', function($scope, realm, User, UserImpersonation) {
$scope.realm = realm;
$scope.page = 0;
@ -220,6 +220,16 @@ module.controller('UserListCtrl', function($scope, realm, User) {
first : 0
}
$scope.impersonate = function(userId) {
UserImpersonation.save({realm : realm.realm, user: userId}, function (data) {
if (data.sameRealm) {
window.location = data.redirect;
} else {
window.open(data.redirect, "_blank");
}
});
};
$scope.firstPage = function() {
$scope.query.first = 0;
$scope.searchQuery();
@ -251,7 +261,7 @@ module.controller('UserListCtrl', function($scope, realm, User) {
module.controller('UserDetailCtrl', function($scope, realm, user, User, UserFederationInstances, RequiredActions, $location, Dialog, Notifications) {
module.controller('UserDetailCtrl', function($scope, realm, user, User, UserFederationInstances, UserImpersonation, RequiredActions, $location, Dialog, Notifications) {
$scope.realm = realm;
$scope.create = !user.id;
$scope.editUsername = $scope.create || $scope.realm.editUsernameAllowed;
@ -264,7 +274,17 @@ module.controller('UserDetailCtrl', function($scope, realm, user, User, UserFede
}
convertAttributeValuesToString(user);
$scope.user = angular.copy(user);
$scope.impersonate = function() {
UserImpersonation.save({realm : realm.realm, user: $scope.user.id}, function (data) {
if (data.sameRealm) {
window.location = data.redirect;
} else {
window.open(data.redirect, "_blank");
}
});
};
if(user.federationLink) {
console.log("federationLink is not null");
UserFederationInstances.get({realm : realm.realm, instance: user.federationLink}, function(link) {

View file

@ -328,6 +328,13 @@ module.factory('UserConsents', function($resource) {
});
});
module.factory('UserImpersonation', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/users/:user/impersonation', {
realm : '@realm',
user : '@user'
});
});
module.factory('UserCredentials', function($resource) {
var credentials = {};

View file

@ -8,8 +8,8 @@
<kc-tabs-user></kc-tabs-user>
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageUsers">
<div class="form-group">
<form class="form-horizontal" name="realmForm" novalidate>
<div class="form-group" kc-read-only="!access.manageUsers">
<label class="col-md-2 control-label" class="control-label">Realm Roles</label>
<div class="col-md-10">
@ -56,7 +56,7 @@
<span>Client Roles</span>
<select class="form-control" id="clients" name="clients" ng-change="changeClient()" ng-model="client" ng-options="a.clientId for a in clients" ng-disabled="false"></select>
</label>
<div class="col-md-10">
<div class="col-md-10" kc-read-only="!access.manageUsers">
<div class="row" data-ng-hide="client">
<div class="col-md-4"><span class="text-muted">Select client to view roles for client</span></div>
</div>

View file

@ -5,8 +5,8 @@
<li data-ng-show="create">Add User</li>
</ol>
<h1 data-ng-hide="create">{{user.username|capitalize}}<i style="padding-left: 20px" class="pficon pficon-delete" data-ng-show="!create && access.manageUsers"
data-ng-hide="changed" data-ng-click="remove()"></i></h1>
<h1 data-ng-hide="create">{{user.username|capitalize}}<i style="padding-left: 20px" class="pficon pficon-delete" data-ng-show="!create && access.manageUsers && !changed"
data-ng-click="remove()"></i></h1>
<h1 data-ng-show="create">Add User</h1>
<kc-tabs-user></kc-tabs-user>
@ -66,7 +66,7 @@
<div class="form-group clearfix block">
<label class="col-md-2 control-label" for="userEnabled">User Enabled</label>
<div class="col-md-6">
<input ng-model="user.enabled" name="userEnabled" id="userEnabled" onoffswitch />
<input ng-model="user.enabled" name="userEnabled" id="userEnabled" ng-disabled="!access.manageUsers" onoffswitch />
</div>
<kc-tooltip>A disabled user cannot login.</kc-tooltip>
</div>
@ -79,7 +79,7 @@
<div class="form-group clearfix block">
<label class="col-md-2 control-label" for="emailVerified">Email verified</label>
<div class="col-md-6">
<input ng-model="user.emailVerified" name="emailVerified" id="emailVerified" onoffswitch />
<input ng-model="user.emailVerified" name="emailVerified" id="emailVerified" ng-disabled="!access.manageUsers" onoffswitch />
</div>
<kc-tooltip>Has the user's email been verified?</kc-tooltip>
</div>
@ -116,9 +116,10 @@
<button kc-cancel data-ng-click="cancel()">Cancel</button>
</div>
<div class="col-md-10 col-md-offset-2" data-ng-show="!create && access.manageUsers">
<button kc-save data-ng-show="changed">Save</button>
<button kc-reset data-ng-show="changed">Cancel</button>
<div class="col-md-10 col-md-offset-2" data-ng-show="!create">
<button kc-save data-ng-show="access.manageUsers && changed">Save</button>
<button kc-reset data-ng-show="access.manageUsers && changed">Cancel</button>
<button data-ng-show="access.impersonation" class="btn btn-default" data-ng-click="impersonate()">Impersonate</button>
</div>
</div>

View file

@ -5,7 +5,7 @@
<caption data-ng-show="users" class="hidden">Table of realm users</caption>
<thead>
<tr>
<th colspan="4">
<th colspan="{{access.impersonation == true ? '5' : '4'}}">
<div class="form-inline">
<div class="form-group">
<div class="input-group">
@ -17,7 +17,7 @@
</div>
<button class="btn btn-primary" ng-click="query.search = null; firstPage()">View all users</button>
<div class="pull-right">
<div class="pull-right" data-ng-show="access.manageUsers">
<a class="btn btn-primary" href="#/create/user/{{realm.realm}}">Add User</a>
</div>
</div>
@ -29,6 +29,7 @@
<th>Last Name</th>
<th>First Name</th>
<th>Email</th>
<th data-ng-show="access.impersonation"></th>
</tr>
</tr>
</thead>
@ -49,6 +50,7 @@
<td>{{user.lastName}}</td>
<td>{{user.firstName}}</td>
<td>{{user.email}}</td>
<td data-ng-show="access.impersonation"><button class="btn btn-default" data-ng-click="impersonate(user.id)">Impersonate</button></td>
</tr>
<tr data-ng-show="!users || users.length == 0">
<td class="text-muted" data-ng-show="!users">Please enter a search, or click on view all users</td>

View file

@ -10,9 +10,9 @@
<table class="table table-striped table-bordered">
<thead>
<tr>
<tr data-ng-show="access.manageUsers">
<th class="kc-table-actions" colspan="6">
<div class="pull-right">
<div class="pull-right" data-ng-show="access.manageUsers">
<a class="btn btn-primary" ng-click="logoutAll()">Logout All Sessions</a>
</div>
</th>
@ -22,7 +22,7 @@
<th>Started</th>
<th>Last Access</th>
<th>Clients</th>
<th>Action</th>
<th data-ng-show="access.manageUsers">Action</th>
</tr>
</thead>
<tbody>
@ -36,7 +36,7 @@
</div>
</ul>
</td>
<td><a href="" ng-click="logoutSession(session.id)">logout</a> </td>
<td data-ng-show="access.manageUsers"><a href="" ng-click="logoutSession(session.id)">logout</a> </td>
</tr>
</tbody>
</table>

View file

@ -1,45 +0,0 @@
<#import "template.ftl" as layout>
<@layout.registrationLayout displayInfo=social.displayInfo; section>
<#if section = "title">
${msg("imperonateTitle",(realm.name!''))}
<#elseif section = "header">
${msg("impersonateTitleHtml",(realm.name!''))}
<#elseif section = "form">
<form id="kc-form-login" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
<input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker}">
<#if realmList??>
<div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}">
<label for="realm" class="${properties.kcLabelClass!}">${msg("realmChoice")}</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<select class="${properties.kcInputClass!}" id="selectRealm" name="realm">
<#list realmList as r>
<option value="${r}">${r}</option>
</#list>
</select>
</div>
</div>
</#if>
<div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}">
<label for="username" class="${properties.kcLabelClass!}"><#if !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if></label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input id="username" class="${properties.kcInputClass!}" name="username" value="" type="text" autofocus />
</div>
</div>
<div class="${properties.kcFormGroupClass!}">
<div class="${properties.kcLabelWrapperClass!}">
<label for="username" class="${properties.kcLabelClass!}"></label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="impersonate" id="kc-impersonate" type="submit" value="${msg("doImpersonate")}"/>
<input class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" name="cancel" id="kc-cancel" type="submit" value="${msg("doCancel")}"/>
</div>
</div>
</form>
</#if>
</@layout.registrationLayout>

View file

@ -1,7 +1,7 @@
package org.keycloak.migration.migrators;
import org.keycloak.migration.ModelVersion;
import org.keycloak.models.ImpersonationServiceConstants;
import org.keycloak.models.ImpersonationConstants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.DefaultAuthenticationFlows;
@ -23,7 +23,7 @@ public class MigrateTo1_4_0 {
DefaultAuthenticationFlows.addFlows(realm);
DefaultRequiredActions.addActions(realm);
}
ImpersonationServiceConstants.setupImpersonationService(session, realm, session.getContext().getContextPath());
ImpersonationConstants.setupImpersonationService(session, realm);
}

View file

@ -7,8 +7,8 @@ import org.keycloak.models.utils.KeycloakModelUtils;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class ImpersonationServiceConstants {
public static String IMPERSONATION_ALLOWED = "impersonation";
public class ImpersonationConstants {
public static String IMPERSONATION_ROLE = "impersonation";
public static void setupMasterRealmRole(RealmProvider model, RealmModel realm) {
RealmModel adminRealm;
@ -22,8 +22,9 @@ public class ImpersonationServiceConstants {
adminRole = adminRealm.getRole(AdminRoles.ADMIN);
}
ClientModel realmAdminApp = adminRealm.getClientByClientId(KeycloakModelUtils.getMasterRealmAdminApplicationClientId(realm));
RoleModel impersonationRole = realmAdminApp.addRole(IMPERSONATION_ALLOWED);
impersonationRole.setDescription("${role_" + IMPERSONATION_ALLOWED + "}");
if (realmAdminApp.getRole(IMPERSONATION_ROLE) != null) return;
RoleModel impersonationRole = realmAdminApp.addRole(IMPERSONATION_ROLE);
impersonationRole.setDescription("${role_" + IMPERSONATION_ROLE + "}");
adminRole.addCompositeRole(impersonationRole);
}
@ -31,29 +32,18 @@ public class ImpersonationServiceConstants {
if (realm.getName().equals(Config.getAdminRealm())) { return; } // don't need to do this for master realm
String realmAdminApplicationClientId = Constants.REALM_MANAGEMENT_CLIENT_ID;
ClientModel realmAdminApp = realm.getClientByClientId(realmAdminApplicationClientId);
RoleModel impersonationRole = realmAdminApp.addRole(IMPERSONATION_ALLOWED);
impersonationRole.setDescription("${role_" + IMPERSONATION_ALLOWED + "}");
if (realmAdminApp.getRole(IMPERSONATION_ROLE) != null) return;
RoleModel impersonationRole = realmAdminApp.addRole(IMPERSONATION_ROLE);
impersonationRole.setDescription("${role_" + IMPERSONATION_ROLE + "}");
RoleModel adminRole = realmAdminApp.getRole(AdminRoles.REALM_ADMIN);
adminRole.addCompositeRole(impersonationRole);
}
public static void setupImpersonationService(KeycloakSession session, RealmModel realm, String contextPath) {
ClientModel client = realm.getClientNameMap().get(Constants.IMPERSONATION_SERVICE_CLIENT_ID);
if (client == null) {
client = KeycloakModelUtils.createClient(realm, Constants.IMPERSONATION_SERVICE_CLIENT_ID);
client.setName("${client_" + Constants.IMPERSONATION_SERVICE_CLIENT_ID + "}");
client.setEnabled(true);
client.setFullScopeAllowed(false);
String base = contextPath + "/realms/" + realm.getName() + "/impersonate";
String redirectUri = base + "/*";
client.addRedirectUri(redirectUri);
client.setBaseUrl(base);
public static void setupImpersonationService(KeycloakSession session, RealmModel realm) {
setupMasterRealmRole(session.realms(), realm);
setupRealmRole(realm);
}
}
}

View file

@ -9,7 +9,7 @@ import org.keycloak.models.AdminRoles;
import org.keycloak.models.ClientModel;
import org.keycloak.models.BrowserSecurityHeaders;
import org.keycloak.models.Constants;
import org.keycloak.models.ImpersonationServiceConstants;
import org.keycloak.models.ImpersonationConstants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider;
@ -236,7 +236,7 @@ public class RealmManager {
}
public void setupImpersonationService(RealmModel realm) {
ImpersonationServiceConstants.setupImpersonationService(session, realm, contextPath);
ImpersonationConstants.setupImpersonationService(session, realm);
}
public void setupBrokerService(RealmModel realm) {

View file

@ -1,177 +0,0 @@
package org.keycloak.services.resources;
import org.jboss.resteasy.spi.NotFoundException;
import org.keycloak.Config;
import org.keycloak.events.EventBuilder;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.ImpersonationServiceConstants;
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.services.ErrorPage;
import org.keycloak.services.ForbiddenException;
import org.keycloak.services.Urls;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.messages.Messages;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import java.net.URI;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class ImpersonationService extends AbstractSecuredLocalService {
public static final String UNKNOWN_USER_MESSAGE = "unknownUser";
private EventBuilder event;
public ImpersonationService(RealmModel realm, ClientModel client, EventBuilder event) {
super(realm, client);
this.event = event;
}
private static Set<String> VALID_PATHS = new HashSet<String>();
static {
}
@Override
protected Set<String> getValidPaths() {
return VALID_PATHS;
}
@Override
protected URI getBaseRedirectUri() {
return Urls.realmBase(uriInfo.getBaseUri()).path(RealmsResource.class, "getImpersonationService").build(realm.getName());
}
@GET
public Response impersonatePage() {
Response challenge = authenticateBrowser();
if (challenge != null) return challenge;
LoginFormsProvider page = page();
return renderPage(page);
}
protected LoginFormsProvider page() {
UserModel user = auth.getUser();
LoginFormsProvider page = session.getProvider(LoginFormsProvider.class)
.setActionUri(getBaseRedirectUri())
.setAttribute("stateChecker", stateChecker);
if (realm.getName().equals(Config.getAdminRealm())) {
List<String> realms = new LinkedList<>();
for (RealmModel possibleRealm : session.realms().getRealms()) {
ClientModel realmAdminApp = realm.getClientByClientId(KeycloakModelUtils.getMasterRealmAdminApplicationClientId(possibleRealm));
RoleModel role = realmAdminApp.getRole(ImpersonationServiceConstants.IMPERSONATION_ALLOWED);
if (user.hasRole(role)) {
realms.add(possibleRealm.getName());
}
}
if (realms.isEmpty()) {
throw new ForbiddenException("not authorized to access impersonation", ErrorPage.error(session, Messages.NO_ACCESS));
}
if (realms.size() > 1 || !realms.get(0).equals(realm.getName())) {
page.setAttribute("realmList", realms);
}
} else {
authorizeCurrentRealm();
} return page;
}
protected Response renderPage(LoginFormsProvider page) {
return page
.createForm("impersonate.ftl", new HashMap<String, Object>());
}
protected void authorizeMaster(String realmName) {
RealmModel possibleRealm = session.realms().getRealmByName(realmName);
if (possibleRealm == null) {
throw new NotFoundException("Could not find realm");
}
ClientModel realmAdminApp = realm.getClientByClientId(KeycloakModelUtils.getMasterRealmAdminApplicationClientId(possibleRealm));
RoleModel role = realmAdminApp.getRole(ImpersonationServiceConstants.IMPERSONATION_ALLOWED);
if (!auth.getUser().hasRole(role)) {
throw new ForbiddenException("not authorized to access impersonation", ErrorPage.error(session, Messages.NO_ACCESS));
}
}
private void authorizeCurrentRealm() {
UserModel user = auth.getUser();
String realmAdminApplicationClientId = Constants.REALM_MANAGEMENT_CLIENT_ID;
ClientModel realmAdminApp = realm.getClientByClientId(realmAdminApplicationClientId);
RoleModel role = realmAdminApp.getRole(ImpersonationServiceConstants.IMPERSONATION_ALLOWED);
if (!user.hasRole(role)) {
throw new ForbiddenException("not authorized to access impersonation", ErrorPage.error(session, Messages.NO_ACCESS));
}
}
@POST
public Response impersonate() {
Response challenge = authenticateBrowser();
if (challenge != null) return challenge;
MultivaluedMap<String, String> formData = request.getDecodedFormParameters();
String realmName = formData.getFirst("realm");
RealmModel chosenRealm = null;
if (realmName == null) {
chosenRealm = realm;
} else{
chosenRealm = session.realms().getRealmByName(realmName);
if (chosenRealm == null) {
throw new NotFoundException("Could not find realm");
}
}
if (realm.getName().equals(Config.getAdminRealm())) {
authorizeMaster(chosenRealm.getName());
} else {
if (realmName == null) authorizeCurrentRealm();
else {
throw new ForbiddenException("not authorized to access impersonation", ErrorPage.error(session, Messages.NO_ACCESS));
}
}
csrfCheck(formData);
if (formData.containsKey("cancel")) {
return renderPage(page());
}
String username = formData.getFirst(AuthenticationManager.FORM_USERNAME);
if (username == null) {
return renderPage(
page().setError(UNKNOWN_USER_MESSAGE)
);
}
UserModel user = session.users().getUserByUsername(username, chosenRealm);
if (user == null) {
user = session.users().getUserByEmail(username, chosenRealm);
}
if (user == null) {
return renderPage(
page().setError(UNKNOWN_USER_MESSAGE)
);
}
// if same realm logout before impersonation
if (chosenRealm.getId().equals(realm.getId())) {
AuthenticationManager.backchannelLogout(session, realm, auth.getSession(), uriInfo, clientConnection, headers, true);
}
UserSessionModel userSession = session.sessions().createUserSession(chosenRealm, user, username, clientConnection.getRemoteAddr(), "impersonate", false, null, null);
AuthenticationManager.createLoginCookie(chosenRealm, userSession.getUser(), userSession, uriInfo, clientConnection);
URI redirect = AccountService.accountServiceApplicationPage(uriInfo).build(chosenRealm.getName());
return Response.status(302).location(redirect).build();
}
}

View file

@ -148,22 +148,6 @@ public class RealmsResource {
return accountService;
}
@Path("{realm}/impersonate")
public ImpersonationService getImpersonationService(final @PathParam("realm") String name) {
RealmModel realm = init(name);
ClientModel client = realm.getClientNameMap().get(Constants.IMPERSONATION_SERVICE_CLIENT_ID);
if (client == null || !client.isEnabled()) {
logger.debug("impersonate service not enabled");
throw new NotFoundException("impersonate service not enabled");
}
EventBuilder event = new EventBuilder(realm, session, clientConnection);
ImpersonationService impersonateService = new ImpersonationService(realm, client, event);
ResteasyProviderFactory.getInstance().injectProperties(impersonateService);
return impersonateService;
}
@Path("{realm}")
public PublicRealmResource getRealmResource(final @PathParam("realm") String name) {
RealmModel realm = init(name);

View file

@ -56,7 +56,7 @@ public class AuthenticationManagementResource {
this.realm = realm;
this.session = session;
this.auth = auth;
this.auth.init(RealmAuth.Resource.IDENTITY_PROVIDER);
this.auth.init(RealmAuth.Resource.REALM);
this.adminEvent = adminEvent;
}
@ -140,6 +140,7 @@ public class AuthenticationManagementResource {
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public List<AuthenticationFlowModel> getFlows() {
this.auth.requireView();
List<AuthenticationFlowModel> flows = new LinkedList<>();
for (AuthenticationFlowModel flow : realm.getAuthenticationFlows()) {
if (flow.isTopLevel()) {
@ -265,6 +266,7 @@ public class AuthenticationManagementResource {
@Produces(MediaType.APPLICATION_JSON)
@NoCache
public AuthenticatorConfigModel getAuthenticatorConfig(@PathParam("executionId") String execution,@PathParam("id") String id) {
this.auth.requireView();
AuthenticatorConfigModel config = realm.getAuthenticatorConfigById(id);
if (config == null) {
throw new NotFoundException("Could not find authenticator config");
@ -363,6 +365,7 @@ public class AuthenticationManagementResource {
@PUT
@Consumes(MediaType.APPLICATION_JSON)
public void updateRequiredAction(@PathParam("alias") String alias, RequiredActionProviderRepresentation rep) {
this.auth.requireManage();
RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(alias);
if (model == null) {
throw new NotFoundException("Failed to find required action: " + alias);
@ -381,6 +384,7 @@ public class AuthenticationManagementResource {
@Path("required-actions/{alias}")
@DELETE
public void updateRequiredAction(@PathParam("alias") String alias) {
this.auth.requireManage();
RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(alias);
if (model == null) {
throw new NotFoundException("Failed to find required action: " + alias);
@ -434,6 +438,7 @@ public class AuthenticationManagementResource {
@Produces(MediaType.APPLICATION_JSON)
@NoCache
public AuthenticatorConfigDescription getAuthenticatorConfigDescription(@PathParam("providerId") String providerId) {
this.auth.requireView();
ConfigurableAuthenticatorFactory factory = getConfigurableAuthenticatorFactory(providerId);
if (factory == null) {
throw new NotFoundException("Could not find authenticator provider");
@ -460,6 +465,7 @@ public class AuthenticationManagementResource {
@POST
@NoCache
public Response createAuthenticatorConfig(AuthenticatorConfigModel config) {
this.auth.requireManage();
config = realm.addAuthenticatorConfig(config);
return Response.created(uriInfo.getAbsolutePathBuilder().path(config.getId()).build()).build();
}
@ -469,6 +475,7 @@ public class AuthenticationManagementResource {
@Produces(MediaType.APPLICATION_JSON)
@NoCache
public AuthenticatorConfigModel getAuthenticatorConfig(@PathParam("id") String id) {
this.auth.requireView();
AuthenticatorConfigModel config = realm.getAuthenticatorConfigById(id);
if (config == null) {
throw new NotFoundException("Could not find authenticator config");
@ -480,6 +487,7 @@ public class AuthenticationManagementResource {
@DELETE
@NoCache
public void removeAuthenticatorConfig(@PathParam("id") String id) {
this.auth.requireManage();
AuthenticatorConfigModel config = realm.getAuthenticatorConfigById(id);
if (config == null) {
throw new NotFoundException("Could not find authenticator config");
@ -502,6 +510,7 @@ public class AuthenticationManagementResource {
@Consumes(MediaType.APPLICATION_JSON)
@NoCache
public void updateAuthenticatorConfig(@PathParam("id") String id, AuthenticatorConfigModel config) {
this.auth.requireManage();
AuthenticatorConfigModel exists = realm.getAuthenticatorConfigById(id);
if (exists == null) {
throw new NotFoundException("Could not find authenticator config");

View file

@ -280,6 +280,7 @@ public class RealmAdminResource {
@Path("logout-all")
@POST
public GlobalRequestResult logoutAll() {
auth.init(RealmAuth.Resource.USER).requireManage();
session.sessions().removeUserSessions(realm);
adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
return new ResourceAdminManager(session).logoutAll(uriInfo.getRequestUri(), realm);
@ -294,6 +295,7 @@ public class RealmAdminResource {
@Path("sessions/{session}")
@DELETE
public void deleteSession(@PathParam("session") String sessionId) {
auth.init(RealmAuth.Resource.USER).requireManage();
UserSessionModel userSession = session.sessions().getUserSession(realm, sessionId);
if (userSession == null) throw new NotFoundException("Sesssion not found");
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, connection, headers, true);

View file

@ -2,6 +2,7 @@ package org.keycloak.services.resources.admin;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ImpersonationConstants;
import org.keycloak.services.ForbiddenException;
@ -13,7 +14,7 @@ public class RealmAuth {
private Resource resource;
public enum Resource {
CLIENT, USER, REALM, EVENTS, IDENTITY_PROVIDER
CLIENT, USER, REALM, EVENTS, IDENTITY_PROVIDER, IMPERSONATION
}
private AdminAuth auth;
@ -29,6 +30,10 @@ public class RealmAuth {
return this;
}
public AdminAuth getAuth() {
return auth;
}
public void requireAny() {
if (!auth.hasOneOfAppRole(realmAdminApp, AdminRoles.ALL_REALM_ROLES)) {
throw new ForbiddenException();
@ -84,6 +89,8 @@ public class RealmAuth {
return AdminRoles.MANAGE_EVENTS;
case IDENTITY_PROVIDER:
return AdminRoles.MANAGE_IDENTITY_PROVIDERS;
case IMPERSONATION:
return ImpersonationConstants.IMPERSONATION_ROLE;
default:
throw new IllegalStateException();
}

View file

@ -61,6 +61,7 @@ import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.WebApplicationException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@ -72,6 +73,7 @@ import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.keycloak.models.UsernameLoginFailureModel;
import org.keycloak.services.managers.BruteForceProtector;
import org.keycloak.services.resources.AccountService;
/**
* Base resource for managing users
@ -270,6 +272,37 @@ public class UsersResource {
return rep;
}
@Path("{id}/impersonation")
@POST
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public Map<String, Object> impersonate(final @PathParam("id") String id) {
auth.init(RealmAuth.Resource.IMPERSONATION);
auth.requireManage();
UserModel user = session.users().getUserById(id, realm);
if (user == null) {
throw new NotFoundException("User not found");
}
RealmModel authenticatedRealm = auth.getAuth().getRealm();
// if same realm logout before impersonation
boolean sameRealm = false;
if (authenticatedRealm.getId().equals(realm.getId())) {
sameRealm = true;
UserSessionModel userSession = session.sessions().getUserSession(authenticatedRealm, auth.getAuth().getToken().getSessionState());
AuthenticationManager.expireIdentityCookie(realm, uriInfo, clientConnection);
AuthenticationManager.expireRememberMeCookie(realm, uriInfo, clientConnection);
AuthenticationManager.backchannelLogout(session, authenticatedRealm, userSession, uriInfo, clientConnection, headers, true);
}
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<String, Object> result = new HashMap<>();
result.put("sameRealm", sameRealm);
result.put("redirect", redirect.toString());
return result;
}
/**
* List set of sessions associated with this user.
*

View file

@ -42,7 +42,7 @@ public class ClientTest extends AbstractClientTest {
@Test
public void getClients() {
assertNames(realm.clients().findAll(), "account", "realm-management", "security-admin-console", "broker", "impersonation");
assertNames(realm.clients().findAll(), "account", "realm-management", "security-admin-console", "broker");
}
private String createClient() {
@ -59,7 +59,7 @@ public class ClientTest extends AbstractClientTest {
String id = createClient();
assertNotNull(realm.clients().get(id));
assertNames(realm.clients().findAll(), "account", "realm-management", "security-admin-console", "broker", "my-app", "impersonation");
assertNames(realm.clients().findAll(), "account", "realm-management", "security-admin-console", "broker", "my-app");
}
@Test

View file

@ -86,7 +86,7 @@ public class ImportTest extends AbstractModelTest {
Assert.assertEquals(0, session.users().getFederatedIdentities(user, realm).size());
List<ClientModel> resources = realm.getClients();
Assert.assertEquals(8, resources.size());
Assert.assertEquals(7, resources.size());
// Test applications imported
ClientModel application = realm.getClientByClientId("Application");
@ -97,7 +97,7 @@ public class ImportTest extends AbstractModelTest {
Assert.assertNotNull(otherApp);
Assert.assertNull(nonExisting);
Map<String, ClientModel> clients = realm.getClientNameMap();
Assert.assertEquals(8, clients.size());
Assert.assertEquals(7, clients.size());
Assert.assertTrue(clients.values().contains(application));
Assert.assertTrue(clients.values().contains(otherApp));
Assert.assertTrue(clients.values().contains(accountApp));