keycloak-uncached

Merge pull request #1555 from mposolda/master KEYCLOAK-1295

8/21/2015 3:41:21 PM

Details

diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
index c6924c9..6806845 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
@@ -74,18 +74,18 @@ module.controller('ClientSignedJWTCtrl', function($scope, $location, realm, clie
     $scope.realm = realm;
     $scope.client = client;
 
-    var signingKeyInfo = ClientCertificate.get({ realm : realm.realm, client : client.id, attribute: 'jwt.credentials' },
+    var signingKeyInfo = ClientCertificate.get({ realm : realm.realm, client : client.id, attribute: 'jwt.credential' },
         function() {
             $scope.signingKeyInfo = signingKeyInfo;
         }
     );
 
     $scope.importCertificate = function() {
-        $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/credentials/client-jwt/Signing/import/jwt.credentials");
+        $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/credentials/client-jwt/Signing/import/jwt.credential");
     };
 
     $scope.generateSigningKey = function() {
-        $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/credentials/client-jwt/Signing/export/jwt.credentials");
+        $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/credentials/client-jwt/Signing/export/jwt.credential");
     };
 
     $scope.cancel = function() {
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/ClientCredentialsProvider.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/ClientCredentialsProvider.java
index b7e7a28..38b0e03 100644
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/ClientCredentialsProvider.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/ClientCredentialsProvider.java
@@ -5,15 +5,57 @@ import java.util.Map;
 import org.keycloak.adapters.KeycloakDeployment;
 
 /**
- * TODO: Javadoc
+ * The simple SPI for authenticating clients/applications . It's used by adapter during all OIDC backchannel requests to Keycloak server
+ * (codeToToken exchange, refresh token or backchannel logout) . You can also use it in your application during direct access grants or service account request
+ * (See the service-account example from Keycloak demo for more info)
+ *
+ * When you implement this SPI on the adapter (application) side, you also need to implement {@link org.keycloak.authentication.ClientAuthenticator} on the server side,
+ * so your server is able to authenticate client
+ *
+ * You must specify a file
+ * META-INF/services/org.keycloak.adapters.authentication.ClientCredentialsProvider in the WAR that this class is contained in (or in the JAR that is attached to the WEB-INF/lib or as jboss module
+ * if you want to share the implementation among more WARs). This file must have the fully qualified class name of all your ClientAuthenticatorFactory classes
+ *
+ * NOTE: The SPI is not finished and method signatures are still subject to change in future versions (for example to support usecase for
+ * authentication with client certificate)
+ *
+ * @see ClientIdAndSecretCredentialsProvider
+ * @see JWTClientCredentialsProvider
  *
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
 public interface ClientCredentialsProvider {
 
+    /**
+     * Return the ID of the provider. Use this ID in the keycloak.json configuration as the subelement of the "credentials" element
+     *
+     * For example if your provider has ID "kerberos-keytab" , use the configuration like this in keycloak.json
+     *
+     * "credentials": {
+     *
+     *     "kerberos-keytab": {
+     *         "keytab": "/tmp/foo"
+     *     }
+     * }
+     *
+     * @return
+     */
     String getId();
 
+    /**
+     * Called by adapter during deployment of your application. You can for example read configuration and init your authenticator here
+     *
+     * @param deployment the adapter configuration
+     * @param config the configuration of your provider read from keycloak.json . For the kerberos-keytab example above, it will return map with the single key "keytab" with value "/tmp/foo"
+     */
     void init(KeycloakDeployment deployment, Object config);
 
+    /**
+     * Called every time adapter needs to perform backchannel request
+     *
+     * @param deployment Fully resolved deployment
+     * @param requestHeaders You should put any HTTP request headers you want to use for authentication of client. These headers will be attached to the HTTP request sent to Keycloak server
+     * @param formParams You should put any request parameters you want to use for authentication of client. These parameters will be attached to the HTTP request sent to Keycloak server
+     */
     void setClientCredentials(KeycloakDeployment deployment, Map<String, String> requestHeaders, Map<String, String> formParams);
 }
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/ClientCredentialsProviderUtils.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/ClientCredentialsProviderUtils.java
index df8b880..be3eb41 100644
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/ClientCredentialsProviderUtils.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/ClientCredentialsProviderUtils.java
@@ -60,6 +60,9 @@ public class ClientCredentialsProviderUtils {
         }
     }
 
+    /**
+     * Use this method when calling backchannel request directly from your application. See service-account example from demo for more details
+     */
     public static void setClientCredentials(KeycloakDeployment deployment, Map<String, String> requestHeaders, Map<String, String> formparams) {
         ClientCredentialsProvider authenticator = deployment.getClientAuthenticator();
         authenticator.setClientCredentials(deployment, requestHeaders, formparams);
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/JWTClientCredentialsProvider.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/JWTClientCredentialsProvider.java
index e249a43..6503e67 100644
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/JWTClientCredentialsProvider.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/authentication/JWTClientCredentialsProvider.java
@@ -12,7 +12,8 @@ import org.keycloak.util.KeystoreUtil;
 import org.keycloak.util.Time;
 
 /**
- * Client authentication based on JWT signed by client private key
+ * Client authentication based on JWT signed by client private key .
+ * See <a href="https://tools.ietf.org/html/draft-jones-oauth-jwt-bearer-03">specs</a> for more details.
  *
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
diff --git a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/KeycloakDeploymentDelegateOAuthClient.java b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/KeycloakDeploymentDelegateOAuthClient.java
index d24b0b4..fc2445b 100644
--- a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/KeycloakDeploymentDelegateOAuthClient.java
+++ b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/KeycloakDeploymentDelegateOAuthClient.java
@@ -4,7 +4,9 @@ import java.util.Map;
 
 import org.keycloak.AbstractOAuthClient;
 import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.constants.ServiceUrlConstants;
 import org.keycloak.enums.RelativeUrlsUsed;
+import org.keycloak.util.KeycloakUriBuilder;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -43,7 +45,7 @@ public class KeycloakDeploymentDelegateOAuthClient extends AbstractOAuthClient {
 
     @Override
     public String getAuthUrl() {
-        return deployment.getAuthUrl().clone().build().toString();
+        throw new IllegalStateException("Illegal to call this method. Use KeycloakDeployment to resolve correct deployment for this request");
     }
 
     @Override
@@ -53,7 +55,7 @@ public class KeycloakDeploymentDelegateOAuthClient extends AbstractOAuthClient {
 
     @Override
     public String getTokenUrl() {
-        return deployment.getTokenUrl();
+        throw new IllegalStateException("Illegal to call this method. Use KeycloakDeployment to resolve correct deployment for this request");
     }
 
     @Override
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 3d61013..72501ac 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
@@ -1,18 +1,26 @@
 package org.keycloak.servlet;
 
+import org.keycloak.KeycloakSecurityContext;
 import org.keycloak.OAuth2Constants;
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.HttpFacade;
+import org.keycloak.adapters.KeycloakDeployment;
 import org.keycloak.adapters.ServerRequest;
+import org.keycloak.enums.RelativeUrlsUsed;
 import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.representations.AccessTokenResponse;
 import org.keycloak.representations.IDToken;
 import org.keycloak.util.KeycloakUriBuilder;
 import org.keycloak.util.UriUtils;
 
+import javax.security.cert.X509Certificate;
 import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.io.InputStream;
 import java.net.URI;
+import java.util.List;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -29,7 +37,8 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
 
     private AccessTokenResponse resolveBearerToken(HttpServletRequest request, String redirectUri, String code) throws IOException, ServerRequest.HttpFailure {
         // Don't send sessionId in oauth clients for now
-        return ServerRequest.invokeAccessCodeToToken(getDeployment(), code, redirectUri, null);
+        KeycloakDeployment resolvedDeployment = resolveDeployment(getDeployment(), request);
+        return ServerRequest.invokeAccessCodeToToken(resolvedDeployment, code, redirectUri, null);
     }
 
     /**
@@ -62,10 +71,12 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
      */
     public void redirect(String redirectUri, HttpServletRequest request, HttpServletResponse response) throws IOException {
         String state = getStateCode();
+        KeycloakDeployment resolvedDeployment = resolveDeployment(getDeployment(), request);
+        String authUrl = resolvedDeployment.getAuthUrl().clone().build().toString();
 
-        KeycloakUriBuilder uriBuilder =  KeycloakUriBuilder.fromUri(getUrl(request, authUrl, true))
+        KeycloakUriBuilder uriBuilder =  KeycloakUriBuilder.fromUri(authUrl)
                 .queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
-                .queryParam(OAuth2Constants.CLIENT_ID, clientId)
+                .queryParam(OAuth2Constants.CLIENT_ID, getClientId())
                 .queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
                 .queryParam(OAuth2Constants.STATE, state);
         if (scope != null) {
@@ -136,7 +147,8 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
     }
 
     public AccessTokenResponse refreshToken(HttpServletRequest request, String refreshToken) throws IOException, ServerRequest.HttpFailure {
-        return ServerRequest.invokeRefresh(getDeployment(), refreshToken);
+        KeycloakDeployment resolvedDeployment = resolveDeployment(getDeployment(), request);
+        return ServerRequest.invokeRefresh(resolvedDeployment, refreshToken);
     }
 
     public static IDToken extractIdToken(String idToken) {
@@ -149,12 +161,90 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
         }
     }
 
-    private String getUrl(HttpServletRequest request, String url, boolean isBrowserRequest) {
-        if (relativeUrlsUsed.useRelative(isBrowserRequest)) {
-            String baseUrl = UriUtils.getOrigin(request.getRequestURL().toString());
-            return baseUrl + url;
-        } else {
-            return url;
+    private KeycloakDeployment resolveDeployment(KeycloakDeployment baseDeployment, HttpServletRequest request) {
+        ServletFacade facade = new ServletFacade(request);
+        return new AdapterDeploymentContext(baseDeployment).resolveDeployment(facade);
+    }
+
+
+    public static class ServletFacade implements HttpFacade {
+
+        private final HttpServletRequest servletRequest;
+
+        private ServletFacade(HttpServletRequest servletRequest) {
+            this.servletRequest = servletRequest;
+        }
+
+        @Override
+        public KeycloakSecurityContext getSecurityContext() {
+            throw new IllegalStateException("Not yet implemented");
+        }
+
+        @Override
+        public Request getRequest() {
+            return new Request() {
+
+                @Override
+                public String getMethod() {
+                    return servletRequest.getMethod();
+                }
+
+                @Override
+                public String getURI() {
+                    return servletRequest.getRequestURL().toString();
+                }
+
+                @Override
+                public boolean isSecure() {
+                    return servletRequest.isSecure();
+                }
+
+                @Override
+                public String getQueryParamValue(String param) {
+                    return servletRequest.getParameter(param);
+                }
+
+                @Override
+                public Cookie getCookie(String cookieName) {
+                    // TODO
+                    return null;
+                }
+
+                @Override
+                public String getHeader(String name) {
+                    return servletRequest.getHeader(name);
+                }
+
+                @Override
+                public List<String> getHeaders(String name) {
+                    // TODO
+                    return null;
+                }
+
+                @Override
+                public InputStream getInputStream() {
+                    try {
+                        return servletRequest.getInputStream();
+                    } catch (IOException ioe) {
+                        throw new RuntimeException(ioe);
+                    }
+                }
+
+                @Override
+                public String getRemoteAddr() {
+                    return servletRequest.getRemoteAddr();
+                }
+            };
+        }
+
+        @Override
+        public Response getResponse() {
+            throw new IllegalStateException("Not yet implemented");
+        }
+
+        @Override
+        public X509Certificate[] getCertificateChain() {
+            throw new IllegalStateException("Not yet implemented");
         }
     }
 }
diff --git a/integration/servlet-oauth-client/src/test/java/org/keycloak/servlet/ServletOAuthClientBuilderTest.java b/integration/servlet-oauth-client/src/test/java/org/keycloak/servlet/ServletOAuthClientBuilderTest.java
index 604b58c..d8f78e2 100644
--- a/integration/servlet-oauth-client/src/test/java/org/keycloak/servlet/ServletOAuthClientBuilderTest.java
+++ b/integration/servlet-oauth-client/src/test/java/org/keycloak/servlet/ServletOAuthClientBuilderTest.java
@@ -15,8 +15,8 @@ public class ServletOAuthClientBuilderTest {
     @Test
     public void testBuilder() {
         ServletOAuthClient oauthClient = ServletOAuthClientBuilder.build(getClass().getResourceAsStream("/keycloak.json"));
-        Assert.assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/auth", oauthClient.getAuthUrl());
-        Assert.assertEquals("https://backend:8443/auth/realms/demo/protocol/openid-connect/token", oauthClient.getTokenUrl());
+        Assert.assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/auth", oauthClient.getDeployment().getAuthUrl().clone().build().toString());
+        Assert.assertEquals("https://backend:8443/auth/realms/demo/protocol/openid-connect/token", oauthClient.getDeployment().getTokenUrl());
         assertEquals(RelativeUrlsUsed.NEVER, oauthClient.getRelativeUrlsUsed());
         Assert.assertEquals("customer-portal", oauthClient.getClientId());
         Assert.assertEquals("234234-234234-234234", oauthClient.getCredentials().get(CredentialRepresentation.SECRET));
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/client/ClientIdAndSecretAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/client/ClientIdAndSecretAuthenticator.java
index 8bbf190..382c55e 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/client/ClientIdAndSecretAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/client/ClientIdAndSecretAuthenticator.java
@@ -22,7 +22,9 @@ import org.keycloak.provider.ProviderConfigProperty;
 import org.keycloak.util.BasicAuthHelper;
 
 /**
- * Validates client based on "client_id" and "client_secret" sent either in request parameters or in "Authorization: Basic" header
+ * Validates client based on "client_id" and "client_secret" sent either in request parameters or in "Authorization: Basic" header .
+ *
+ * See org.keycloak.adapters.authentication.ClientIdAndSecretAuthenticator for the adapter
  *
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java
index 3f7ddb6..db0cb9f 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java
@@ -24,6 +24,12 @@ import org.keycloak.representations.JsonWebToken;
 import org.keycloak.services.Urls;
 
 /**
+ * Client authentication based on JWT signed by client private key .
+ * See <a href="https://tools.ietf.org/html/draft-jones-oauth-jwt-bearer-03">specs</a> for more details.
+ *
+ * This is server side, which verifies JWT from client_assertion parameter, where the assertion was created on adapter side by
+ * org.keycloak.adapters.authentication.JWTClientCredentialsProvider
+ *
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
 public class JWTClientAuthenticator extends AbstractClientAuthenticator {
diff --git a/services/src/main/java/org/keycloak/authentication/ClientAuthenticationFlowContext.java b/services/src/main/java/org/keycloak/authentication/ClientAuthenticationFlowContext.java
index 9f11eb9..3dba809 100644
--- a/services/src/main/java/org/keycloak/authentication/ClientAuthenticationFlowContext.java
+++ b/services/src/main/java/org/keycloak/authentication/ClientAuthenticationFlowContext.java
@@ -5,6 +5,7 @@ import java.util.Map;
 import org.keycloak.models.ClientModel;
 
 /**
+ * Encapsulates information about the execution in ClientAuthenticationFlow
  *
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
diff --git a/services/src/main/java/org/keycloak/authentication/ClientAuthenticator.java b/services/src/main/java/org/keycloak/authentication/ClientAuthenticator.java
index 4cbf2ef..47c0d5d 100644
--- a/services/src/main/java/org/keycloak/authentication/ClientAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/ClientAuthenticator.java
@@ -6,12 +6,23 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.provider.Provider;
 
 /**
+ * This interface is for users that want to add custom client authenticators to an authentication flow.
+ * You must implement this interface as well as a ClientAuthenticatorFactory.
+ *
+ * This interface is for verifying client credentials from request. On the adapter side, you must also implement org.keycloak.adapters.authentication.ClientCredentialsProvider , which is supposed
+ * to add the client credentials to the request, which will ClientAuthenticator verify on server side
+ *
+ * @see org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator
+ * @see org.keycloak.authentication.authenticators.client.JWTClientAuthenticator
+ *
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
 public interface ClientAuthenticator extends Provider {
 
     /**
-     * TODO: javadoc
+     * Initial call for the authenticator.  This method should check the current HTTP request to determine if the request
+     * satisfies the ClientAuthenticator's requirements.  If it doesn't, it should send back a challenge response by calling
+     * the ClientAuthenticationFlowContext.challenge(Response).
      *
      * @param context
      */
diff --git a/services/src/main/java/org/keycloak/authentication/ClientAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/ClientAuthenticatorFactory.java
index 9f9cc86..18ddaa5 100644
--- a/services/src/main/java/org/keycloak/authentication/ClientAuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/ClientAuthenticatorFactory.java
@@ -3,7 +3,11 @@ package org.keycloak.authentication;
 import org.keycloak.provider.ProviderFactory;
 
 /**
- * TODO
+ * Factory for creating ClientAuthenticator instances.  This is a singleton and created when Keycloak boots.
+ *
+ * You must specify a file
+ * META-INF/services/org.keycloak.authentication.ClientAuthenticatorFactory in the jar that this class is contained in
+ * This file must have the fully qualified class name of all your ClientAuthenticatorFactory classes
  *
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
@@ -19,7 +23,7 @@ public interface ClientAuthenticatorFactory extends ProviderFactory<ClientAuthen
     boolean isConfigurable();
 
     /**
-     * Is this authenticator configurable per client?
+     * Is this authenticator configurable per client? The configuration will be in "Clients" / "Credentials" tab in admin console
      *
      * @return
      */