From 7ff2c77a82d5e777abb550af2d8e82e8a0ab376e Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Fri, 2 May 2014 12:30:08 -0400 Subject: [PATCH] relative uri tests and fixes --- .../adapters/AdapterDeploymentContext.java | 11 +- .../keycloak/adapters/KeycloakDeployment.java | 2 +- .../adapters/KeycloakDeploymentBuilder.java | 14 +- .../adapters/PreAuthActionsHandler.java | 28 +++- .../as7/KeycloakAuthenticatorValve.java | 11 +- .../ServletPreAuthActionsHandler.java | 11 +- .../managers/ResourceAdminManager.java | 55 +++--- .../services/resources/AccountService.java | 3 +- .../services/resources/TokenService.java | 2 +- .../resources/admin/ApplicationResource.java | 8 +- .../resources/admin/RealmAdminResource.java | 10 +- .../resources/admin/UsersResource.java | 4 +- .../services/util/ResolveRelative.java | 20 +++ .../adapter/RelativeUriAdapterTest.java | 156 ++++++++++++++++++ .../cust-app-keycloak-relative.json | 9 + .../customer-db-keycloak-relative.json | 9 + .../adapter-test/demorealm-relative.json | 120 ++++++++++++++ .../product-keycloak-relative.json | 9 + 18 files changed, 419 insertions(+), 63 deletions(-) create mode 100755 services/src/main/java/org/keycloak/services/util/ResolveRelative.java create mode 100755 testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java create mode 100755 testsuite/integration/src/test/resources/adapter-test/cust-app-keycloak-relative.json create mode 100755 testsuite/integration/src/test/resources/adapter-test/customer-db-keycloak-relative.json create mode 100755 testsuite/integration/src/test/resources/adapter-test/demorealm-relative.json create mode 100755 testsuite/integration/src/test/resources/adapter-test/product-keycloak-relative.json diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java index 786ea7c072..541012fddc 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java @@ -46,6 +46,7 @@ public class AdapterDeploymentContext { public KeycloakDeployment resolveDeployment(HttpFacade facade) { KeycloakDeployment deployment = this.deployment; if (deployment == null) return null; + if (deployment.getAuthServerBaseUrl() == null) return deployment; if (deployment.relativeUrls) { deployment = new DeploymentDelegate(this.deployment); deployment.setAuthServerBaseUrl(getBaseBuilder(facade, this.deployment.getAuthServerBaseUrl()).build().toString()); @@ -93,6 +94,11 @@ public class AdapterDeploymentContext { } } + /** + * This delegate is used to store temporary, per-request metadata like request resolved URLs. + * Ever method is delegated except URL get methods and isConfigured() + * + */ protected static class DeploymentDelegate extends KeycloakDeployment { protected KeycloakDeployment delegate; @@ -100,11 +106,6 @@ public class AdapterDeploymentContext { this.delegate = delegate; } - @Override - public boolean isConfigured() { - return delegate.isConfigured(); - } - @Override public String getResourceName() { return delegate.getResourceName(); diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java index 9e63654e1d..726502424a 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java @@ -51,7 +51,7 @@ public class KeycloakDeployment { } public boolean isConfigured() { - return realm != null && realmKey != null && (bearerOnly || authServerBaseUrl != null); + return getRealm() != null && getRealmKey() != null && (isBearerOnly() || getAuthServerBaseUrl() != null); } public String getResourceName() { diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java index 4138e6afde..2ccdcea522 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java @@ -52,16 +52,18 @@ public class KeycloakDeploymentBuilder { deployment.setCorsAllowedMethods(adapterConfig.getCorsAllowedMethods()); } + deployment.setBearerOnly(adapterConfig.isBearerOnly()); + + if (adapterConfig.isBearerOnly()) { + } + if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) { throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url"); } - if (adapterConfig.isBearerOnly()) { - deployment.setBearerOnly(true); - return deployment; + if (realmKeyPem == null || !deployment.isBearerOnly()) { + deployment.setClient(new HttpClientBuilder().build(adapterConfig)); } - - deployment.setClient(new HttpClientBuilder().build(adapterConfig)); - if (adapterConfig.getAuthServerUrl() == null) { + if (adapterConfig.getAuthServerUrl() == null && (!deployment.isBearerOnly() || realmKeyPem == null)) { throw new RuntimeException("You must specify auth-url"); } deployment.setAuthServerBaseUrl(adapterConfig.getAuthServerUrl()); diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java index c937809ceb..0842b8da98 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java @@ -25,32 +25,46 @@ public class PreAuthActionsHandler { private static final Logger log = Logger.getLogger(PreAuthActionsHandler.class); protected UserSessionManagement userSessionManagement; + protected AdapterDeploymentContext deploymentContext; protected KeycloakDeployment deployment; protected HttpFacade facade; - public PreAuthActionsHandler(UserSessionManagement userSessionManagement, KeycloakDeployment deployment, HttpFacade facade) { + public PreAuthActionsHandler(UserSessionManagement userSessionManagement, AdapterDeploymentContext deploymentContext, HttpFacade facade) { this.userSessionManagement = userSessionManagement; - this.deployment = deployment; + this.deploymentContext = deploymentContext; this.facade = facade; } + protected boolean resolveDeployment() { + deployment = deploymentContext.resolveDeployment(facade); + if (!deployment.isConfigured()) { + log.warn("can't take request, adapter not configured"); + facade.getResponse().sendError(403, "adapter not configured"); + return false; + } + return true; + } + public boolean handleRequest() { - if (!deployment.isConfigured()) return false; String requestUri = facade.getRequest().getURI(); log.debugv("adminRequest {0}", requestUri); if (preflightCors()) { return true; } if (requestUri.endsWith(AdapterConstants.K_LOGOUT)) { + if (!resolveDeployment()) return true; handleLogout(); return true; } else if (requestUri.endsWith(AdapterConstants.K_PUSH_NOT_BEFORE)) { + if (!resolveDeployment()) return true; handlePushNotBefore(); return true; } else if (requestUri.endsWith(AdapterConstants.K_GET_SESSION_STATS)) { + if (!resolveDeployment()) return true; handleGetSessionStats(); return true; }else if (requestUri.endsWith(AdapterConstants.K_GET_USER_STATS)) { + if (!resolveDeployment()) return true; handleGetUserStats(); return true; } @@ -58,6 +72,9 @@ public class PreAuthActionsHandler { } public boolean preflightCors() { + // don't need to resolve deployment on cors requests. Just need to know local cors config. + KeycloakDeployment deployment = deploymentContext.getDeployment(); + if (!deployment.isCors()) return false; log.debugv("checkCorsPreflight {0}", facade.getRequest().getURI()); if (!facade.getRequest().getMethod().equalsIgnoreCase("OPTIONS")) { return false; @@ -134,6 +151,11 @@ public class PreAuthActionsHandler { } protected JWSInput verifyAdminRequest() throws Exception { + if (deployment.isSslRequired() && !facade.getRequest().isSecure()) { + log.warn("SSL is required for adapter admin action"); + facade.getResponse().sendError(403, "ssl required"); + + } String token = StreamUtil.readString(facade.getRequest().getInputStream()); if (token == null) { log.warn("admin request failed, no token"); diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java index b3e10f288b..6c0c58fc6b 100755 --- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java +++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/KeycloakAuthenticatorValve.java @@ -107,12 +107,9 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif public void invoke(Request request, Response response) throws IOException, ServletException { try { CatalinaHttpFacade facade = new CatalinaHttpFacade(request, response); - KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade); - if (deployment != null && deployment.isConfigured()) { - PreAuthActionsHandler handler = new PreAuthActionsHandler(userSessionManagement, deployment, facade); - if (handler.handleRequest()) { - return; - } + PreAuthActionsHandler handler = new PreAuthActionsHandler(userSessionManagement, deploymentContext, facade); + if (handler.handleRequest()) { + return; } checkKeycloakSession(request, facade); super.invoke(request, response); @@ -153,7 +150,7 @@ public class KeycloakAuthenticatorValve extends FormAuthenticator implements Lif RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) request.getSessionInternal().getNote(KeycloakSecurityContext.class.getName()); if (session == null) return; // just in case session got serialized - session.setDeployment(deploymentContext.resolveDeployment(facade)); + if (session.getDeployment() == null) session.setDeployment(deploymentContext.resolveDeployment(facade)); if (session.isActive()) return; // FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPreAuthActionsHandler.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPreAuthActionsHandler.java index bae421766b..96d9db0993 100755 --- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPreAuthActionsHandler.java +++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPreAuthActionsHandler.java @@ -47,13 +47,10 @@ public class ServletPreAuthActionsHandler implements HttpHandler { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { UndertowHttpFacade facade = new UndertowHttpFacade(exchange); - KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade); - if (deployment != null && deployment.isConfigured()) { - final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); - SessionManagementBridge bridge = new SessionManagementBridge(userSessionManagement, servletRequestContext.getDeployment().getSessionManager()); - PreAuthActionsHandler handler = new PreAuthActionsHandler(bridge, deployment, facade); - if (handler.handleRequest()) return; - } + final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY); + SessionManagementBridge bridge = new SessionManagementBridge(userSessionManagement, servletRequestContext.getDeployment().getSessionManager()); + PreAuthActionsHandler handler = new PreAuthActionsHandler(bridge, deploymentContext, facade); + if (handler.handleRequest()) return; next.handleRequest(exchange); } } diff --git a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java index df6fc052df..942def012a 100755 --- a/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java +++ b/services/src/main/java/org/keycloak/services/managers/ResourceAdminManager.java @@ -17,11 +17,12 @@ import org.keycloak.representations.adapters.action.SessionStatsAction; import org.keycloak.representations.adapters.action.UserStats; import org.keycloak.representations.adapters.action.UserStatsAction; import org.keycloak.services.util.HttpClientBuilder; +import org.keycloak.services.util.ResolveRelative; import org.keycloak.util.Time; import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; +import java.net.URI; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -33,11 +34,11 @@ import java.util.Map; public class ResourceAdminManager { protected static Logger logger = Logger.getLogger(ResourceAdminManager.class); - public SessionStats getSessionStats(RealmModel realm, ApplicationModel application, boolean users) { + public SessionStats getSessionStats(URI requestUri, RealmModel realm, ApplicationModel application, boolean users) { ApacheHttpClient4Executor executor = createExecutor(); try { - return getSessionStats(realm, application, users, executor); + return getSessionStats(requestUri, realm, application, users, executor); } finally { executor.getHttpClient().getConnectionManager().shutdown(); } @@ -51,8 +52,8 @@ public class ResourceAdminManager { return new ApacheHttpClient4Executor(client); } - public SessionStats getSessionStats(RealmModel realm, ApplicationModel application, boolean users, ApacheHttpClient4Executor client) { - String managementUrl = application.getManagementUrl(); + public SessionStats getSessionStats(URI requestUri, RealmModel realm, ApplicationModel application, boolean users, ApacheHttpClient4Executor client) { + String managementUrl = getManagementUrl(requestUri, application); if (managementUrl != null) { SessionStatsAction adminAction = new SessionStatsAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, application.getName()); adminAction.setListUsers(users); @@ -94,11 +95,19 @@ public class ResourceAdminManager { } - public UserStats getUserStats(RealmModel realm, ApplicationModel application, UserModel user) { + protected String getManagementUrl(URI requestUri, ApplicationModel application) { + String mgmtUrl = application.getManagementUrl(); + + // this is to support relative admin urls when keycloak and applications are deployed on the same machine + return ResolveRelative.resolveRelativeUri(requestUri, mgmtUrl); + + } + + public UserStats getUserStats(URI requestUri, RealmModel realm, ApplicationModel application, UserModel user) { ApacheHttpClient4Executor executor = createExecutor(); try { - return getUserStats(realm, application, user, executor); + return getUserStats(requestUri, realm, application, user, executor); } finally { executor.getHttpClient().getConnectionManager().shutdown(); } @@ -106,8 +115,8 @@ public class ResourceAdminManager { } - public UserStats getUserStats(RealmModel realm, ApplicationModel application, UserModel user, ApacheHttpClient4Executor client) { - String managementUrl = application.getManagementUrl(); + public UserStats getUserStats(URI requestUri, RealmModel realm, ApplicationModel application, UserModel user, ApacheHttpClient4Executor client) { + String managementUrl = getManagementUrl(requestUri, application); if (managementUrl != null) { UserStatsAction adminAction = new UserStatsAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, application.getName(), user.getId()); String token = new TokenManager().encodeToken(realm, adminAction); @@ -137,7 +146,7 @@ public class ResourceAdminManager { } - public void logoutUser(RealmModel realm, UserModel user) { + public void logoutUser(URI requestUri, RealmModel realm, UserModel user) { ApacheHttpClient4Executor executor = createExecutor(); try { @@ -145,13 +154,13 @@ public class ResourceAdminManager { List resources = realm.getApplications(); logger.debugv("logging out {0} resources ", resources.size()); for (ApplicationModel resource : resources) { - logoutApplication(realm, resource, user.getId(), executor, 0); + logoutApplication(requestUri, realm, resource, user.getId(), executor, 0); } } finally { executor.getHttpClient().getConnectionManager().shutdown(); } } - public void logoutAll(RealmModel realm) { + public void logoutAll(URI requestUri, RealmModel realm) { ApacheHttpClient4Executor executor = createExecutor(); try { @@ -159,19 +168,19 @@ public class ResourceAdminManager { List resources = realm.getApplications(); logger.debugv("logging out {0} resources ", resources.size()); for (ApplicationModel resource : resources) { - logoutApplication(realm, resource, null, executor, realm.getNotBefore()); + logoutApplication(requestUri, realm, resource, null, executor, realm.getNotBefore()); } } finally { executor.getHttpClient().getConnectionManager().shutdown(); } } - public void logoutApplication(RealmModel realm, ApplicationModel resource, String user) { + public void logoutApplication(URI requestUri, RealmModel realm, ApplicationModel resource, String user) { ApacheHttpClient4Executor executor = createExecutor(); try { resource.setNotBefore(Time.currentTime()); - logoutApplication(realm, resource, user, executor, resource.getNotBefore()); + logoutApplication(requestUri, realm, resource, user, executor, resource.getNotBefore()); } finally { executor.getHttpClient().getConnectionManager().shutdown(); } @@ -179,8 +188,8 @@ public class ResourceAdminManager { } - protected boolean logoutApplication(RealmModel realm, ApplicationModel resource, String user, ApacheHttpClient4Executor client, int notBefore) { - String managementUrl = resource.getManagementUrl(); + protected boolean logoutApplication(URI requestUri, RealmModel realm, ApplicationModel resource, String user, ApacheHttpClient4Executor client, int notBefore) { + String managementUrl = getManagementUrl(requestUri, resource); if (managementUrl != null) { LogoutAction adminAction = new LogoutAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getName(), user, notBefore); String token = new TokenManager().encodeToken(realm, adminAction); @@ -205,32 +214,32 @@ public class ResourceAdminManager { } } - public void pushRealmRevocationPolicy(RealmModel realm) { + public void pushRealmRevocationPolicy(URI requestUri, RealmModel realm) { ApacheHttpClient4Executor executor = createExecutor(); try { for (ApplicationModel application : realm.getApplications()) { - pushRevocationPolicy(realm, application, realm.getNotBefore(), executor); + pushRevocationPolicy(requestUri, realm, application, realm.getNotBefore(), executor); } } finally { executor.getHttpClient().getConnectionManager().shutdown(); } } - public void pushApplicationRevocationPolicy(RealmModel realm, ApplicationModel application) { + public void pushApplicationRevocationPolicy(URI requestUri, RealmModel realm, ApplicationModel application) { ApacheHttpClient4Executor executor = createExecutor(); try { - pushRevocationPolicy(realm, application, application.getNotBefore(), executor); + pushRevocationPolicy(requestUri, realm, application, application.getNotBefore(), executor); } finally { executor.getHttpClient().getConnectionManager().shutdown(); } } - protected boolean pushRevocationPolicy(RealmModel realm, ApplicationModel resource, int notBefore, ApacheHttpClient4Executor client) { + protected boolean pushRevocationPolicy(URI requestUri, RealmModel realm, ApplicationModel resource, int notBefore, ApacheHttpClient4Executor client) { if (notBefore <= 0) return false; - String managementUrl = resource.getManagementUrl(); + String managementUrl = getManagementUrl(requestUri, resource); if (managementUrl != null) { PushNotBeforeAction adminAction = new PushNotBeforeAction(TokenIdGenerator.generateId(), Time.currentTime() + 30, resource.getName(), notBefore); String token = new TokenManager().encodeToken(realm, adminAction); diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java index 30c7e557db..327f20ad1f 100755 --- a/services/src/main/java/org/keycloak/services/resources/AccountService.java +++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java @@ -56,6 +56,7 @@ import org.keycloak.services.messages.Messages; import org.keycloak.services.resources.flows.Flows; import org.keycloak.services.resources.flows.OAuthRedirect; import org.keycloak.services.resources.flows.Urls; +import org.keycloak.services.util.ResolveRelative; import org.keycloak.services.validation.Validation; import org.keycloak.social.SocialLoader; import org.keycloak.social.SocialProvider; @@ -514,7 +515,7 @@ public class AccountService { if (referrerUri != null) { referrerUri = TokenService.verifyRedirectUri(uriInfo, referrerUri, application); } else { - referrerUri = application.getBaseUrl(); + referrerUri = ResolveRelative.resolveRelativeUri(uriInfo.getRequestUri(), application.getBaseUrl()); } if (referrerUri != null) { diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java index 743af42605..32cf67847f 100755 --- a/services/src/main/java/org/keycloak/services/resources/TokenService.java +++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java @@ -723,7 +723,7 @@ public class TokenService { logger.infov("Logging out: {0}", user.getLoginName()); authManager.expireIdentityCookie(realm, uriInfo); authManager.expireRememberMeCookie(realm, uriInfo); - resourceAdminManager.logoutUser(realm, user); + resourceAdminManager.logoutUser(uriInfo.getRequestUri(), realm, user); audit.user(user).success(); } else { diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java index 0c8052ebf6..fcf2e0d433 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ApplicationResource.java @@ -204,7 +204,7 @@ public class ApplicationResource { @POST public void pushRevocation() { auth.requireManage(); - new ResourceAdminManager().pushApplicationRevocationPolicy(realm, application); + new ResourceAdminManager().pushApplicationRevocationPolicy(uriInfo.getRequestUri(), realm, application); } @Path("session-stats") @@ -220,7 +220,7 @@ public class ApplicationResource { if (users) stats.setUsers(new HashMap()); return stats; } - SessionStats stats = new ResourceAdminManager().getSessionStats(realm, application, users); + SessionStats stats = new ResourceAdminManager().getSessionStats(uriInfo.getRequestUri(), realm, application, users); if (stats == null) { logger.info("app returned null stats"); } else { @@ -234,7 +234,7 @@ public class ApplicationResource { @POST public void logoutAll() { auth.requireManage(); - new ResourceAdminManager().logoutApplication(realm, application, null); + new ResourceAdminManager().logoutApplication(uriInfo.getRequestUri(), realm, application, null); } @Path("logout-user/{username}") @@ -245,7 +245,7 @@ public class ApplicationResource { if (user == null) { throw new NotFoundException("User not found"); } - new ResourceAdminManager().logoutApplication(realm, application, user.getId()); + new ResourceAdminManager().logoutApplication(uriInfo.getRequestUri(), realm, application, user.getId()); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java index ab6fa496ec..862aeb2faf 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -32,6 +32,7 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -57,6 +58,9 @@ public class RealmAdminResource { @Context protected ProviderSession providers; + @Context + protected UriInfo uriInfo; + public RealmAdminResource(RealmAuth auth, RealmModel realm, TokenManager tokenManager) { this.auth = auth; this.realm = realm; @@ -145,14 +149,14 @@ public class RealmAdminResource { @POST public void pushRevocation() { auth.requireManage(); - new ResourceAdminManager().pushRealmRevocationPolicy(realm); + new ResourceAdminManager().pushRealmRevocationPolicy(uriInfo.getRequestUri(), realm); } @Path("logout-all") @POST public void logoutAll() { auth.requireManage(); - new ResourceAdminManager().logoutAll(realm); + new ResourceAdminManager().logoutAll(uriInfo.getRequestUri(), realm); } @Path("session-stats") @@ -165,7 +169,7 @@ public class RealmAdminResource { Map stats = new HashMap(); for (ApplicationModel applicationModel : realm.getApplications()) { if (applicationModel.getManagementUrl() == null) continue; - SessionStats appStats = new ResourceAdminManager().getSessionStats(realm, applicationModel, false); + SessionStats appStats = new ResourceAdminManager().getSessionStats(uriInfo.getRequestUri(), realm, applicationModel, false); stats.put(applicationModel.getName(), appStats); } return stats; diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java index 47c8128cf5..8dfcf5940e 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java @@ -172,7 +172,7 @@ public class UsersResource { Map stats = new HashMap(); for (ApplicationModel applicationModel : realm.getApplications()) { if (applicationModel.getManagementUrl() == null) continue; - UserStats appStats = new ResourceAdminManager().getUserStats(realm, applicationModel, user); + UserStats appStats = new ResourceAdminManager().getUserStats(uriInfo.getRequestUri(), realm, applicationModel, user); if (appStats == null) continue; if (appStats.isLoggedIn()) stats.put(applicationModel.getName(), appStats); } @@ -189,7 +189,7 @@ public class UsersResource { } // set notBefore so that user will be forced to log in. user.setNotBefore(Time.currentTime()); - new ResourceAdminManager().logoutUser(realm, user); + new ResourceAdminManager().logoutUser(uriInfo.getRequestUri(), realm, user); } diff --git a/services/src/main/java/org/keycloak/services/util/ResolveRelative.java b/services/src/main/java/org/keycloak/services/util/ResolveRelative.java new file mode 100755 index 0000000000..7833f213f2 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/util/ResolveRelative.java @@ -0,0 +1,20 @@ +package org.keycloak.services.util; + +import javax.ws.rs.core.UriBuilder; +import java.net.URI; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class ResolveRelative { + public static String resolveRelativeUri(URI requestUri, String url) { + if (url == null || !url.startsWith("/")) return url; + UriBuilder builder = UriBuilder.fromPath(url).host(requestUri.getHost()); + builder.scheme(requestUri.getScheme()); + if (requestUri.getPort() != -1) { + builder.port(requestUri.getPort()); + } + return builder.build().toString(); + } +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java new file mode 100755 index 0000000000..6ac6ff695f --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java @@ -0,0 +1,156 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012, Red Hat, Inc., and individual contributors + * as indicated by the @author tags. See the copyright.txt file in the + * distribution for a full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.keycloak.testsuite.adapter; + +import org.junit.Assert; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.keycloak.OAuth2Constants; +import org.keycloak.models.ApplicationModel; +import org.keycloak.models.Constants; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.representations.AccessToken; +import org.keycloak.representations.adapters.action.SessionStats; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.services.managers.RealmManager; +import org.keycloak.services.managers.TokenManager; +import org.keycloak.testsuite.OAuthClient; +import org.keycloak.testsuite.pages.LoginPage; +import org.keycloak.testsuite.rule.AbstractKeycloakRule; +import org.keycloak.testsuite.rule.WebResource; +import org.keycloak.testsuite.rule.WebRule; +import org.keycloak.testutils.KeycloakServer; +import org.openqa.selenium.WebDriver; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.GenericType; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.UriBuilder; +import java.net.URL; +import java.security.PublicKey; +import java.util.Map; + +/** + * Tests Undertow Adapter + * + * Also tests relative URIs in the adapter and valid redirect uris. + * Also tests adapters not configured with public key + * + * @author Bill Burke + */ +public class RelativeUriAdapterTest { + + public static final String LOGIN_URL = "http://localhost:8081/auth/rest/realms/demo/tokens/login"; + public static PublicKey realmPublicKey; + @ClassRule + public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule(){ + @Override + protected void configure(RealmManager manager, RealmModel adminRealm) { + RealmRepresentation representation = KeycloakServer.loadJson(getClass().getResourceAsStream("/adapter-test/demorealm-relative.json"), RealmRepresentation.class); + RealmModel realm = manager.importRealm(representation); + + realmPublicKey = realm.getPublicKey(); + + URL url = getClass().getResource("/adapter-test/cust-app-keycloak-relative.json"); + deployApplication("customer-portal", "/customer-portal", CustomerServlet.class, url.getPath(), "user"); + url = getClass().getResource("/adapter-test/customer-db-keycloak-relative.json"); + deployApplication("customer-db", "/customer-db", CustomerDatabaseServlet.class, url.getPath(), "user"); + url = getClass().getResource("/adapter-test/product-keycloak-relative.json"); + deployApplication("product-portal", "/product-portal", ProductServlet.class, url.getPath(), "user"); + ApplicationModel adminConsole = adminRealm.getApplicationByName(Constants.ADMIN_CONSOLE_APPLICATION); + TokenManager tm = new TokenManager(); + UserModel admin = adminRealm.getUser("admin"); + AccessToken token = tm.createClientAccessToken(null, adminRealm, adminConsole, admin); + adminToken = tm.encodeToken(adminRealm, token); + + } + }; + + public static String adminToken; + + @Rule + public WebRule webRule = new WebRule(this); + + @WebResource + protected WebDriver driver; + + @WebResource + protected OAuthClient oauth; + + @WebResource + protected LoginPage loginPage; + + @Test + public void testLoginSSOAndLogout() throws Exception { + // test login to customer-portal which does a bearer request to customer-db + driver.navigate().to("http://localhost:8081/customer-portal"); + System.out.println("Current url: " + driver.getCurrentUrl()); + Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL)); + loginPage.login("bburke@redhat.com", "password"); + System.out.println("Current url: " + driver.getCurrentUrl()); + Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/customer-portal"); + String pageSource = driver.getPageSource(); + System.out.println(pageSource); + Assert.assertTrue(pageSource.contains("Bill Burke") && pageSource.contains("Stian Thorgersen")); + + // test SSO + driver.navigate().to("http://localhost:8081/product-portal"); + Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8081/product-portal"); + pageSource = driver.getPageSource(); + System.out.println(pageSource); + Assert.assertTrue(pageSource.contains("iPhone") && pageSource.contains("iPad")); + + // View stats + Client client = ClientBuilder.newClient(); + WebTarget adminTarget = client.target("http://localhost:8081/auth/rest/admin/realms/demo"); + Map stats = adminTarget.path("session-stats").request() + .header(HttpHeaders.AUTHORIZATION, "Bearer " + adminToken) + .get(new GenericType>(){}); + + SessionStats custStats = stats.get("customer-portal"); + Assert.assertNotNull(custStats); + Assert.assertEquals(1, custStats.getActiveSessions()); + SessionStats prodStats = stats.get("product-portal"); + Assert.assertNotNull(prodStats); + Assert.assertEquals(1, prodStats.getActiveSessions()); + + client.close(); + + + // test logout + + String logoutUri = UriBuilder.fromUri("http://localhost:8081/auth/rest/realms/demo/tokens/logout") + .queryParam(OAuth2Constants.REDIRECT_URI, "http://localhost:8081/customer-portal").build().toString(); + driver.navigate().to(logoutUri); + Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL)); + driver.navigate().to("http://localhost:8081/product-portal"); + Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL)); + driver.navigate().to("http://localhost:8081/customer-portal"); + Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL)); + + + } +} diff --git a/testsuite/integration/src/test/resources/adapter-test/cust-app-keycloak-relative.json b/testsuite/integration/src/test/resources/adapter-test/cust-app-keycloak-relative.json new file mode 100755 index 0000000000..7e20025125 --- /dev/null +++ b/testsuite/integration/src/test/resources/adapter-test/cust-app-keycloak-relative.json @@ -0,0 +1,9 @@ +{ + "realm": "demo", + "resource": "customer-portal", + "auth-server-url": "/auth", + "ssl-not-required": true, + "credentials": { + "secret": "password" + } +} diff --git a/testsuite/integration/src/test/resources/adapter-test/customer-db-keycloak-relative.json b/testsuite/integration/src/test/resources/adapter-test/customer-db-keycloak-relative.json new file mode 100755 index 0000000000..4ab9db2718 --- /dev/null +++ b/testsuite/integration/src/test/resources/adapter-test/customer-db-keycloak-relative.json @@ -0,0 +1,9 @@ +{ + "realm" : "demo", + "resource" : "customer-db", + "auth-server-url": "/auth", + "ssl-not-required": true, + "bearer-only" : true, + "enable-cors" : true + +} diff --git a/testsuite/integration/src/test/resources/adapter-test/demorealm-relative.json b/testsuite/integration/src/test/resources/adapter-test/demorealm-relative.json new file mode 100755 index 0000000000..80132e0093 --- /dev/null +++ b/testsuite/integration/src/test/resources/adapter-test/demorealm-relative.json @@ -0,0 +1,120 @@ +{ + "realm": "demo", + "enabled": true, + "accessTokenLifespan": 3000, + "accessCodeLifespan": 10, + "accessCodeLifespanUserAction": 6000, + "sslNotRequired": true, + "registrationAllowed": false, + "social": false, + "updateProfileOnInitialSocialLogin": false, + "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=", + "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", + "requiredCredentials": [ "password" ], + "users" : [ + { + "username" : "bburke@redhat.com", + "enabled": true, + "email" : "bburke@redhat.com", + "firstName": "Bill", + "lastName": "Burke", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ] + } + ], + "roles" : { + "realm" : [ + { + "name": "user", + "description": "User privileges" + }, + { + "name": "admin", + "description": "Administrator privileges" + } + ] + }, + "roleMappings": [ + { + "username": "bburke@redhat.com", + "roles": ["user"] + } + ], + "scopeMappings": [ + { + "client": "third-party", + "roles": ["user"] + }, + { + "client": "customer-portal", + "roles": ["user"] + }, + { + "client": "product-portal", + "roles": ["user"] + } + + ], + "applications": [ + { + "name": "customer-portal", + "enabled": true, + "adminUrl": "/customer-portal", + "baseUrl": "/customer-portal", + "redirectUris": [ + "/customer-portal/*" + ], + "secret": "password" + }, + { + "name": "customer-portal-js", + "enabled": true, + "publicClient": true, + "baseUrl": "/customer-portal-js", + "redirectUris": [ + "/customer-portal-js/*" + ] + }, + { + "name": "customer-portal-cli", + "enabled": true, + "publicClient": true, + "redirectUris": [ + "urn:ietf:wg:oauth:2.0:oob", + "http://localhost" + ] + }, + { + "name": "product-portal", + "enabled": true, + "adminUrl": "/product-portal", + "baseUrl": "/product-portal", + "redirectUris": [ + "/product-portal/*" + ], + "secret": "password" + } + ], + "oauthClients": [ + { + "name": "third-party", + "enabled": true, + "redirectUris": [ + "/oauth-client/*", + "/oauth-client-cdi/*" + ], + "secret": "password" + } + ], + "applicationRoleMappings": { + "account": [ + { + "username": "bburke@redhat.com", + "roles": ["manage-account"] + } + ] + } + +} diff --git a/testsuite/integration/src/test/resources/adapter-test/product-keycloak-relative.json b/testsuite/integration/src/test/resources/adapter-test/product-keycloak-relative.json new file mode 100755 index 0000000000..4e5cd4c06b --- /dev/null +++ b/testsuite/integration/src/test/resources/adapter-test/product-keycloak-relative.json @@ -0,0 +1,9 @@ +{ + "realm" : "demo", + "resource" : "product-portal", + "auth-server-url" : "/auth", + "ssl-not-required" : true, + "credentials" : { + "secret": "password" + } +}