impersonation redo in admin console
This commit is contained in:
1 parent
ef255c6a5c
commit
608701aea7
17 files changed
+114
-278
No files matched your search
@@ -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);
|
||||
|
||||
+12
-1
@@ -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");
|
||||
@@ -328,6 +330,7 @@ public class AuthenticationManagementResource {
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@NoCache
|
||||
public List<RequiredActionProviderRepresentation> getRequiredActions() {
|
||||
this.auth.requireView();
|
||||
List<RequiredActionProviderRepresentation> list = new LinkedList<>();
|
||||
for (RequiredActionProviderModel model : realm.getRequiredActionProviders()) {
|
||||
RequiredActionProviderRepresentation rep = toRepresentation(model);
|
||||
@@ -351,6 +354,7 @@ public class AuthenticationManagementResource {
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@NoCache
|
||||
public RequiredActionProviderRepresentation getRequiredAction(@PathParam("alias") String alias) {
|
||||
this.auth.requireView();
|
||||
RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(alias);
|
||||
if (model == null) {
|
||||
throw new NotFoundException("Failed to find required action: " + alias);
|
||||
@@ -363,6 +367,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 +386,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 +440,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 +467,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 +477,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 +489,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 +512,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");
|
||||
|
||||
@@ -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
|
||||
@@ -276,6 +278,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.
|
||||
*
|
||||
|
||||
Reference in new issue
Block a user