keycloak-uncached

KEYCLOAK-2962 Autodetect bearrer-only clients Suport

12/18/2016 12:25:50 PM

Details

diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
index b9ee4c6..8664800 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java
@@ -57,6 +57,7 @@ public class KeycloakDeployment {
 
     protected String resourceName;
     protected boolean bearerOnly;
+    protected boolean autodetectBearerOnly;
     protected boolean enableBasicAuth;
     protected boolean publicClient;
     protected Map<String, Object> resourceCredentials = new HashMap<>();
@@ -201,6 +202,14 @@ public class KeycloakDeployment {
         this.bearerOnly = bearerOnly;
     }
 
+    public boolean isAutodetectBearerOnly() {
+        return autodetectBearerOnly;
+    }
+
+    public void setAutodetectBearerOnly(boolean autodetectBearerOnly) {
+        this.autodetectBearerOnly = autodetectBearerOnly;
+    }
+
     public boolean isEnableBasicAuth() {
         return enableBasicAuth;
     }
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
index 85b19ca..65e9456 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java
@@ -99,6 +99,7 @@ public class KeycloakDeploymentBuilder {
         }
 
         deployment.setBearerOnly(adapterConfig.isBearerOnly());
+        deployment.setAutodetectBearerOnly(adapterConfig.isAutodetectBearerOnly());
         deployment.setEnableBasicAuth(adapterConfig.isEnableBasicAuth());
         deployment.setAlwaysRefreshToken(adapterConfig.isAlwaysRefreshToken());
         deployment.setRegisterNodeAtStartup(adapterConfig.isRegisterNodeAtStartup());
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java
index c04f21c..0cbe687 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RequestAuthenticator.java
@@ -17,6 +17,8 @@
 
 package org.keycloak.adapters;
 
+import java.util.Collections;
+import java.util.List;
 import org.jboss.logging.Logger;
 import org.keycloak.KeycloakPrincipal;
 import org.keycloak.adapters.spi.AuthChallenge;
@@ -116,6 +118,12 @@ public abstract class RequestAuthenticator {
             return AuthOutcome.NOT_ATTEMPTED;
         }
 
+        if (isAutodetectedBearerOnly(facade.getRequest())) {
+            challenge = bearer.getChallenge();
+            log.debug("NOT_ATTEMPTED: Treating as bearer only");
+            return AuthOutcome.NOT_ATTEMPTED;
+        }
+
         if (log.isTraceEnabled()) {
             log.trace("try oauth");
         }
@@ -158,6 +166,36 @@ public abstract class RequestAuthenticator {
         return false;
     }
 
+    protected boolean isAutodetectedBearerOnly(HttpFacade.Request request) {
+        if (!deployment.isAutodetectBearerOnly()) return false;
+
+        String headerValue = facade.getRequest().getHeader("X-Requested-With");
+        if (headerValue != null && headerValue.equalsIgnoreCase("XMLHttpRequest")) {
+            return true;
+        }
+
+        headerValue = facade.getRequest().getHeader("Faces-Request");
+        if (headerValue != null && headerValue.startsWith("partial/")) {
+            return true;
+        }
+
+        headerValue = facade.getRequest().getHeader("SOAPAction");
+        if (headerValue != null) {
+            return true;
+        }
+
+        List<String> accepts = facade.getRequest().getHeaders("Accept");
+        if (accepts == null) accepts = Collections.emptyList();
+
+        for (String accept : accepts) {
+            if (accept.contains("text/html") || accept.contains("text/*") || accept.contains("*/*")) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
     protected abstract OAuthRequestAuthenticator createOAuthAuthenticator();
 
     protected BearerTokenRequestAuthenticator createBearerTokenAuthenticator() {
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
index e4065bc..0a107bb 100755
--- a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java
@@ -30,7 +30,7 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
         "resource", "public-client", "credentials",
         "use-resource-role-mappings",
         "enable-cors", "cors-max-age", "cors-allowed-methods",
-        "expose-token", "bearer-only",
+        "expose-token", "bearer-only", "autodetect-bearer-only",
         "connection-pool-size",
         "allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password",
         "client-keystore", "client-keystore-password", "client-key-password",
diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java
index f9138a1..3cef2a0 100755
--- a/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java
+++ b/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java
@@ -33,7 +33,7 @@ import java.util.Map;
         "resource", "public-client", "credentials",
         "use-resource-role-mappings",
         "enable-cors", "cors-max-age", "cors-allowed-methods",
-        "expose-token", "bearer-only", "enable-basic-auth"})
+        "expose-token", "bearer-only", "autodetect-bearer-only", "enable-basic-auth"})
 public class BaseAdapterConfig extends BaseRealmConfig {
     @JsonProperty("resource")
     protected String resource;
@@ -51,6 +51,8 @@ public class BaseAdapterConfig extends BaseRealmConfig {
     protected boolean exposeToken;
     @JsonProperty("bearer-only")
     protected boolean bearerOnly;
+    @JsonProperty("autodetect-bearer-only")
+    protected boolean autodetectBearerOnly;
     @JsonProperty("enable-basic-auth")
     protected boolean enableBasicAuth;
     @JsonProperty("public-client")
@@ -123,6 +125,14 @@ public class BaseAdapterConfig extends BaseRealmConfig {
         this.bearerOnly = bearerOnly;
     }
 
+    public boolean isAutodetectBearerOnly() {
+        return autodetectBearerOnly;
+    }
+
+    public void setAutodetectBearerOnly(boolean autodetectBearerOnly) {
+        this.autodetectBearerOnly = autodetectBearerOnly;
+    }
+
     public boolean isEnableBasicAuth() {
         return enableBasicAuth;
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
index 404d24d..1f2f620 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java
@@ -72,6 +72,12 @@ public class AdapterTest {
                     .name("product-portal").contextPath("/product-portal")
                     .servletClass(ProductServlet.class).adapterConfigPath(url.getPath())
                     .role("user").deployApplication();
+           
+            url = getClass().getResource("/adapter-test/product-autodetect-bearer-only-keycloak.json");
+            createApplicationDeployment()
+                    .name("product-portal-autodetect-bearer-only").contextPath("/product-portal-autodetect-bearer-only")
+                    .servletClass(ProductServlet.class).adapterConfigPath(url.getPath())
+                    .role("user").deployApplication();
 
             // Test that replacing system properties works for adapters
             System.setProperty("app.server.base.url", "http://localhost:8081");
@@ -150,6 +156,11 @@ public class AdapterTest {
     }
 
     @Test
+    public void testAutodetectBearerOnly() throws Exception {
+        testStrategy.testAutodetectBearerOnly();
+    }
+
+    @Test
     public void testBasicAuthErrorHandling() throws Exception {
         testStrategy.testBasicAuthErrorHandling();
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java
index 9341df2..bd0a144 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java
@@ -400,6 +400,55 @@ public class AdapterTestStrategy extends ExternalResource {
         Time.setOffset(0);
     }
 
+    public void testAutodetectBearerOnly() throws Exception {
+        Client client = ClientBuilder.newClient();
+
+        // Do not redirect client to login page if it's an XHR
+        WebTarget target = client.target(APP_SERVER_BASE_URL + "/product-portal-autodetect-bearer-only");
+        Response response = target.request().header("X-Requested-With", "XMLHttpRequest").get();
+        Assert.assertEquals(401, response.getStatus());
+        response.close();
+
+        // Do not redirect client to login page if it's a partial Faces request
+        response = target.request().header("Faces-Request", "partial/ajax").get();
+        Assert.assertEquals(401, response.getStatus());
+        response.close();
+        
+        // Do not redirect client to login page if it's a SOAP request
+        response = target.request().header("SOAPAction", "").get();
+        Assert.assertEquals(401, response.getStatus());
+        response.close();
+
+        // Do not redirect client to login page if Accept header is missing
+        response = target.request().get();
+        Assert.assertEquals(401, response.getStatus());
+        response.close();
+
+        // Do not redirect client to login page if client does not understand HTML reponses
+        response = target.request().header(HttpHeaders.ACCEPT, "application/json,text/xml").get();
+        Assert.assertEquals(401, response.getStatus());
+        response.close();
+
+        // Redirect client to login page if it's not an XHR
+        response = target.request().header("X-Requested-With", "Dont-Know").header(HttpHeaders.ACCEPT, "*/*").get();
+        Assert.assertEquals(302, response.getStatus());
+        Assert.assertTrue(response.getHeaderString(HttpHeaders.LOCATION).contains("response_type=code"));
+        response.close();
+
+        // Redirect client to login page if client explicitely understands HTML responses
+        response = target.request().header(HttpHeaders.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9").get();
+        Assert.assertEquals(302, response.getStatus());
+        Assert.assertTrue(response.getHeaderString(HttpHeaders.LOCATION).contains("response_type=code"));
+        response.close();
+
+        // Redirect client to login page if client understands all response types
+        response = target.request().header(HttpHeaders.ACCEPT, "*/*").get();
+        Assert.assertEquals(302, response.getStatus());
+        Assert.assertTrue(response.getHeaderString(HttpHeaders.LOCATION).contains("response_type=code"));
+        response.close();
+        client.close();
+    }
+
     /**
      * KEYCLOAK-518
      * @throws Exception
diff --git a/testsuite/integration/src/test/resources/adapter-test/product-autodetect-bearer-only-keycloak.json b/testsuite/integration/src/test/resources/adapter-test/product-autodetect-bearer-only-keycloak.json
new file mode 100644
index 0000000..b92abc6
--- /dev/null
+++ b/testsuite/integration/src/test/resources/adapter-test/product-autodetect-bearer-only-keycloak.json
@@ -0,0 +1,11 @@
+{
+  "realm" : "demo",
+  "resource" : "product-portal",
+  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+  "auth-server-url" : "http://localhost:8081/auth",
+  "ssl-required" : "external",
+  "credentials" : {
+      "secret": "password"
+   },
+   "autodetect-bearer-only" : true
+}