keycloak-aplcache

Details

diff --git a/core/src/main/java/org/keycloak/AbstractOAuthClient.java b/core/src/main/java/org/keycloak/AbstractOAuthClient.java
index 600dab0..00fa0ed 100755
--- a/core/src/main/java/org/keycloak/AbstractOAuthClient.java
+++ b/core/src/main/java/org/keycloak/AbstractOAuthClient.java
@@ -1,5 +1,6 @@
 package org.keycloak;
 
+import org.keycloak.enums.RelativeUrlsUsed;
 import org.keycloak.util.KeycloakUriBuilder;
 
 import java.util.Map;
@@ -19,7 +20,7 @@ public class AbstractOAuthClient {
     protected String authUrl;
     protected String codeUrl;
     protected String refreshUrl;
-    protected boolean relativeUrls;
+    protected RelativeUrlsUsed relativeUrlsUsed;
     protected String scope;
     protected String stateCookieName = OAUTH_TOKEN_REQUEST_STATE;
     protected String stateCookiePath;
@@ -101,12 +102,12 @@ public class AbstractOAuthClient {
         this.publicClient = publicClient;
     }
 
-    public boolean isRelativeUrls() {
-        return relativeUrls;
+    public RelativeUrlsUsed getRelativeUrlsUsed() {
+        return relativeUrlsUsed;
     }
 
-    public void setRelativeUrls(boolean relativeUrls) {
-        this.relativeUrls = relativeUrls;
+    public void setRelativeUrlsUsed(RelativeUrlsUsed relativeUrlsUsed) {
+        this.relativeUrlsUsed = relativeUrlsUsed;
     }
 
     protected String stripOauthParametersFromRedirect(String uri) {
diff --git a/core/src/main/java/org/keycloak/enums/RelativeUrlsUsed.java b/core/src/main/java/org/keycloak/enums/RelativeUrlsUsed.java
new file mode 100644
index 0000000..333986a
--- /dev/null
+++ b/core/src/main/java/org/keycloak/enums/RelativeUrlsUsed.java
@@ -0,0 +1,36 @@
+package org.keycloak.enums;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public enum RelativeUrlsUsed {
+
+    /**
+     * Always use relative URI and resolve them later based on browser HTTP request
+     */
+    ALL_REQUESTS,
+
+    /**
+     * Use relative Uris just for browser requests and resolve those based on browser HTTP requests.
+     * Backend request (like refresh token request, codeToToken request etc) will use the URI based on current hostname
+     */
+    BROWSER_ONLY,
+
+    /**
+     * Relative Uri not used. Configuration contains absolute URI
+     */
+    NEVER;
+
+    public boolean useRelative(boolean browserReq) {
+        switch (this) {
+            case ALL_REQUESTS:
+                return true;
+            case NEVER:
+                return false;
+            case BROWSER_ONLY:
+                return browserReq;
+            default:
+                return true;
+        }
+    }
+}
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 39552b2..acb4243 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
@@ -16,7 +16,8 @@ import org.codehaus.jackson.annotate.JsonPropertyOrder;
         "expose-token", "bearer-only",
         "connection-pool-size",
         "allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password",
-        "client-keystore", "client-keystore-password", "client-key-password"
+        "client-keystore", "client-keystore-password", "client-key-password",
+        "use-hostname-for-local-requests", "local-requests-scheme", "local-requests-port"
 })
 public class AdapterConfig extends BaseAdapterConfig {
 
@@ -36,6 +37,12 @@ public class AdapterConfig extends BaseAdapterConfig {
     protected String clientKeyPassword;
     @JsonProperty("connection-pool-size")
     protected int connectionPoolSize = 20;
+    @JsonProperty("use-hostname-for-local-requests")
+    protected boolean useHostnameForLocalRequests;
+    @JsonProperty("local-requests-scheme")
+    protected String localRequestsScheme = "http";
+    @JsonProperty("local-requests-port")
+    protected int localRequestsPort = 8080;
 
     public boolean isAllowAnyHostname() {
         return allowAnyHostname;
@@ -101,4 +108,27 @@ public class AdapterConfig extends BaseAdapterConfig {
         this.connectionPoolSize = connectionPoolSize;
     }
 
+    public boolean isUseHostnameForLocalRequests() {
+        return useHostnameForLocalRequests;
+    }
+
+    public void setUseHostnameForLocalRequests(boolean useHostnameForLocalRequests) {
+        this.useHostnameForLocalRequests = useHostnameForLocalRequests;
+    }
+
+    public String getLocalRequestsScheme() {
+        return localRequestsScheme;
+    }
+
+    public void setLocalRequestsScheme(String localRequestsScheme) {
+        this.localRequestsScheme = localRequestsScheme;
+    }
+
+    public int getLocalRequestsPort() {
+        return localRequestsPort;
+    }
+
+    public void setLocalRequestsPort(int localRequestsPort) {
+        this.localRequestsPort = localRequestsPort;
+    }
 }
diff --git a/core/src/main/java/org/keycloak/util/UriUtils.java b/core/src/main/java/org/keycloak/util/UriUtils.java
index 873283f..5f13756 100644
--- a/core/src/main/java/org/keycloak/util/UriUtils.java
+++ b/core/src/main/java/org/keycloak/util/UriUtils.java
@@ -1,6 +1,8 @@
 package org.keycloak.util;
 
+import java.net.InetAddress;
 import java.net.URI;
+import java.net.UnknownHostException;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -16,4 +18,28 @@ public class UriUtils {
         return u.substring(0, u.indexOf('/', 8));
     }
 
+    /**
+     * Get origin based on current hostname
+     *
+     * @param scheme
+     * @param port
+     * @return Address like "http://myHost:8080"
+     */
+    public static String getLocalOrigin(String scheme, Integer port) {
+        String hostname = getHostName();
+        StringBuilder sb = new StringBuilder(scheme + "://" + hostname);
+        if (port != null && port != -1) {
+            sb.append(":").append(port);
+        }
+        return sb.toString();
+    }
+
+    public static String getHostName() {
+        try {
+            return InetAddress.getLocalHost().getHostName();
+        } catch (UnknownHostException uhe) {
+            throw new IllegalStateException(uhe);
+        }
+    }
+
 }
diff --git a/examples/demo-template/third-party/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java b/examples/demo-template/third-party/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java
index d9377c9..a12bcee 100755
--- a/examples/demo-template/third-party/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java
+++ b/examples/demo-template/third-party/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java
@@ -5,9 +5,11 @@ import org.apache.http.HttpResponse;
 import org.apache.http.client.HttpClient;
 import org.apache.http.client.methods.HttpGet;
 import org.keycloak.adapters.ServerRequest;
+import org.keycloak.enums.RelativeUrlsUsed;
 import org.keycloak.representations.AccessTokenResponse;
 import org.keycloak.servlet.ServletOAuthClient;
 import org.keycloak.util.JsonSerialization;
+import org.keycloak.util.UriUtils;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -58,7 +60,6 @@ public class ProductDatabaseClient {
         // and obtain the ServletOAuthClient.  I actually suggest downloading the ServletOAuthClient code
         // and take a look how it works. You can also take a look at third-party-cdi example
         ServletOAuthClient oAuthClient = (ServletOAuthClient) request.getServletContext().getAttribute(ServletOAuthClient.class.getName());
-        String token = null;
         try {
             return oAuthClient.getBearerToken(request);
         } catch (IOException e) {
@@ -78,7 +79,7 @@ public class ProductDatabaseClient {
         ServletOAuthClient oAuthClient = (ServletOAuthClient) request.getServletContext().getAttribute(ServletOAuthClient.class.getName());
         HttpClient client = oAuthClient.getClient();
 
-        HttpGet get = new HttpGet(getBaseUrl(request) + "/database/products");
+        HttpGet get = new HttpGet(getBaseUrl(oAuthClient, request) + "/database/products");
         get.addHeader("Authorization", "Bearer " + accessToken);
         try {
             HttpResponse response = client.execute(get);
@@ -97,9 +98,19 @@ public class ProductDatabaseClient {
         }
     }
 
-    public static String getBaseUrl(HttpServletRequest request) {
-        String url = request.getRequestURL().toString();
-        return url.substring(0, url.indexOf('/', 8));
+    public static String getBaseUrl(ServletOAuthClient oAuthClient, HttpServletRequest request) {
+        switch (oAuthClient.getRelativeUrlsUsed()) {
+            case ALL_REQUESTS:
+                // Resolve baseURI from the request
+                return UriUtils.getOrigin(request.getRequestURL().toString());
+            case BROWSER_ONLY:
+                // Resolve baseURI from the codeURL (This is already non-relative and based on our hostname)
+                return UriUtils.getOrigin(oAuthClient.getCodeUrl());
+            case NEVER:
+                return "";
+            default:
+                return "";
+        }
     }
 
 }
diff --git a/examples/demo-template/third-party/src/main/webapp/WEB-INF/keycloak.json b/examples/demo-template/third-party/src/main/webapp/WEB-INF/keycloak.json
index 559df05..14bbd79 100755
--- a/examples/demo-template/third-party/src/main/webapp/WEB-INF/keycloak.json
+++ b/examples/demo-template/third-party/src/main/webapp/WEB-INF/keycloak.json
@@ -5,5 +5,6 @@
   "ssl-required" : "external",
    "credentials" : {
        "secret": "password"
-   }
+   },
+   "use-hostname-for-local-requests": false
 }
\ No newline at end of file
diff --git a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
index 34e8035..9413d61 100755
--- a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
+++ b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
@@ -40,7 +40,7 @@ public class ServletOAuthClient extends AbstractOAuthClient {
     }
 
     private AccessTokenResponse resolveBearerToken(HttpServletRequest request, String redirectUri, String code) throws IOException, ServerRequest.HttpFailure {
-        return ServerRequest.invokeAccessCodeToToken(client, publicClient, code, getUrl(request, codeUrl), redirectUri, clientId, credentials);
+        return ServerRequest.invokeAccessCodeToToken(client, publicClient, code, getUrl(request, codeUrl, false), redirectUri, clientId, credentials);
     }
 
     /**
@@ -74,7 +74,7 @@ public class ServletOAuthClient extends AbstractOAuthClient {
     public void redirect(String redirectUri, HttpServletRequest request, HttpServletResponse response) throws IOException {
         String state = getStateCode();
 
-        KeycloakUriBuilder uriBuilder =  KeycloakUriBuilder.fromUri(getUrl(request, authUrl))
+        KeycloakUriBuilder uriBuilder =  KeycloakUriBuilder.fromUri(getUrl(request, authUrl, true))
                 .queryParam(OAuth2Constants.CLIENT_ID, clientId)
                 .queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
                 .queryParam(OAuth2Constants.STATE, state);
@@ -146,7 +146,7 @@ public class ServletOAuthClient extends AbstractOAuthClient {
     }
 
     public AccessTokenResponse refreshToken(HttpServletRequest request, String refreshToken) throws IOException, ServerRequest.HttpFailure {
-        return ServerRequest.invokeRefresh(client, publicClient, refreshToken, getUrl(request, refreshUrl), clientId, credentials);
+        return ServerRequest.invokeRefresh(client, publicClient, refreshToken, getUrl(request, refreshUrl, false), clientId, credentials);
     }
 
     public static IDToken extractIdToken(String idToken) {
@@ -159,8 +159,8 @@ public class ServletOAuthClient extends AbstractOAuthClient {
         }
     }
 
-    private String getUrl(HttpServletRequest request, String url) {
-        if (relativeUrls) {
+    private String getUrl(HttpServletRequest request, String url, boolean isBrowserRequest) {
+        if (relativeUrlsUsed.useRelative(isBrowserRequest)) {
             String baseUrl = request.getRequestURL().toString();
             baseUrl = baseUrl.substring(0, baseUrl.indexOf('/', 8));
             return baseUrl + url;
diff --git a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClientBuilder.java b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClientBuilder.java
index 52d9710..e1ccd5d 100755
--- a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClientBuilder.java
+++ b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClientBuilder.java
@@ -3,9 +3,11 @@ package org.keycloak.servlet;
 import org.apache.http.client.HttpClient;
 import org.keycloak.ServiceUrlConstants;
 import org.keycloak.adapters.HttpClientBuilder;
+import org.keycloak.enums.RelativeUrlsUsed;
 import org.keycloak.representations.adapters.config.AdapterConfig;
 import org.keycloak.util.JsonSerialization;
 import org.keycloak.util.KeycloakUriBuilder;
+import org.keycloak.util.UriUtils;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -21,7 +23,7 @@ public class ServletOAuthClientBuilder {
         return build(adapterConfig);
     }
 
-    private static AdapterConfig getAdapterConfig(InputStream is) {
+    public static AdapterConfig getAdapterConfig(InputStream is) {
         try {
             return JsonSerialization.readValue(is, AdapterConfig.class);
         } catch (IOException e) {
@@ -50,13 +52,31 @@ public class ServletOAuthClientBuilder {
             throw new RuntimeException("You must specify auth-url");
         }
         KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(adapterConfig.getAuthServerUrl());
-        oauthClient.setRelativeUrls(serverBuilder.clone().getHost() == null);
+        RelativeUrlsUsed useRelative = relativeUrls(serverBuilder, adapterConfig);
+        oauthClient.setRelativeUrlsUsed(useRelative);
 
         String authUrl = serverBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_LOGIN_PATH).build(adapterConfig.getRealm()).toString();
-        String tokenUrl = serverBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_ACCESS_CODE_PATH).build(adapterConfig.getRealm()).toString();
-        String refreshUrl = serverBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_REFRESH_PATH).build(adapterConfig.getRealm()).toString();
+
+        KeycloakUriBuilder tokenUrlBuilder = serverBuilder.clone();
+        KeycloakUriBuilder refreshUrlBuilder = serverBuilder.clone();
+
+        if (useRelative == RelativeUrlsUsed.BROWSER_ONLY) {
+            // Use absolute URI for refreshToken and codeToToken requests
+            tokenUrlBuilder.scheme(adapterConfig.getLocalRequestsScheme()).host(UriUtils.getHostName()).port(adapterConfig.getLocalRequestsPort());
+            refreshUrlBuilder.scheme(adapterConfig.getLocalRequestsScheme()).host(UriUtils.getHostName()).port(adapterConfig.getLocalRequestsPort());
+        }
+        String tokenUrl = tokenUrlBuilder.path(ServiceUrlConstants.TOKEN_SERVICE_ACCESS_CODE_PATH).build(adapterConfig.getRealm()).toString();
+        String refreshUrl = refreshUrlBuilder.path(ServiceUrlConstants.TOKEN_SERVICE_REFRESH_PATH).build(adapterConfig.getRealm()).toString();
         oauthClient.setAuthUrl(authUrl);
         oauthClient.setCodeUrl(tokenUrl);
         oauthClient.setRefreshUrl(refreshUrl);
     }
+
+    private static RelativeUrlsUsed relativeUrls(KeycloakUriBuilder serverBuilder, AdapterConfig adapterConfig) {
+        if (serverBuilder.clone().getHost() == null) {
+            return (adapterConfig.isUseHostnameForLocalRequests()) ? RelativeUrlsUsed.BROWSER_ONLY : RelativeUrlsUsed.ALL_REQUESTS;
+        } else {
+            return RelativeUrlsUsed.NEVER;
+        }
+    }
 }