keycloak-uncached

Changes

model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/KeycloakTcpTransportFactory.java 120(+0 -120)

Details

diff --git a/core/src/main/java/org/keycloak/representations/AccessTokenResponse.java b/core/src/main/java/org/keycloak/representations/AccessTokenResponse.java
index b211aef..6539cd9 100755
--- a/core/src/main/java/org/keycloak/representations/AccessTokenResponse.java
+++ b/core/src/main/java/org/keycloak/representations/AccessTokenResponse.java
@@ -57,7 +57,17 @@ public class AccessTokenResponse {
 
     protected Map<String, Object> otherClaims = new HashMap<String, Object>();
 
+    // OIDC Financial API Read Only Profile : scope MUST be returned in the response from Token Endpoint
+    @JsonProperty("scope")
+    protected String scope;
 
+    public String getScope() {
+        return scope;
+    }
+
+    public void setScope(String scope) {
+        this.scope = scope;
+    }
 
     public String getToken() {
         return token;
diff --git a/misc/CrossDataCenter.md b/misc/CrossDataCenter.md
index 7d2d841..21dd586 100644
--- a/misc/CrossDataCenter.md
+++ b/misc/CrossDataCenter.md
@@ -123,8 +123,6 @@ Keycloak servers setup
     <store class="org.keycloak.models.sessions.infinispan.remotestore.KeycloakRemoteStoreConfigurationBuilder" passivation="false" fetch-state="false" purge="false" preload="false" shared="true">   
         <property name="rawValues">true</property>	
         <property name="marshaller">org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory</property>
-        <property name="transportFactory">org.keycloak.models.sessions.infinispan.remotestore.KeycloakTcpTransportFactory</property>
-        <property name="remoteServers">localhost:${remote.cache.port}</property>
         <property name="remoteCacheName">work</property> 
         <property name="sessionCache">false</property>
     </store>
diff --git a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java
index 24ef873..6c84385 100755
--- a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java
@@ -42,7 +42,6 @@ import org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.sessions.infinispan.remotestore.KeycloakRemoteStoreConfigurationBuilder;
-import org.keycloak.models.sessions.infinispan.remotestore.KeycloakTcpTransportFactory;
 
 import javax.naming.InitialContext;
 
@@ -356,7 +355,6 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
         builder.persistence()
                 .passivation(false)
                 .addStore(KeycloakRemoteStoreConfigurationBuilder.class)
-                    .remoteServers(jdgServer + ":" + jdgPort)
                     .sessionCache(sessionCache)
                     .fetchPersistentState(false)
                     .ignoreModifications(false)
@@ -367,10 +365,9 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
                     .rawValues(true)
                     .forceReturnValues(false)
                     .marshaller(KeycloakHotRodMarshallerFactory.class.getName())
-                    .transportFactory(KeycloakTcpTransportFactory.class.getName())
-//                    .addServer()
-//                        .host(jdgServer)
-//                        .port(jdgPort)
+                    .addServer()
+                        .host(jdgServer)
+                        .port(jdgPort)
 //                  .connectionPool()
 //                      .maxActive(100)
 //                      .exhaustedAction(ExhaustedAction.CREATE_NEW)
@@ -386,7 +383,6 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
         builder.persistence()
                 .passivation(false)
                 .addStore(KeycloakRemoteStoreConfigurationBuilder.class)
-                    .remoteServers(jdgServer + ":" + jdgPort)
                     .sessionCache(false)
                     .fetchPersistentState(false)
                     .ignoreModifications(false)
@@ -397,10 +393,9 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
                     .rawValues(true)
                     .forceReturnValues(false)
                     .marshaller(KeycloakHotRodMarshallerFactory.class.getName())
-                    .transportFactory(KeycloakTcpTransportFactory.class.getName())
-//                    .addServer()
-//                        .host(jdgServer)
-//                        .port(jdgPort)
+                    .addServer()
+                        .host(jdgServer)
+                        .port(jdgPort)
                     .async()
                         .enabled(async);
 
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/KeycloakRemoteStore.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/KeycloakRemoteStore.java
index 88df049..a6f526d 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/KeycloakRemoteStore.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/KeycloakRemoteStore.java
@@ -17,7 +17,6 @@
 
 package org.keycloak.models.sessions.infinispan.remotestore;
 
-import java.util.Optional;
 import java.util.concurrent.Executor;
 
 import org.infinispan.commons.CacheException;
@@ -62,17 +61,10 @@ public class KeycloakRemoteStore extends RemoteStore {
             EmbeddedCacheManager cacheManager = ctx.getCache().getCacheManager();
             cacheManager.getCache(cacheTemplateName, true);
 
-            Optional<StoreConfiguration> optional = cacheManager.getCacheConfiguration(cacheTemplateName).persistence().stores().stream().filter((StoreConfiguration storeConfig) -> {
-
-                return storeConfig instanceof KeycloakRemoteStoreConfiguration;
-
-            }).findFirst();
-
-            if (!optional.isPresent()) {
-                throw new CacheException("Unable to find remoteStore on cache '" + cacheTemplateName + ".");
-            }
-
-            KeycloakRemoteStoreConfiguration templateConfig = (KeycloakRemoteStoreConfiguration) optional.get();
+            KeycloakRemoteStoreConfiguration templateConfig = (KeycloakRemoteStoreConfiguration) cacheManager.getCacheConfiguration(cacheTemplateName).persistence().stores().stream()
+              .filter((StoreConfiguration storeConfig) -> storeConfig instanceof KeycloakRemoteStoreConfiguration)
+              .findFirst()
+              .orElseThrow(() -> new CacheException("Unable to find remoteStore on cache '" + cacheTemplateName + "."));
 
             // We have template configuration, so create new configuration from it. Override just remoteCacheName and sessionsCache (not pretty, but works for now)
             PersistenceConfigurationBuilder readPersistenceBuilder = new ConfigurationBuilder().read(ctx.getCache().getCacheConfiguration()).persistence();
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/KeycloakRemoteStoreConfiguration.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/KeycloakRemoteStoreConfiguration.java
index 3f7d258..fda2c76 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/KeycloakRemoteStoreConfiguration.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/KeycloakRemoteStoreConfiguration.java
@@ -31,18 +31,15 @@ import org.infinispan.persistence.remote.configuration.RemoteStoreConfiguration;
 public class KeycloakRemoteStoreConfiguration extends RemoteStoreConfiguration {
 
     static final AttributeDefinition<String> USE_CONFIG_TEMPLATE_FROM_CACHE = AttributeDefinition.builder("useConfigTemplateFromCache", null, String.class).immutable().build();
-    static final AttributeDefinition<String> REMOTE_SERVERS = AttributeDefinition.builder("remoteServers", null, String.class).immutable().build();
     static final AttributeDefinition<Boolean> SESSION_CACHE = AttributeDefinition.builder("sessionCache", null, Boolean.class).immutable().build();
 
     private final Attribute<String> useConfigTemplateFromCache;
-    private final Attribute<String> remoteServers;
     private final Attribute<Boolean> sessionCache;
 
 
     public KeycloakRemoteStoreConfiguration(RemoteStoreConfiguration other) {
         super(other.attributes(), other.async(), other.singletonStore(), other.asyncExecutorFactory(), other.connectionPool());
         useConfigTemplateFromCache = attributes.attribute(USE_CONFIG_TEMPLATE_FROM_CACHE.name());
-        remoteServers = attributes.attribute(REMOTE_SERVERS.name());
         sessionCache = attributes.attribute(SESSION_CACHE.name());
     }
 
@@ -52,11 +49,6 @@ public class KeycloakRemoteStoreConfiguration extends RemoteStoreConfiguration {
     }
 
 
-    public String remoteServers() {
-        return remoteServers.get();
-    }
-
-
     public Boolean sessionCache() {
         return sessionCache.get()==null ? false : sessionCache.get();
     }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/KeycloakRemoteStoreConfigurationBuilder.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/KeycloakRemoteStoreConfigurationBuilder.java
index a6ea1e6..3734114 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/KeycloakRemoteStoreConfigurationBuilder.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/KeycloakRemoteStoreConfigurationBuilder.java
@@ -18,9 +18,7 @@
 package org.keycloak.models.sessions.infinispan.remotestore;
 
 import java.lang.reflect.Field;
-import java.util.List;
 import java.util.Map;
-import java.util.StringTokenizer;
 
 import org.infinispan.commons.CacheConfigurationException;
 import org.infinispan.commons.configuration.attributes.Attribute;
@@ -49,10 +47,6 @@ public class KeycloakRemoteStoreConfigurationBuilder extends RemoteStoreConfigur
             Attribute<String> attribute = def.toAttribute();
             attributesInternal.put(def.name(), attribute);
 
-            def = KeycloakRemoteStoreConfiguration.REMOTE_SERVERS;
-            attribute = def.toAttribute();
-            attributesInternal.put(def.name(), attribute);
-
             AttributeDefinition<Boolean> defBool = KeycloakRemoteStoreConfiguration.SESSION_CACHE;
             Attribute<Boolean> attributeBool = defBool.toAttribute();
             attributesInternal.put(defBool.name(), attributeBool);
@@ -65,12 +59,6 @@ public class KeycloakRemoteStoreConfigurationBuilder extends RemoteStoreConfigur
 
     @Override
     public KeycloakRemoteStoreConfiguration create() {
-        String remoteServersAttr = attributes.attribute(KeycloakRemoteStoreConfiguration.REMOTE_SERVERS).get();
-        boolean isServersAlreadySet = isServersAlreadySet();
-        if (remoteServersAttr != null && !isServersAlreadySet) {
-            parseRemoteServersAttr(remoteServersAttr);
-        }
-
         RemoteStoreConfiguration cfg = super.create();
         KeycloakRemoteStoreConfiguration cfg2 = new KeycloakRemoteStoreConfiguration(cfg);
         return cfg2;
@@ -83,40 +71,8 @@ public class KeycloakRemoteStoreConfigurationBuilder extends RemoteStoreConfigur
     }
 
 
-    public KeycloakRemoteStoreConfigurationBuilder remoteServers(String remoteServers) {
-        attributes.attribute(KeycloakRemoteStoreConfiguration.REMOTE_SERVERS).set(remoteServers);
-        return this;
-    }
-
-
     public KeycloakRemoteStoreConfigurationBuilder sessionCache(Boolean sessionCache) {
         attributes.attribute(KeycloakRemoteStoreConfiguration.SESSION_CACHE).set(sessionCache);
         return this;
     }
-
-
-    private void parseRemoteServersAttr(String remoteServers) {
-        StringTokenizer st = new StringTokenizer(remoteServers, ",");
-
-        while (st.hasMoreElements()) {
-            String nodeStr = st.nextToken();
-            String[] node = nodeStr.trim().split(":", 2);
-
-            addServer()
-                    .host(node[0].trim())
-                    .port(Integer.parseInt(node[1].trim()));
-        }
-    }
-
-
-    private boolean isServersAlreadySet() {
-        try {
-            Field f = Reflections.findDeclaredField(RemoteStoreConfigurationBuilder.class, "servers");
-            f.setAccessible(true);
-            List originalRemoteServers = (List) f.get(this);
-            return !originalRemoteServers.isEmpty();
-        } catch (IllegalAccessException iae) {
-            throw new RuntimeException(iae);
-        }
-    }
 }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
index 5fa46a0..7a1f005 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
@@ -433,6 +433,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
         if (request.getPrompt() != null) authenticationSession.setClientNote(OIDCLoginProtocol.PROMPT_PARAM, request.getPrompt());
         if (request.getIdpHint() != null) authenticationSession.setClientNote(AdapterConstants.KC_IDP_HINT, request.getIdpHint());
         if (request.getResponseMode() != null) authenticationSession.setClientNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM, request.getResponseMode());
+        if (request.getClaims()!= null) authenticationSession.setClientNote(OIDCLoginProtocol.CLAIMS_PARAM, request.getClaims());
 
         // https://tools.ietf.org/html/rfc7636#section-4
         if (request.getCodeChallenge() != null) authenticationSession.setClientNote(OIDCLoginProtocol.CODE_CHALLENGE_PARAM, request.getCodeChallenge());
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequest.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequest.java
index a0f874b..29b734f 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequest.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequest.java
@@ -36,6 +36,7 @@ public class AuthorizationEndpointRequest {
     String nonce;
     Integer maxAge;
     String idpHint;
+    String claims;
     Map<String, String> additionalReqParams = new HashMap<>();
 
     // https://tools.ietf.org/html/rfc7636#section-6.1
@@ -86,6 +87,10 @@ public class AuthorizationEndpointRequest {
         return idpHint;
     }
 
+    public String getClaims() {
+        return claims;
+    }
+
     public Map<String, String> getAdditionalReqParams() {
         return additionalReqParams;
     }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestObjectParser.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestObjectParser.java
index 06de42c..6803680 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestObjectParser.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestObjectParser.java
@@ -14,12 +14,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.keycloak.protocol.oidc.endpoints.request;
 
+import com.fasterxml.jackson.databind.JsonNode;
 import java.security.PublicKey;
 import java.util.HashMap;
-import java.util.Map;
+import java.util.HashSet;
 import java.util.Set;
 
 import org.keycloak.jose.jws.Algorithm;
@@ -39,7 +39,7 @@ import org.keycloak.util.JsonSerialization;
  */
 class AuthzEndpointRequestObjectParser extends AuthzEndpointRequestParser {
 
-    private final Map<String, Object> requestParams;
+    private final JsonNode requestParams;
 
     public AuthzEndpointRequestObjectParser(KeycloakSession session, String requestObject, ClientModel client) throws Exception {
         JWSInput input = new JWSInput(requestObject);
@@ -52,7 +52,7 @@ class AuthzEndpointRequestObjectParser extends AuthzEndpointRequestParser {
         }
 
         if (header.getAlgorithm() == Algorithm.none) {
-            this.requestParams = JsonSerialization.readValue(input.getContent(), TypedHashMap.class);
+            this.requestParams = JsonSerialization.readValue(input.getContent(), JsonNode.class);
         } else if (header.getAlgorithm() == Algorithm.RS256) {
             PublicKey clientPublicKey = PublicKeyStorageManager.getClientPublicKey(session, client, input);
             if (clientPublicKey == null) {
@@ -64,7 +64,7 @@ class AuthzEndpointRequestObjectParser extends AuthzEndpointRequestParser {
                 throw new RuntimeException("Failed to verify signature on 'request' object");
             }
 
-            this.requestParams = JsonSerialization.readValue(input.getContent(), TypedHashMap.class);
+            this.requestParams = JsonSerialization.readValue(input.getContent(), JsonNode.class);
         } else {
             throw new RuntimeException("Unsupported JWA algorithm used for signed request");
         }
@@ -72,8 +72,14 @@ class AuthzEndpointRequestObjectParser extends AuthzEndpointRequestParser {
 
     @Override
     protected String getParameter(String paramName) {
-        Object val = this.requestParams.get(paramName);
-        return val==null ? null : val.toString();
+        JsonNode val = this.requestParams.get(paramName);
+        if (val == null) {
+            return null;
+        } else if (val.isValueNode()) {
+            return val.asText();
+        } else {
+            return val.toString();
+        }
     }
 
     @Override
@@ -84,7 +90,9 @@ class AuthzEndpointRequestObjectParser extends AuthzEndpointRequestParser {
 
     @Override
     protected Set<String> keySet() {
-        return requestParams.keySet();
+        HashSet<String> keys = new HashSet<>();
+        requestParams.fieldNames().forEachRemaining(keys::add);
+        return keys;
     }
 
     static class TypedHashMap extends HashMap<String, Object> {
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestParser.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestParser.java
index 346b1a6..0f9d152 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestParser.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestParser.java
@@ -61,6 +61,7 @@ abstract class AuthzEndpointRequestParser {
         KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.UI_LOCALES_PARAM);
         KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REQUEST_PARAM);
         KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.REQUEST_URI_PARAM);
+        KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.CLAIMS_PARAM);
 
         // https://tools.ietf.org/html/rfc7636#section-6.1
         KNOWN_REQ_PARAMS.add(OIDCLoginProtocol.CODE_CHALLENGE_PARAM);
@@ -87,6 +88,7 @@ abstract class AuthzEndpointRequestParser {
         request.idpHint = replaceIfNotNull(request.idpHint, getParameter(AdapterConstants.KC_IDP_HINT));
         request.nonce = replaceIfNotNull(request.nonce, getParameter(OIDCLoginProtocol.NONCE_PARAM));
         request.maxAge = replaceIfNotNull(request.maxAge, getIntParameter(OIDCLoginProtocol.MAX_AGE_PARAM));
+        request.claims = replaceIfNotNull(request.claims, getParameter(OIDCLoginProtocol.CLAIMS_PARAM));
 
         // https://tools.ietf.org/html/rfc7636#section-6.1
         request.codeChallenge = replaceIfNotNull(request.codeChallenge, getParameter(OIDCLoginProtocol.CODE_CHALLENGE_PARAM));
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index d4bb40f..39a4215 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -70,6 +70,7 @@ import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
 
 import java.security.PublicKey;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -854,8 +855,57 @@ public class TokenManager {
             if (userNotBefore > notBefore) notBefore = userNotBefore;
             res.setNotBeforePolicy(notBefore);
 
+            // OIDC Financial API Read Only Profile : scope MUST be returned in the response from Token Endpoint
+            String requestedScope = clientSession.getNote(OAuth2Constants.SCOPE);
+            if (accessToken != null && requestedScope != null) {
+                List<String> returnedScopes = new ArrayList<String>();
+                // at attachAuthenticationSession(), take over notes from AuthenticationSessionModel to AuthenticatedClientSessionModel
+                List<String> requestedScopes = Arrays.asList(requestedScope.split(" "));
+
+                // distinguish between so called role scope and oauth scope
+                // only pick up oauth scope following https://tools.ietf.org/html/rfc6749#section-5.1
+
+                // for realm role - scope
+                if (accessToken.getRealmAccess() != null && accessToken.getRealmAccess().getRoles() != null) {
+                    addRolesAsScopes(returnedScopes, requestedScopes, accessToken.getRealmAccess().getRoles());
+                }
+                // for client role - scope
+                if (accessToken.getResourceAccess() != null) {
+                    for (String clientId : accessToken.getResourceAccess().keySet()) {
+                        if (accessToken.getResourceAccess(clientId).getRoles() != null) {
+                            addRolesAsScopes(returnedScopes, requestedScopes, accessToken.getResourceAccess(clientId).getRoles(), clientId);
+                        }
+                    }
+                }
+                StringBuilder builder = new StringBuilder();
+                for (String s : returnedScopes) {
+                    builder.append(s).append(" ");
+                }
+                res.setScope(builder.toString().trim());
+            }
+
             return res;
         }
+
+        private void addRolesAsScopes(List<String> returnedScopes, List<String> requestedScopes, Set<String> roles) {
+            for (String r : roles) {
+                for (String s : requestedScopes) {
+                    if (s.equals(r)) {
+                        returnedScopes.add(s);
+                    }
+                }
+            }
+        }
+
+        private void addRolesAsScopes(List<String> returnedScopes, List<String> requestedScopes, Set<String> roles, String clientId) {
+            for (String r : roles) {
+                for (String s : requestedScopes) {
+                    if (s.equals(clientId + "/" + r)) {
+                        returnedScopes.add(s);
+                    }
+                }
+            }       	
+        }
     }
 
     public class RefreshResult {
diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/crossdc/cross-dc-setup.cli b/testsuite/integration-arquillian/servers/auth-server/jboss/common/crossdc/cross-dc-setup.cli
index 836cde1..3a0a7b0 100644
--- a/testsuite/integration-arquillian/servers/auth-server/jboss/common/crossdc/cross-dc-setup.cli
+++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/crossdc/cross-dc-setup.cli
@@ -22,8 +22,6 @@ echo ** Update replicated-cache work element **
     name=properties, value={ \
         rawValues=true, \
         marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory, \
-        transportFactory=org.keycloak.models.sessions.infinispan.remotestore.KeycloakTcpTransportFactory, \
-        remoteServers=localhost:${remote.cache.port}, \
         remoteCacheName=work, \
         sessionCache=false \
     } \
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
index 7a3a5b3..53d3f48 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
@@ -949,6 +949,8 @@ public class OAuthClient {
         private int expiresIn;
         private int refreshExpiresIn;
         private String refreshToken;
+        // OIDC Financial API Read Only Profile : scope MUST be returned in the response from Token Endpoint
+        private String scope;
 
         private String error;
         private String errorDescription;
@@ -970,6 +972,11 @@ public class OAuthClient {
                     expiresIn = (Integer) responseJson.get("expires_in");
                     refreshExpiresIn = (Integer) responseJson.get("refresh_expires_in");
 
+                    // OIDC Financial API Read Only Profile : scope MUST be returned in the response from Token Endpoint
+                    if (responseJson.containsKey(OAuth2Constants.SCOPE)) {
+                        scope = (String) responseJson.get(OAuth2Constants.SCOPE);
+                    }
+
                     if (responseJson.containsKey(OAuth2Constants.REFRESH_TOKEN)) {
                         refreshToken = (String) responseJson.get(OAuth2Constants.REFRESH_TOKEN);
                     }
@@ -1017,6 +1024,11 @@ public class OAuthClient {
         public String getTokenType() {
             return tokenType;
         }
+
+        // OIDC Financial API Read Only Profile : scope MUST be returned in the response from Token Endpoint
+        public String getScope() {
+            return scope;
+        }
     }
 
     public PublicKey getRealmPublicKey(String realm) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthScopeInTokenResponseTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthScopeInTokenResponseTest.java
new file mode 100644
index 0000000..e6c8253
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthScopeInTokenResponseTest.java
@@ -0,0 +1,208 @@
+package org.keycloak.testsuite.oauth;
+
+import static org.junit.Assert.assertEquals;
+import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.util.OAuthClient;
+
+//OIDC Financial API Read Only Profile : scope MUST be returned in the response from Token Endpoint
+public class OAuthScopeInTokenResponseTest extends AbstractKeycloakTest {
+
+    @Override
+    public void beforeAbstractKeycloakTest() throws Exception {
+        super.beforeAbstractKeycloakTest();
+    }
+
+    @Override
+    public void addTestRealms(List<RealmRepresentation> testRealms) {
+        RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class);
+        testRealms.add(realm);
+    }
+
+    @Test
+    public void specifyNoScopeTest() throws Exception {
+        String loginUser = "john-doh@localhost";
+        String loginPassword = "password";
+        String clientSecret = "password";
+        
+    	String expectedScope = "";
+    	
+        oauth.doLogin(loginUser, loginPassword);
+
+        String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+        
+        expectSuccessfulResponseFromTokenEndpoint(code, expectedScope, clientSecret);
+    }
+    
+    @Test
+    public void specifySingleScopeAsRealmRoleTest() throws Exception {
+        String loginUser = "john-doh@localhost";
+        String loginPassword = "password";
+        String clientSecret = "password";
+        
+    	String requestedScope = "user";
+    	String expectedScope = requestedScope;
+    	
+    	oauth.scope(requestedScope);
+        oauth.doLogin(loginUser, loginPassword);
+
+        String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+        
+        expectSuccessfulResponseFromTokenEndpoint(code, expectedScope, clientSecret);
+    }
+    
+    @Test
+    public void specifyMultipleScopeAsRealmRoleTest() throws Exception {
+        String loginUser = "rich.roles@redhat.com";
+        String loginPassword = "password";
+        String clientSecret = "password";
+        
+    	String requestedScope = "user realm-composite-role";
+    	String expectedScope = requestedScope;
+    	
+    	oauth.scope(requestedScope);
+        oauth.doLogin(loginUser, loginPassword);
+
+        String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+        
+        expectSuccessfulResponseFromTokenEndpoint(code, expectedScope, clientSecret);
+    }
+
+    @Test
+    public void specifyNotAssignedScopeAsRealmRoleTest() throws Exception {
+        String loginUser = "john-doh@localhost";
+        String loginPassword = "password";
+        String clientSecret = "password";
+        
+    	String requestedScope = "user realm-composite-role";
+    	String expectedScope = "user";
+    	
+    	oauth.scope(requestedScope);
+        oauth.doLogin(loginUser, loginPassword);
+
+        String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+        
+        expectSuccessfulResponseFromTokenEndpoint(code, expectedScope, clientSecret);
+    }
+
+    @Test
+    public void specifySingleScopeAsClientRoleTest() throws Exception {
+        String loginUser = "john-doh@localhost";
+        String loginPassword = "password";
+        String clientSecret = "password";
+        
+    	String requestedScope = "test-app/customer-user";
+    	String expectedScope = requestedScope;
+    	
+    	oauth.scope(requestedScope);
+        oauth.doLogin(loginUser, loginPassword);
+
+        String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+        
+        expectSuccessfulResponseFromTokenEndpoint(code, expectedScope, clientSecret);
+    }
+
+    @Test
+    public void specifyMultipleScopeAsClientRoleTest() throws Exception {
+        String loginUser = "rich.roles@redhat.com";
+        String loginPassword = "password";
+        String clientSecret = "password";
+        
+    	String requestedScope = "test-app-scope/test-app-disallowed-by-scope test-app-scope/test-app-allowed-by-scope";
+    	String expectedScope = requestedScope;
+    	
+    	oauth.scope(requestedScope);
+        oauth.doLogin(loginUser, loginPassword);
+
+        String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+        
+        expectSuccessfulResponseFromTokenEndpoint(code, expectedScope, clientSecret);
+    }
+
+    @Test
+    public void specifyNotAssignedScopeAsClientRoleTest() throws Exception {
+        String loginUser = "rich.roles@redhat.com";
+        String loginPassword = "password";
+        String clientSecret = "password";
+        
+    	String requestedScope = "test-app-scope/test-app-unspecified-by-scope test-app-scope/test-app-allowed-by-scope";
+    	String expectedScope = "test-app-scope/test-app-allowed-by-scope";
+    	
+    	oauth.scope(requestedScope);
+        oauth.doLogin(loginUser, loginPassword);
+
+        String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+        
+        expectSuccessfulResponseFromTokenEndpoint(code, expectedScope, clientSecret);
+    }
+
+    @Test
+    public void specifyMultipleScopeAsRealmAndClientRoleTest() throws Exception {
+        String loginUser = "rich.roles@redhat.com";
+        String loginPassword = "password";
+        String clientSecret = "password";
+        
+    	String requestedScope = "test-app-scope/test-app-disallowed-by-scope admin test-app/customer-user test-app-scope/test-app-allowed-by-scope";
+    	String expectedScope = requestedScope;
+    	
+    	oauth.scope(requestedScope);
+        oauth.doLogin(loginUser, loginPassword);
+
+        String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+        
+        expectSuccessfulResponseFromTokenEndpoint(code, expectedScope, clientSecret);
+    }
+
+    @Test
+    public void specifyNotAssignedScopeAsRealmAndClientRoleTest() throws Exception {
+        String loginUser = "john-doh@localhost";
+        String loginPassword = "password";
+        String clientSecret = "password";
+        
+    	String requestedScope = "test-app/customer-user test-app-scope/test-app-disallowed-by-scope admin test-app/customer-user user test-app-scope/test-app-allowed-by-scope";
+    	String expectedScope = "user test-app/customer-user";
+    	
+    	oauth.scope(requestedScope);
+        oauth.doLogin(loginUser, loginPassword);
+
+        String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+        
+        expectSuccessfulResponseFromTokenEndpoint(code, expectedScope, clientSecret);
+    }
+ 
+    @Test
+    public void specifyDuplicatedScopeAsRealmAndClientRoleTest() throws Exception {
+        String loginUser = "john-doh@localhost";
+        String loginPassword = "password";
+        String clientSecret = "password";
+        
+    	String requestedScope = "test-app/customer-user user user test-app/customer-user";
+    	String expectedScope = "user test-app/customer-user";
+    	
+    	oauth.scope(requestedScope);
+        oauth.doLogin(loginUser, loginPassword);
+
+        String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+        
+        expectSuccessfulResponseFromTokenEndpoint(code, expectedScope, clientSecret);
+    }
+    
+    private void expectSuccessfulResponseFromTokenEndpoint(String code, String expectedScope, String clientSecret) throws Exception {
+    	OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, clientSecret);
+        assertEquals(200, response.getStatusCode());
+        log.info("expectedScopes = " + expectedScope);
+        log.info("receivedScopes = " + response.getScope());
+    	Collection<String> expectedScopes = Arrays.asList(expectedScope.split(" "));
+    	Collection<String> receivedScopes = Arrays.asList(response.getScope().split(" "));
+    	Assert.assertTrue(expectedScopes.containsAll(receivedScopes) && receivedScopes.containsAll(expectedScopes));
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java
index f558ede..5bbf71e 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java
@@ -17,17 +17,24 @@
 
 package org.keycloak.testsuite.oidc;
 
+import org.jboss.arquillian.container.test.api.Deployment;
 import org.jboss.arquillian.graphene.page.Page;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.keycloak.OAuth2Constants;
 import org.keycloak.OAuthErrorException;
 import org.keycloak.admin.client.resource.ClientResource;
 import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator;
 import org.keycloak.common.util.Time;
 import org.keycloak.events.Details;
 import org.keycloak.jose.jws.Algorithm;
+import org.keycloak.jose.jws.JWSBuilder;
+import org.keycloak.models.AuthenticatedClientSessionModel;
 import org.keycloak.models.Constants;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserSessionModel;
 import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
 import org.keycloak.representations.IDToken;
@@ -49,10 +56,16 @@ import org.keycloak.testsuite.pages.ErrorPage;
 import org.keycloak.testsuite.pages.LoginPage;
 import org.keycloak.testsuite.pages.OAuthGrantPage;
 import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResource;
+import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
 import org.keycloak.testsuite.util.ClientManager;
 import org.keycloak.testsuite.util.OAuthClient;
+import org.keycloak.util.JsonSerialization;
 
+import com.google.common.collect.ImmutableMap;
+import java.io.IOException;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -83,6 +96,10 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
     @Page
     protected ErrorPage errorPage;
 
+    @Deployment
+    public static WebArchive deploy() {
+        return RunOnServerDeployment.create(OIDCAdvancedRequestParamsTest.class, AbstractTestRealmKeycloakTest.class);
+    }
 
     @Override
     public void configureTestRealm(RealmRepresentation testRealm) {
@@ -478,5 +495,78 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
 
         events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent();
     }
+    
+    // CLAIMS
+    // included in the session client notes, so custom providers can make use of it
+    
+    @Test
+    public void processClaimsQueryParam() throws IOException {
+        Map<String, Object> claims = ImmutableMap.of(
+                "id_token", ImmutableMap.of(
+                        "test_claim", ImmutableMap.of(
+                                "essential", true)));
+
+        String claimsJson = JsonSerialization.writeValueAsString(claims);
+
+        driver.navigate().to(oauth.getLoginFormUrl() + "&" + OIDCLoginProtocol.CLAIMS_PARAM + "=" + claimsJson);
+        
+        // need to login so session id can be read from event
+        loginPage.assertCurrent();
+        loginPage.login("test-user@localhost", "password");
+        Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        EventRepresentation loginEvent = events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent();
+        String sessionId = loginEvent.getSessionId();
+        String clientId = loginEvent.getClientId();
+        
+        testingClient.server("test").run(session -> {
+            RealmModel realmModel = session.getContext().getRealm();
+            String clientUuid = realmModel.getClientByClientId(clientId).getId();
+            UserSessionModel userSession = session.sessions().getUserSession(realmModel, sessionId);
+            AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessions().get(clientUuid);
+            
+            String claimsInSession = clientSession.getNote(OIDCLoginProtocol.CLAIMS_PARAM);
+            assertEquals(claimsJson, claimsInSession);
+        });
+    }
+    
+    @Test
+    public void processClaimsRequestParam() throws Exception {
+        Map<String, Object> claims = ImmutableMap.of(
+                "id_token", ImmutableMap.of(
+                        "test_claim", ImmutableMap.of(
+                                "essential", true)));
+        
+        String claimsJson = JsonSerialization.writeValueAsString(claims);
+
+        Map<String, Object> oidcRequest = new HashMap<>();
+        oidcRequest.put(OIDCLoginProtocol.CLIENT_ID_PARAM, "test-app");
+        oidcRequest.put(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, OAuth2Constants.CODE);
+        oidcRequest.put(OIDCLoginProtocol.REDIRECT_URI_PARAM, oauth.getRedirectUri());
+        oidcRequest.put(OIDCLoginProtocol.CLAIMS_PARAM, claims);
+
+        String request = new JWSBuilder().jsonContent(oidcRequest).none();
+        
+        driver.navigate().to(oauth.getLoginFormUrl() + "&" + OIDCLoginProtocol.REQUEST_PARAM + "=" + request);
+        
+        // need to login so session id can be read from event
+        loginPage.assertCurrent();
+        loginPage.login("test-user@localhost", "password");
+        Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        EventRepresentation loginEvent = events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent();
+        String sessionId = loginEvent.getSessionId();
+        String clientId = loginEvent.getClientId();
+        
+        testingClient.server("test").run(session -> {
+            RealmModel realmModel = session.getContext().getRealm();
+            String clientUuid = realmModel.getClientByClientId(clientId).getId();
+            UserSessionModel userSession = session.sessions().getUserSession(realmModel, sessionId);
+            AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessions().get(clientUuid);
+            
+            String claimsInSession = clientSession.getNote(OIDCLoginProtocol.CLAIMS_PARAM);
+            assertEquals(claimsJson, claimsInSession);
+        });
+    }
 
 }