Merge pull request #1442 from patriot1burke/master
impersonation in admin console
This commit is contained in:
commit
aca799b28c
19 changed files with 125 additions and 289 deletions
|
@ -79,6 +79,9 @@ module.controller('GlobalCtrl', function($scope, $http, Auth, WhoAmI, Current, $
|
|||
|
||||
get manageEvents() {
|
||||
return getAccess('manage-events');
|
||||
},
|
||||
get impersonation() {
|
||||
return getAccess('impersonation');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 = {};
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,13 +116,14 @@
|
|||
<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>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<kc-menu></kc-menu>
|
||||
<kc-menu></kc-menu>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="#/realms/{{realm.realm}}/users">Users</a></li>
|
||||
<li>{{user.username}}</li>
|
||||
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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,28 +32,17 @@ 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);
|
||||
|
||||
setupMasterRealmRole(session.realms(), realm);
|
||||
setupRealmRole(realm);
|
||||
}
|
||||
public static void setupImpersonationService(KeycloakSession session, RealmModel realm) {
|
||||
setupMasterRealmRole(session.realms(), realm);
|
||||
setupRealmRole(realm);
|
||||
}
|
||||
|
||||
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
|
Loading…
Reference in a new issue