keycloak-uncached

fix proxy

12/5/2014 8:38:56 PM

Details

diff --git a/docbook/reference/en/en-US/modules/proxy.xml b/docbook/reference/en/en-US/modules/proxy.xml
index f5f6053..3a17557 100755
--- a/docbook/reference/en/en-US/modules/proxy.xml
+++ b/docbook/reference/en/en-US/modules/proxy.xml
@@ -30,6 +30,7 @@ $ java -jar bin/launcher.jar [your-config.json]
 <programlisting><![CDATA[
 {
     "target-url": "http://localhost:8082",
+    "send-access-token": true,
     "bind-address": "localhost",
     "http-port": "8080",
     "https-port": "8443",
@@ -93,6 +94,15 @@ $ java -jar bin/launcher.jar [your-config.json]
                     </listitem>
                 </varlistentry>
                 <varlistentry>
+                    <term>send-access-token</term>
+                    <listitem>
+                        <para>
+                            Boolean flag.  If true, this will send the access token via the KEYCLOAK_ACCESS_TOKEN header to the
+                            proxied server. <emphasis>OPTIONAL.</emphasis>.  Default is false.
+                        </para>
+                    </listitem>
+                </varlistentry>
+                <varlistentry>
                     <term>bind-address</term>
                     <listitem>
                         <para>
@@ -313,6 +323,15 @@ $ java -jar bin/launcher.jar [your-config.json]
                         </para>
                     </listitem>
                 </varlistentry>
+                <varlistentry>
+                    <term>KEYCLOAK_ACCESS_TOKEN</term>
+                    <listitem>
+                        <para>
+                            Send the access token in this header if the proxy was configured to send it.  This token can
+                            be used to make bearer token requests.
+                        </para>
+                    </listitem>
+                </varlistentry>
             </variablelist>
         </para>
     </section>
diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintAuthorizationHandler.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintAuthorizationHandler.java
index d0ff18b..9889d51 100755
--- a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintAuthorizationHandler.java
+++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintAuthorizationHandler.java
@@ -17,13 +17,16 @@ public class ConstraintAuthorizationHandler implements HttpHandler {
     public static final HttpString KEYCLOAK_USERNAME = new HttpString("KEYCLOAK_USERNAME");
     public static final HttpString KEYCLOAK_EMAIL = new HttpString("KEYCLOAK_EMAIL");
     public static final HttpString KEYCLOAK_NAME = new HttpString("KEYCLOAK_NAME");
+    public static final HttpString KEYCLOAK_ACCESS_TOKEN = new HttpString("KEYCLOAK_ACCESS_TOKEN");
 
     protected HttpHandler next;
     protected String errorPage;
+    protected boolean sendAccessToken;
 
-    public ConstraintAuthorizationHandler(String errorPage, HttpHandler next) {
-        this.errorPage = errorPage;
+    public ConstraintAuthorizationHandler(HttpHandler next, String errorPage, boolean sendAccessToken) {
         this.next = next;
+        this.errorPage = errorPage;
+        this.sendAccessToken = sendAccessToken;
     }
 
     @Override
@@ -57,7 +60,8 @@ public class ConstraintAuthorizationHandler implements HttpHandler {
 
     public void authenticatedRequest(KeycloakUndertowAccount account, HttpServerExchange exchange) throws Exception {
         if (account != null) {
-            IDToken idToken = account.getKeycloakSecurityContext().getIdToken();
+            IDToken idToken = account.getKeycloakSecurityContext().getToken();
+            if (idToken == null) return;
             if (idToken.getSubject() != null) {
                 exchange.getRequestHeaders().put(KEYCLOAK_SUBJECT, idToken.getSubject());
             }
@@ -70,6 +74,9 @@ public class ConstraintAuthorizationHandler implements HttpHandler {
             if (idToken.getName() != null) {
                 exchange.getRequestHeaders().put(KEYCLOAK_NAME, idToken.getName());
             }
+            if (sendAccessToken) {
+                exchange.getRequestHeaders().put(KEYCLOAK_ACCESS_TOKEN, account.getKeycloakSecurityContext().getTokenString());
+            }
         }
         next.handleRequest(exchange);
     }
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 2fd260b..998302d 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
@@ -29,7 +29,7 @@ public class ConstraintMatcherHandler implements HttpHandler {
     @Override
     public void handleRequest(HttpServerExchange exchange) throws Exception {
         log.debugv("ConstraintMatcherHandler: {0}", exchange.getRelativePath());
-        SingleConstraintMatch match = matcher.getSecurityInfo(exchange.getRelativePath(), exchange.getRequestMethod().toString()).getMergedConstraint();
+        SingleConstraintMatch match = matcher.getSecurityInfo(exchange.getRelativePath(), exchange.getRequestMethod().toString());
         if (match == null || (match.getRequiredRoles().isEmpty() && match.getEmptyRoleSemantic() == SecurityInfo.EmptyRoleSemantic.PERMIT)) {
             unsecuredHandler.handleRequest(exchange);
             return;
diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyAuthenticationCallHandler.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyAuthenticationCallHandler.java
new file mode 100755
index 0000000..7246693
--- /dev/null
+++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyAuthenticationCallHandler.java
@@ -0,0 +1,35 @@
+package org.keycloak.proxy;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ProxyAuthenticationCallHandler implements HttpHandler {
+
+    private final HttpHandler next;
+
+    public ProxyAuthenticationCallHandler(final HttpHandler next) {
+        this.next = next;
+    }
+
+    /**
+     * Only allow the request through if successfully authenticated or if authentication is not required.
+     *
+     * @see io.undertow.server.HttpHandler#handleRequest(io.undertow.server.HttpServerExchange)
+     */
+    @Override
+    public void handleRequest(final HttpServerExchange exchange) throws Exception {
+        SecurityContext context = exchange.getSecurityContext();
+        if (context.authenticate()) {
+            if(!exchange.isComplete()) {
+                next.handleRequest(exchange);
+            }
+        } else {
+            exchange.endExchange();
+        }
+    }
+}
diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyConfig.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyConfig.java
index 66b7f52..dd41a05 100755
--- a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyConfig.java
+++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyConfig.java
@@ -37,6 +37,8 @@ public class ProxyConfig {
     protected Boolean directBuffers;
     @JsonProperty("target-url")
     protected String targetUrl;
+    @JsonProperty("send-access-token")
+    protected boolean sendAccessToken;
     @JsonProperty("applications")
     protected List<Application> applications = new LinkedList<Application>();
 
@@ -144,6 +146,14 @@ public class ProxyConfig {
         this.applications = applications;
     }
 
+    public boolean isSendAccessToken() {
+        return sendAccessToken;
+    }
+
+    public void setSendAccessToken(boolean sendAccessToken) {
+        this.sendAccessToken = sendAccessToken;
+    }
+
     public static class Application {
         @JsonProperty("base-path")
         protected String basePath;
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 379656b..8835856 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
@@ -76,6 +76,7 @@ public class ProxyServerBuilder {
 
     protected PathHandler root = new PathHandler(NOT_FOUND);
     protected HttpHandler proxyHandler;
+    protected boolean sendAccessToken;
 
     public ProxyServerBuilder target(String uri) {
         SimpleProxyClientProvider provider = null;
@@ -95,6 +96,10 @@ public class ProxyServerBuilder {
         return this;
     }
 
+    public ProxyServerBuilder sendAccessToken(boolean flag) {
+        this.sendAccessToken = flag;
+        return this;
+    }
     public ApplicationBuilder application(AdapterConfig config) {
         return new ApplicationBuilder(config);
     }
@@ -219,8 +224,8 @@ public class ProxyServerBuilder {
                     errorPage = base + "/" + errorPage;
                 }
             }
-            handler = new ConstraintAuthorizationHandler(errorPage, handler);
-            handler = new AuthenticationCallHandler(handler);
+            handler = new ConstraintAuthorizationHandler(handler, errorPage, sendAccessToken);
+            handler = new ProxyAuthenticationCallHandler(handler);
             handler = new ConstraintMatcherHandler(matches, handler, toWrap, errorPage);
             final List<AuthenticationMechanism> mechanisms = new LinkedList<AuthenticationMechanism>();
             mechanisms.add(new CachedAuthenticatedSessionMechanism());
@@ -379,6 +384,7 @@ public class ProxyServerBuilder {
     }
 
     public static void initOptions(ProxyConfig config, ProxyServerBuilder builder) {
+        builder.sendAccessToken(config.isSendAccessToken());
         if (config.getBufferSize() != null) builder.setBufferSize(config.getBufferSize());
         if (config.getBuffersPerRegion() != null) builder.setBuffersPerRegion(config.getBuffersPerRegion());
         if (config.getIoThreads() != null) builder.setIoThreads(config.getIoThreads());
diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/SecurityPathMatches.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/SecurityPathMatches.java
index c5c39a0..d894829 100755
--- a/proxy/proxy-server/src/main/java/org/keycloak/proxy/SecurityPathMatches.java
+++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/SecurityPathMatches.java
@@ -58,19 +58,19 @@ public class SecurityPathMatches {
                 extensionRoleInformation.isEmpty();
     }
 
-    public SecurityPathMatch getSecurityInfo(final String path, final String method) {
+    public SingleConstraintMatch getSecurityInfo(final String path, final String method) {
         RuntimeMatch currentMatch = new RuntimeMatch();
         handleMatch(method, defaultPathSecurityInformation, currentMatch);
         PathSecurityInformation match = exactPathRoleInformation.get(path);
         if (match != null) {
             handleMatch(method, match, currentMatch);
-            return new SecurityPathMatch(mergeConstraints(currentMatch));
+            return mergeConstraints(currentMatch);
         }
 
         match = prefixPathRoleInformation.get(path);
         if (match != null) {
             handleMatch(method, match, currentMatch);
-            return new SecurityPathMatch(mergeConstraints(currentMatch));
+            return mergeConstraints(currentMatch);
         }
 
         int qsPos = -1;
@@ -83,7 +83,7 @@ public class SecurityPathMatches {
                 match = exactPathRoleInformation.get(part);
                 if (match != null) {
                     handleMatch(method, match, currentMatch);
-                    return new SecurityPathMatch(mergeConstraints(currentMatch));
+                    return mergeConstraints(currentMatch);
                 }
                 qsPos = i;
                 extension = false;
@@ -93,7 +93,7 @@ public class SecurityPathMatches {
                 match = prefixPathRoleInformation.get(part);
                 if (match != null) {
                     handleMatch(method, match, currentMatch);
-                    return new SecurityPathMatch(mergeConstraints(currentMatch));
+                    return mergeConstraints(currentMatch);
                 }
             } else if (c == '.') {
                 if (!extension) {
@@ -107,12 +107,12 @@ public class SecurityPathMatches {
                     match = extensionRoleInformation.get(ext);
                     if (match != null) {
                         handleMatch(method, match, currentMatch);
-                        return new SecurityPathMatch(mergeConstraints(currentMatch));
+                        return mergeConstraints(currentMatch);
                     }
                 }
             }
         }
-        return new SecurityPathMatch(mergeConstraints(currentMatch));
+        return mergeConstraints(currentMatch);
     }
 
     /**
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 b025ba8..c6e3698 100755
--- a/testsuite/proxy/src/test/java/org/keycloak/testsuite/ProxyTest.java
+++ b/testsuite/proxy/src/test/java/org/keycloak/testsuite/ProxyTest.java
@@ -62,6 +62,11 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import java.io.File;
 import java.io.IOException;
@@ -90,6 +95,7 @@ public class ProxyTest {
     public static class SendUsernameServlet extends HttpServlet {
         @Override
         protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
+            String requestURI = req.getRequestURI();
             resp.setContentType("text/plain");
             OutputStream stream = resp.getOutputStream();
             stream.write(req.getRequestURL().toString().getBytes());
@@ -104,6 +110,34 @@ public class ProxyTest {
                 String name = headers.nextElement();
                 System.out.println(name +": " + req.getHeader(name));
             }
+
+            if (requestURI.contains("/bearer")) {
+                Client client = ClientBuilder.newClient();
+
+                try {
+                    String appBase = "http://localhost:8080/customer-portal";
+                    WebTarget target = client.target(appBase + "/call-bearer");
+
+                    Response response = null;
+                    response = target.request()
+                            .header(HttpHeaders.AUTHORIZATION, "Bearer CRAP")
+                            .get();
+                    Assert.assertEquals(401, response.getStatus());
+                    response.close();
+                    response = target.request()
+                            .header(HttpHeaders.AUTHORIZATION, "Bearer " + req.getHeader("KEYCLOAK_ACCESS_TOKEN"))
+                            .get();
+                    Assert.assertEquals(200, response.getStatus());
+                    String data = response.readEntity(String.class);
+                    response.close();
+                    stream.write(data.getBytes());
+                } finally {
+                    client.close();
+                }
+
+            } else if (requestURI.contains("/call-bearer")) {
+                stream.write("bearer called".getBytes());
+            }
         }
         @Override
         protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
@@ -151,20 +185,6 @@ public class ProxyTest {
     public static void initProxy() throws Exception {
         initTomcat();
         InputStream is = ProxyTest.class.getResourceAsStream("/proxy-config.json");
-        /*
-        ProxyServerBuilder builder = new ProxyServerBuilder().addHttpListener(8080, "localhost");
-        AdapterConfig config = KeycloakDeploymentBuilder.loadAdapterConfig(is);
-
-        builder.target("http://localhost:8082")
-                .application(config)
-                    .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 = ProxyServerBuilder.build(is);
         proxyServer.start();
 
@@ -221,6 +241,11 @@ public class ProxyTest {
         Assert.assertTrue(pageSource.contains("customer-portal/users"));
         Assert.assertTrue(pageSource.contains("count:1")); // test http session
 
+        driver.navigate().to(baseUrl + "/customer-portal/bearer");
+        pageSource = driver.getPageSource();
+        Assert.assertTrue(pageSource.contains("bearer called"));
+
+
         driver.navigate().to(baseUrl + "/customer-portal/users/deny");
         Assert.assertEquals(driver.getCurrentUrl(), baseUrl + "/customer-portal/users/deny");
         pageSource = driver.getPageSource();
diff --git a/testsuite/proxy/src/test/resources/proxy-config.json b/testsuite/proxy/src/test/resources/proxy-config.json
index c6f583b..06c9eda 100755
--- a/testsuite/proxy/src/test/resources/proxy-config.json
+++ b/testsuite/proxy/src/test/resources/proxy-config.json
@@ -6,6 +6,7 @@
     "keystore-password": "password",
     "key-password": "password",
     "target-url": "http://localhost:8082",
+    "send-access-token": true,
     "applications": [
         {
             "base-path": "/customer-portal",
@@ -30,6 +31,18 @@
                     ]
                 },
                 {
+                    "pattern": "/call-bearer/*",
+                    "roles-allowed": [
+                        "user"
+                    ]
+                },
+                {
+                    "pattern": "/bearer/*",
+                    "roles-allowed": [
+                        "user"
+                    ]
+                },
+                {
                     "pattern": "/admins/*",
                     "roles-allowed": [
                         "admin"