From 3a95401086d2b81ea10ba7ebfe8a5f870840b66a Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Fri, 21 Nov 2014 11:20:47 -0500 Subject: [PATCH] proxy constraints --- ...va => ConstraintAuthorizationHandler.java} | 16 +++-- .../proxy/ConstraintMatcherHandler.java | 16 ++++- .../keycloak/proxy/ProxyServerBuilder.java | 22 +++++- .../org/keycloak/testsuite/ProxyTest.java | 70 +++++++++++++++---- .../test/resources/tomcat-test/demorealm.json | 15 ++++ .../tomcat-test/webapp/WEB-INF/web.xml | 8 +++ 6 files changed, 122 insertions(+), 25 deletions(-) rename proxy/proxy-server/src/main/java/org/keycloak/proxy/{RoleAuthHandler.java => ConstraintAuthorizationHandler.java} (70%) diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/RoleAuthHandler.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintAuthorizationHandler.java similarity index 70% rename from proxy/proxy-server/src/main/java/org/keycloak/proxy/RoleAuthHandler.java rename to proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintAuthorizationHandler.java index 3beb870de4..75b19e252f 100755 --- a/proxy/proxy-server/src/main/java/org/keycloak/proxy/RoleAuthHandler.java +++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintAuthorizationHandler.java @@ -10,13 +10,13 @@ import java.util.Collection; * @author Bill Burke * @version $Revision: 1 $ */ -public class RoleAuthHandler implements HttpHandler { +public class ConstraintAuthorizationHandler implements HttpHandler { - protected Collection roles; protected HttpHandler next; + protected String errorPage; - public RoleAuthHandler(Collection roles, HttpHandler next) { - this.roles = roles; + public ConstraintAuthorizationHandler(String errorPage, HttpHandler next) { + this.errorPage = errorPage; this.next = next; } @@ -36,6 +36,14 @@ public class RoleAuthHandler implements HttpHandler { } } } + if (errorPage != null) { + exchange.setRequestPath(errorPage); + exchange.setRelativePath(errorPage); + exchange.setResolvedPath(errorPage); + next.handleRequest(exchange); + return; + + } exchange.setResponseCode(403); exchange.endExchange(); diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintMatcherHandler.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintMatcherHandler.java index bdbddc6b97..9eb49dbcd4 100755 --- a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintMatcherHandler.java +++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintMatcherHandler.java @@ -15,11 +15,13 @@ public class ConstraintMatcherHandler implements HttpHandler { protected SecurityPathMatches matcher; protected HttpHandler securedHandler; protected HttpHandler unsecuredHandler; + protected String errorPage; - public ConstraintMatcherHandler(SecurityPathMatches matcher, HttpHandler securedHandler, HttpHandler unsecuredHandler) { + public ConstraintMatcherHandler(SecurityPathMatches matcher, HttpHandler securedHandler, HttpHandler unsecuredHandler, String errorPage) { this.matcher = matcher; this.securedHandler = securedHandler; this.unsecuredHandler = unsecuredHandler; + this.errorPage = errorPage; } @Override @@ -31,8 +33,16 @@ public class ConstraintMatcherHandler implements HttpHandler { } if (match.getRequiredRoles().isEmpty() && match.getEmptyRoleSemantic() == SecurityInfo.EmptyRoleSemantic.DENY) { - exchange.setResponseCode(403); - exchange.endExchange(); + if (errorPage != null) { + exchange.setRequestPath(errorPage); + exchange.setRelativePath(errorPage); + exchange.setResolvedPath(errorPage); + unsecuredHandler.handleRequest(exchange); + } else { + exchange.setResponseCode(403); + exchange.endExchange(); + } + return; } exchange.getSecurityContext().setAuthenticationRequired(); exchange.putAttachment(CONSTRAINT_KEY, match); diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyServerBuilder.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyServerBuilder.java index f236579a05..16b8778d87 100755 --- a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyServerBuilder.java +++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyServerBuilder.java @@ -4,7 +4,6 @@ import io.undertow.Undertow; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.AuthenticationMode; import io.undertow.security.handlers.AuthenticationCallHandler; -import io.undertow.security.handlers.AuthenticationConstraintHandler; import io.undertow.security.handlers.AuthenticationMechanismsHandler; import io.undertow.security.handlers.SecurityInitialHandler; import io.undertow.security.idm.Account; @@ -72,7 +71,7 @@ public class ProxyServerBuilder { proxyHandler = new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { - exchange.setRelativePath(exchange.getResolvedPath()); // need this otherwise proxy forwards to chopped off path + exchange.setRelativePath(exchange.getRequestPath()); // need this otherwise proxy forwards to chopped off path handler.handleRequest(exchange); } }; @@ -93,12 +92,21 @@ public class ProxyServerBuilder { protected String base; protected SecurityPathMatches.Builder constraintBuilder = new SecurityPathMatches.Builder(); protected SecurityPathMatches matches; + protected String errorPage; public ApplicationBuilder base(String base) { this.base = base; return this; } + public ApplicationBuilder errorPage(String errorPage) { + if (errorPage != null && errorPage.startsWith("/")) { + errorPage = errorPage.substring(1); + } + this.errorPage = errorPage; + return this; + } + public ApplicationBuilder(AdapterConfig config) { this.deployment = KeycloakDeploymentBuilder.build(config); this.deploymentContext = new AdapterDeploymentContext(deployment); @@ -172,8 +180,16 @@ public class ProxyServerBuilder { private HttpHandler addSecurity(final HttpHandler toWrap) { HttpHandler handler = toWrap; handler = new UndertowAuthenticatedActionsHandler(deploymentContext, toWrap); + if (errorPage != null) { + if (base.endsWith("/")) { + errorPage = base + errorPage; + } else { + errorPage = base + "/" + errorPage; + } + } + handler = new ConstraintAuthorizationHandler(errorPage, handler); handler = new AuthenticationCallHandler(handler); - handler = new ConstraintMatcherHandler(matches, handler, toWrap); + handler = new ConstraintMatcherHandler(matches, handler, toWrap, errorPage); final List mechanisms = new LinkedList(); mechanisms.add(new CachedAuthenticatedSessionMechanism()); mechanisms.add(new UndertowAuthenticationMechanism(deploymentContext, userSessionManagement, nodesRegistrationManagement, -1)); diff --git a/testsuite/proxy/src/test/java/org/keycloak/testsuite/ProxyTest.java b/testsuite/proxy/src/test/java/org/keycloak/testsuite/ProxyTest.java index ecd0d594ab..511e02daef 100755 --- a/testsuite/proxy/src/test/java/org/keycloak/testsuite/ProxyTest.java +++ b/testsuite/proxy/src/test/java/org/keycloak/testsuite/ProxyTest.java @@ -98,7 +98,7 @@ public class ProxyTest { Integer count = (Integer)req.getSession().getAttribute("counter"); if (count == null) count = new Integer(0); req.getSession().setAttribute("counter", new Integer(count.intValue() + 1)); - stream.write(count.toString().getBytes()); + stream.write(("count:"+count).getBytes()); @@ -108,6 +108,18 @@ public class ProxyTest { doGet(req, resp); } } + public static class SendError extends HttpServlet { + @Override + protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { + resp.setContentType("text/plain"); + OutputStream stream = resp.getOutputStream(); + stream.write("access error".getBytes()); + } + @Override + protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { + doGet(req, resp); + } + } static Tomcat tomcat = null; @@ -133,7 +145,7 @@ public class ProxyTest { static Undertow proxyServer = null; - //@BeforeClass + @BeforeClass public static void initProxy() throws Exception { initTomcat(); ProxyServerBuilder builder = new ProxyServerBuilder().addHttpListener(8080, "localhost"); @@ -142,8 +154,13 @@ public class ProxyTest { builder.target("http://localhost:8082") .application(config) - .base("/customer-portal") - .constraint("/*").add().add(); + .base("/customer-portal") + .errorPage("/error.html") + .constraint("/users/*").role("user").add() + .constraint("/admins/*").role("admin").add() + .constraint("/users/permit").permit().add() + .constraint("/users/deny").deny().add() + .add(); proxyServer = builder.build(); proxyServer.start(); @@ -167,34 +184,57 @@ public class ProxyTest { @Test public void testLoginSSOAndLogout() throws Exception { - initProxy(); - driver.navigate().to("http://localhost:8080/customer-portal"); + driver.navigate().to("http://localhost:8080/customer-portal/users"); 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:8080/customer-portal"); + Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/customer-portal/users"); String pageSource = driver.getPageSource(); System.out.println(pageSource); - Assert.assertTrue(pageSource.contains("customer-portal")); - Assert.assertTrue(pageSource.contains("0")); - driver.navigate().to("http://localhost:8080/customer-portal"); - Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/customer-portal"); + Assert.assertTrue(pageSource.contains("customer-portal/users")); + Assert.assertTrue(pageSource.contains("count:0")); + driver.navigate().to("http://localhost:8080/customer-portal/users"); + Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/customer-portal/users"); pageSource = driver.getPageSource(); System.out.println(pageSource); - Assert.assertTrue(pageSource.contains("customer-portal")); - Assert.assertTrue(pageSource.contains("1")); // test http session + Assert.assertTrue(pageSource.contains("customer-portal/users")); + Assert.assertTrue(pageSource.contains("count:1")); // test http session + + driver.navigate().to("http://localhost:8080/customer-portal/users/deny"); + Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/customer-portal/users/deny"); + pageSource = driver.getPageSource(); + System.out.println(pageSource); + Assert.assertTrue(pageSource.contains("access error")); + + driver.navigate().to("http://localhost:8080/customer-portal/admins"); + Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/customer-portal/admins"); + pageSource = driver.getPageSource(); + System.out.println(pageSource); + Assert.assertTrue(pageSource.contains("access error")); + + // test logout String logoutUri = OpenIDConnectService.logoutUrl(UriBuilder.fromUri("http://localhost:8081/auth")) - .queryParam(OAuth2Constants.REDIRECT_URI, "http://localhost:8080/customer-portal").build("demo").toString(); + .queryParam(OAuth2Constants.REDIRECT_URI, "http://localhost:8080/customer-portal/users").build("demo").toString(); driver.navigate().to(logoutUri); Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL)); - driver.navigate().to("http://localhost:8080/customer-portal"); + driver.navigate().to("http://localhost:8080/customer-portal/users"); String currentUrl = driver.getCurrentUrl(); Assert.assertTrue(currentUrl.startsWith(LOGIN_URL)); + // test unsecured page + driver.navigate().to("http://localhost:8080/customer-portal") ; + pageSource = driver.getPageSource(); + System.out.println(pageSource); + Assert.assertTrue(pageSource.contains("customer-portal")); + driver.navigate().to("http://localhost:8080/customer-portal/users/permit") ; + pageSource = driver.getPageSource(); + System.out.println(pageSource); + Assert.assertTrue(pageSource.contains("customer-portal/users/permit")); + } diff --git a/testsuite/proxy/src/test/resources/tomcat-test/demorealm.json b/testsuite/proxy/src/test/resources/tomcat-test/demorealm.json index a4a6ec9903..29de45cb9c 100755 --- a/testsuite/proxy/src/test/resources/tomcat-test/demorealm.json +++ b/testsuite/proxy/src/test/resources/tomcat-test/demorealm.json @@ -24,6 +24,21 @@ { "type" : "password", "value" : "password" } ], + "realmRoles": [ "user" ], + "applicationRoles": { + "account": [ "manage-account" ] + } + }, + { + "username" : "admin", + "enabled": true, + "email" : "admin.burke@redhat.com", + "firstName": "Admin", + "lastName": "Burke", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ], "realmRoles": [ "user", "admin" ], "applicationRoles": { "account": [ "manage-account" ] diff --git a/testsuite/proxy/src/test/resources/tomcat-test/webapp/WEB-INF/web.xml b/testsuite/proxy/src/test/resources/tomcat-test/webapp/WEB-INF/web.xml index 7e0b269ed6..8e76e3ee53 100755 --- a/testsuite/proxy/src/test/resources/tomcat-test/webapp/WEB-INF/web.xml +++ b/testsuite/proxy/src/test/resources/tomcat-test/webapp/WEB-INF/web.xml @@ -10,7 +10,15 @@ SendUsername org.keycloak.testsuite.ProxyTest$SendUsernameServlet + + Error + org.keycloak.testsuite.ProxyTest$SendError + + + Error + /error.html + SendUsername /*