keycloak-uncached
Changes
misc/CrossDataCenter.md 2(+0 -2)
model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java 17(+6 -11)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/KeycloakRemoteStore.java 16(+4 -12)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/KeycloakRemoteStoreConfiguration.java 8(+0 -8)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/KeycloakRemoteStoreConfigurationBuilder.java 44(+0 -44)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/KeycloakTcpTransportFactory.java 120(+0 -120)
services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthorizationEndpointRequest.java 5(+5 -0)
services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestObjectParser.java 24(+16 -8)
services/src/main/java/org/keycloak/protocol/oidc/endpoints/request/AuthzEndpointRequestParser.java 2(+2 -0)
testsuite/integration-arquillian/servers/auth-server/jboss/common/crossdc/cross-dc-setup.cli 2(+0 -2)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java 12(+12 -0)
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;
misc/CrossDataCenter.md 2(+0 -2)
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);
+ });
+ }
}