/* * Copyright 2016 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.keycloak.services.resources.admin; import com.fasterxml.jackson.annotation.JsonProperty; import org.jboss.logging.Logger; import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.HttpResponse; import org.jboss.resteasy.spi.NotFoundException; import org.keycloak.Config; import org.keycloak.common.ClientConnection; import org.keycloak.common.Version; import org.keycloak.models.AdminRoles; import org.keycloak.models.ClientModel; import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; import org.keycloak.protocol.oidc.OIDCLoginProtocolService; import org.keycloak.services.Urls; import org.keycloak.services.managers.AppAuthManager; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.ClientManager; import org.keycloak.services.managers.RealmManager; import org.keycloak.services.resources.KeycloakApplication; import org.keycloak.theme.BrowserSecurityHeaderSetup; import org.keycloak.theme.FreeMarkerException; import org.keycloak.theme.FreeMarkerUtil; import org.keycloak.theme.Theme; import org.keycloak.utils.MediaType; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import javax.ws.rs.ext.Providers; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; /** * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @version $Revision: 1 $ */ public class AdminConsole { protected static final Logger logger = Logger.getLogger(AdminConsole.class); @Context protected UriInfo uriInfo; @Context protected ClientConnection clientConnection; @Context protected HttpRequest request; @Context protected HttpResponse response; @Context protected KeycloakSession session; @Context protected Providers providers; @Context protected KeycloakApplication keycloak; protected AppAuthManager authManager; protected RealmModel realm; public AdminConsole(RealmModel realm) { this.realm = realm; this.authManager = new AppAuthManager(); } public static class WhoAmI { protected String userId; protected String realm; protected String displayName; @JsonProperty("createRealm") protected boolean createRealm; @JsonProperty("realm_access") protected Map<String, Set<String>> realmAccess = new HashMap<String, Set<String>>(); public WhoAmI() { } public WhoAmI(String userId, String realm, String displayName, boolean createRealm, Map<String, Set<String>> realmAccess) { this.userId = userId; this.realm = realm; this.displayName = displayName; this.createRealm = createRealm; this.realmAccess = realmAccess; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getRealm() { return realm; } public void setRealm(String realm) { this.realm = realm; } public String getDisplayName() { return displayName; } public void setDisplayName(String displayName) { this.displayName = displayName; } public boolean isCreateRealm() { return createRealm; } public void setCreateRealm(boolean createRealm) { this.createRealm = createRealm; } public Map<String, Set<String>> getRealmAccess() { return realmAccess; } public void setRealmAccess(Map<String, Set<String>> realmAccess) { this.realmAccess = realmAccess; } } /** * Adapter configuration for the admin console for this realm * * @return */ @Path("config") @GET @Produces(MediaType.APPLICATION_JSON) @NoCache public ClientManager.InstallationAdapterConfig config() { ClientModel consoleApp = realm.getClientByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID); if (consoleApp == null) { throw new NotFoundException("Could not find admin console client"); } return new ClientManager().toInstallationRepresentation(realm, consoleApp, keycloak.getBaseUri(uriInfo)); } /** * Permission information * * @param headers * @return */ @Path("whoami") @GET @Produces(MediaType.APPLICATION_JSON) @NoCache public Response whoAmI(final @Context HttpHeaders headers) { RealmManager realmManager = new RealmManager(session); AuthenticationManager.AuthResult authResult = authManager.authenticateBearerToken(session, realm, uriInfo, clientConnection, headers); if (authResult == null) { return Response.status(401).build(); } UserModel user= authResult.getUser(); String displayName; if ((user.getFirstName() != null && !user.getFirstName().trim().equals("")) || (user.getLastName() != null && !user.getLastName().trim().equals(""))) { displayName = user.getFirstName(); if (user.getLastName() != null) { displayName = displayName != null ? displayName + " " + user.getLastName() : user.getLastName(); } } else { displayName = user.getUsername(); } RealmModel masterRealm = getAdminstrationRealm(realmManager); Map<String, Set<String>> realmAccess = new HashMap<String, Set<String>>(); if (masterRealm == null) throw new NotFoundException("No realm found"); boolean createRealm = false; if (realm.equals(masterRealm)) { logger.debug("setting up realm access for a master realm user"); createRealm = user.hasRole(masterRealm.getRole(AdminRoles.CREATE_REALM)); addMasterRealmAccess(realm, user, realmAccess); } else { logger.debug("setting up realm access for a realm user"); addRealmAccess(realm, user, realmAccess); } return Response.ok(new WhoAmI(user.getId(), realm.getName(), displayName, createRealm, realmAccess)).build(); } private void addRealmAccess(RealmModel realm, UserModel user, Map<String, Set<String>> realmAdminAccess) { RealmManager realmManager = new RealmManager(session); ClientModel realmAdminApp = realm.getClientByClientId(realmManager.getRealmAdminClientId(realm)); Set<RoleModel> roles = realmAdminApp.getRoles(); for (RoleModel role : roles) { if (!user.hasRole(role)) continue; if (!realmAdminAccess.containsKey(realm.getName())) { realmAdminAccess.put(realm.getName(), new HashSet<String>()); } realmAdminAccess.get(realm.getName()).add(role.getName()); } } private void addMasterRealmAccess(RealmModel masterRealm, UserModel user, Map<String, Set<String>> realmAdminAccess) { List<RealmModel> realms = session.realms().getRealms(); for (RealmModel realm : realms) { ClientModel realmAdminApp = realm.getMasterAdminClient(); Set<RoleModel> roles = realmAdminApp.getRoles(); for (RoleModel role : roles) { if (!user.hasRole(role)) continue; if (!realmAdminAccess.containsKey(realm.getName())) { realmAdminAccess.put(realm.getName(), new HashSet<String>()); } realmAdminAccess.get(realm.getName()).add(role.getName()); } } } /** * Logout from the admin console * * @return */ @Path("logout") @GET @NoCache public Response logout() { URI redirect = AdminRoot.adminConsoleUrl(uriInfo).build(realm.getName()); return Response.status(302).location( OIDCLoginProtocolService.logoutUrl(uriInfo).queryParam("redirect_uri", redirect.toString()).build(realm.getName()) ).build(); } protected RealmModel getAdminstrationRealm(RealmManager realmManager) { return realmManager.getKeycloakAdminstrationRealm(); } /** * Main page of this realm's admin console * * @return * @throws URISyntaxException */ @GET @NoCache public Response getMainPage() throws URISyntaxException, IOException, FreeMarkerException { if (!uriInfo.getRequestUri().getPath().endsWith("/")) { return Response.status(302).location(uriInfo.getRequestUriBuilder().path("/").build()).build(); } else { Theme theme = AdminRoot.getTheme(session, realm); Map<String, Object> map = new HashMap<>(); URI baseUri = uriInfo.getBaseUri(); String authUrl = baseUri.toString(); authUrl = authUrl.substring(0, authUrl.length() - 1); map.put("authUrl", authUrl); map.put("consoleBaseUrl", Urls.adminConsoleRoot(baseUri, realm.getName())); map.put("resourceUrl", Urls.themeRoot(baseUri) + "/admin/" + theme.getName()); map.put("masterRealm", Config.getAdminRealm()); map.put("resourceVersion", Version.RESOURCES_VERSION); map.put("properties", theme.getProperties()); FreeMarkerUtil freeMarkerUtil = new FreeMarkerUtil(); String result = freeMarkerUtil.processTemplate(map, "index.ftl", theme); Response.ResponseBuilder builder = Response.status(Response.Status.OK).type(MediaType.TEXT_HTML_UTF_8).language(Locale.ENGLISH).entity(result); BrowserSecurityHeaderSetup.headers(builder, realm); return builder.build(); } } @GET @Path("{indexhtml: index.html}") // this expression is a hack to get around jaxdoclet generation bug. Doesn't like index.html public Response getIndexHtmlRedirect() { return Response.status(302).location(uriInfo.getRequestUriBuilder().path("../").build()).build(); } @GET @Path("messages.json") @Produces(MediaType.APPLICATION_JSON) public Properties getMessages(@QueryParam("lang") String lang) { return AdminRoot.getMessages(session, realm, lang, "admin-messages"); } }