keycloak-aplcache
Changes
adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakDependencyProcessor.java 3(+3 -0)
adapters/oidc/servlet-filter/pom.xml 2(+1 -1)
adapters/oidc/undertow/pom.xml 2(+1 -1)
adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakDependencyProcessor.java 3(+3 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakDependencyProcessor.java 3(+3 -0)
adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakDependencyProcessor.java 3(+3 -0)
adapters/saml/servlet-filter/pom.xml 2(+1 -1)
adapters/saml/undertow/pom.xml 2(+1 -1)
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakDependencyProcessor.java 3(+3 -0)
adapters/spi/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/FilterSessionStore.java 48(+2 -46)
core/src/main/java/org/keycloak/representations/idm/AbstractAuthenticationExecutionRepresentation.java 77(+77 -0)
core/src/main/java/org/keycloak/representations/idm/AuthenticationExecutionExportRepresentation.java 50(+50 -0)
core/src/main/java/org/keycloak/representations/idm/AuthenticationExecutionInfoRepresentation.java 128(+128 -0)
core/src/main/java/org/keycloak/representations/idm/AuthenticationExecutionRepresentation.java 87(+18 -69)
core/src/main/java/org/keycloak/representations/idm/AuthenticationFlowRepresentation.java 15(+12 -3)
core/src/main/java/org/keycloak/representations/idm/AuthenticatorConfigInfoRepresentation.java 65(+65 -0)
distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json 16(+9 -7)
examples/basic-auth/pom.xml 2(+1 -1)
examples/demo-template/README.md 27(+24 -3)
examples/kerberos/pom.xml 2(+1 -1)
examples/ldap/pom.xml 2(+1 -1)
examples/multi-tenant/pom.xml 2(+1 -1)
integration/admin-client/src/main/java/org/keycloak/admin/client/resource/AuthenticationManagementResource.java 194(+194 -0)
integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java 4(+4 -0)
integration/client-registration/src/main/java/org/keycloak/client/registration/ClientRegistration.java 2(+1 -1)
model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java 12(+6 -6)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedClient.java 41(+41 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedClientRole.java 32(+32 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedClientTemplate.java 34(+34 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedGroup.java 31(+31 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedRealm.java 59(+59 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedRealmRole.java 31(+31 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedUser.java 30(+30 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/Revisioned.java 10(+10 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProvider.java 401(+401 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProviderFactory.java 161(+161 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedConnectionProviderFactory.java 47(+47 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedRealmCache.java 255(+255 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/UpdateCounter.java 20(+20 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java 463(+463 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProviderFactory.java 161(+161 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingConnectionProviderFactory.java 54(+54 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java 294(+294 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewCacheRealmProviderFactory.java 160(+160 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewConnectionProviderFactory.java 164(+164 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewRealmCache.java 269(+269 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewRealmCacheProvider.java 472(+472 -0)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java 14(+14 -0)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java 5(+4 -1)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/InfinispanUserSessionInitializer.java 39(+10 -29)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/OfflineUserSessionLoader.java 7(+3 -4)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/SessionLoader.java 2(+1 -1)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/TimeAwareInitializerState.java 34(+34 -0)
model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory 4(+3 -1)
model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory 4(+3 -1)
model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ConcurrencyLockingTest.java 88(+88 -0)
model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ConcurrencyVersioningTest.java 269(+269 -0)
model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationExecutionEntity.java 3(+3 -0)
pom.xml 6(+3 -3)
services/pom.xml 2(+1 -1)
services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java 2(+1 -1)
services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java 279(+50 -229)
testsuite/integration/pom.xml 2(+1 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KeycloakSPNegoSchemeFactory.java 8(+4 -4)
testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java 2(+1 -1)
testsuite/integration-arquillian/README.md 21(+16 -5)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authentication/AbstractAuthenticationTest.java 151(+151 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authentication/ExecutionTest.java 173(+173 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authentication/FlowTest.java 128(+128 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authentication/InitialFlowsTest.java 200(+200 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authentication/InitialProvidersTest.java 101(+101 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authentication/ShiftExecutionTest.java 81(+81 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/AbstractClusterTest.java 30(+23 -7)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/AbstractTwoNodeClusterTest.java 57(+57 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/EntityInvalidationClusterTest.java 90(+34 -56)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/SessionFailoverClusterTest.java 95(+95 -0)
testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json 29(+21 -8)
testsuite/jetty/jetty81/pom.xml 2(+1 -1)
testsuite/jetty/jetty91/pom.xml 2(+1 -1)
testsuite/jetty/jetty92/pom.xml 2(+1 -1)
testsuite/proxy/pom.xml 2(+1 -1)
testsuite/tomcat6/pom.xml 2(+1 -1)
testsuite/tomcat7/pom.xml 2(+1 -1)
testsuite/tomcat8/pom.xml 2(+1 -1)
testsuite/wildfly/pom.xml 2(+1 -1)
Details
diff --git a/adapters/oidc/as7-eap6/as7-adapter/pom.xml b/adapters/oidc/as7-eap6/as7-adapter/pom.xml
index a9251d5..55037af 100755
--- a/adapters/oidc/as7-eap6/as7-adapter/pom.xml
+++ b/adapters/oidc/as7-eap6/as7-adapter/pom.xml
@@ -73,7 +73,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/adapters/oidc/as7-eap6/as7-adapter-spi/pom.xml b/adapters/oidc/as7-eap6/as7-adapter-spi/pom.xml
index c926324..217b417 100755
--- a/adapters/oidc/as7-eap6/as7-adapter-spi/pom.xml
+++ b/adapters/oidc/as7-eap6/as7-adapter-spi/pom.xml
@@ -41,7 +41,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakDependencyProcessor.java b/adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakDependencyProcessor.java
index 2ea60dc..8f84a7c 100755
--- a/adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakDependencyProcessor.java
+++ b/adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakDependencyProcessor.java
@@ -49,6 +49,9 @@ public abstract class KeycloakDependencyProcessor implements DeploymentUnitProce
String deploymentName = deploymentUnit.getName();
if (!KeycloakAdapterConfigService.getInstance().isSecureDeployment(deploymentName)) {
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
+ if (warMetaData == null) {
+ return;
+ }
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
if (webMetaData == null) {
return;
diff --git a/adapters/oidc/jaxrs-oauth-client/pom.xml b/adapters/oidc/jaxrs-oauth-client/pom.xml
index eac0f49..60e8ae7 100755
--- a/adapters/oidc/jaxrs-oauth-client/pom.xml
+++ b/adapters/oidc/jaxrs-oauth-client/pom.xml
@@ -72,7 +72,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/adapters/oidc/js/src/main/resources/keycloak.js b/adapters/oidc/js/src/main/resources/keycloak.js
index dad522e..4bfec59 100755
--- a/adapters/oidc/js/src/main/resources/keycloak.js
+++ b/adapters/oidc/js/src/main/resources/keycloak.js
@@ -236,7 +236,7 @@
kc.createLogoutUrl = function(options) {
var url = getRealmUrl()
+ '/protocol/openid-connect/logout'
- + '?redirect_uri=' + encodeURIComponent(adapter.redirectUri(options));
+ + '?redirect_uri=' + encodeURIComponent(adapter.redirectUri(options, false));
return url;
}
@@ -842,14 +842,18 @@
return createPromise().promise;
},
- redirectUri: function(options) {
+ redirectUri: function(options, encodeHash) {
+ if (arguments.length == 1) {
+ encodeHash = true;
+ }
+
if (options && options.redirectUri) {
return options.redirectUri;
} else if (kc.redirectUri) {
return kc.redirectUri;
} else {
var redirectUri = location.href;
- if (location.hash) {
+ if (location.hash && encodeHash) {
redirectUri = redirectUri.substring(0, location.href.indexOf('#'));
redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'redirect_fragment=' + encodeURIComponent(location.hash.substring(1));
}
adapters/oidc/servlet-filter/pom.xml 2(+1 -1)
diff --git a/adapters/oidc/servlet-filter/pom.xml b/adapters/oidc/servlet-filter/pom.xml
index 1e01239..9e39669 100755
--- a/adapters/oidc/servlet-filter/pom.xml
+++ b/adapters/oidc/servlet-filter/pom.xml
@@ -75,7 +75,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/adapters/oidc/servlet-oauth-client/pom.xml b/adapters/oidc/servlet-oauth-client/pom.xml
index 560eecf..0f2891e 100755
--- a/adapters/oidc/servlet-oauth-client/pom.xml
+++ b/adapters/oidc/servlet-oauth-client/pom.xml
@@ -53,7 +53,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/adapters/oidc/spring-security/pom.xml b/adapters/oidc/spring-security/pom.xml
index ff39fb5..d07f2e4 100755
--- a/adapters/oidc/spring-security/pom.xml
+++ b/adapters/oidc/spring-security/pom.xml
@@ -52,7 +52,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
adapters/oidc/undertow/pom.xml 2(+1 -1)
diff --git a/adapters/oidc/undertow/pom.xml b/adapters/oidc/undertow/pom.xml
index ed5e7ce..80d476e 100755
--- a/adapters/oidc/undertow/pom.xml
+++ b/adapters/oidc/undertow/pom.xml
@@ -75,7 +75,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakDependencyProcessor.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakDependencyProcessor.java
index dc7b2e0..26a9723 100755
--- a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakDependencyProcessor.java
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakDependencyProcessor.java
@@ -49,6 +49,9 @@ public abstract class KeycloakDependencyProcessor implements DeploymentUnitProce
String deploymentName = deploymentUnit.getName();
if (!KeycloakAdapterConfigService.getInstance().isSecureDeployment(deploymentName)) {
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
+ if (warMetaData == null) {
+ return;
+ }
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
if (webMetaData == null) {
return;
diff --git a/adapters/oidc/wildfly/wildfly-adapter/pom.xml b/adapters/oidc/wildfly/wildfly-adapter/pom.xml
index 8e83abd..c92a5ae 100755
--- a/adapters/oidc/wildfly/wildfly-adapter/pom.xml
+++ b/adapters/oidc/wildfly/wildfly-adapter/pom.xml
@@ -89,7 +89,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakDependencyProcessor.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakDependencyProcessor.java
index 1a83f12..373f57a 100755
--- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakDependencyProcessor.java
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakDependencyProcessor.java
@@ -49,6 +49,9 @@ public abstract class KeycloakDependencyProcessor implements DeploymentUnitProce
String deploymentName = deploymentUnit.getName();
if (!KeycloakAdapterConfigService.getInstance().isSecureDeployment(deploymentName)) {
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
+ if (warMetaData == null) {
+ return;
+ }
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
if (webMetaData == null) {
return;
diff --git a/adapters/saml/as7-eap6/adapter/pom.xml b/adapters/saml/as7-eap6/adapter/pom.xml
index 25cf62f..f190d76 100755
--- a/adapters/saml/as7-eap6/adapter/pom.xml
+++ b/adapters/saml/as7-eap6/adapter/pom.xml
@@ -53,7 +53,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakDependencyProcessor.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakDependencyProcessor.java
index 75190de..fc326e6 100755
--- a/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakDependencyProcessor.java
+++ b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakDependencyProcessor.java
@@ -48,6 +48,9 @@ public abstract class KeycloakDependencyProcessor implements DeploymentUnitProce
String deploymentName = deploymentUnit.getName();
if (Configuration.INSTANCE.getSecureDeployment(deploymentName) == null) {
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
+ if (warMetaData == null) {
+ return;
+ }
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
if (webMetaData == null) {
return;
adapters/saml/servlet-filter/pom.xml 2(+1 -1)
diff --git a/adapters/saml/servlet-filter/pom.xml b/adapters/saml/servlet-filter/pom.xml
index 69ce19b..e3dbee8 100755
--- a/adapters/saml/servlet-filter/pom.xml
+++ b/adapters/saml/servlet-filter/pom.xml
@@ -61,7 +61,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
adapters/saml/undertow/pom.xml 2(+1 -1)
diff --git a/adapters/saml/undertow/pom.xml b/adapters/saml/undertow/pom.xml
index f429c45..ce7006e 100755
--- a/adapters/saml/undertow/pom.xml
+++ b/adapters/saml/undertow/pom.xml
@@ -69,7 +69,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/adapters/saml/wildfly/wildfly-adapter/pom.xml b/adapters/saml/wildfly/wildfly-adapter/pom.xml
index 7b4e9a7..acc426f 100755
--- a/adapters/saml/wildfly/wildfly-adapter/pom.xml
+++ b/adapters/saml/wildfly/wildfly-adapter/pom.xml
@@ -69,7 +69,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
diff --git a/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakDependencyProcessor.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakDependencyProcessor.java
index 4ab6283..391310c 100755
--- a/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakDependencyProcessor.java
+++ b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakDependencyProcessor.java
@@ -47,6 +47,9 @@ public abstract class KeycloakDependencyProcessor implements DeploymentUnitProce
String deploymentName = deploymentUnit.getName();
if (Configuration.INSTANCE.getSecureDeployment(deploymentName) == null) {
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
+ if (warMetaData == null) {
+ return;
+ }
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
if (webMetaData == null) {
return;
diff --git a/adapters/spi/jboss-adapter-core/pom.xml b/adapters/spi/jboss-adapter-core/pom.xml
index a2229e3..9e05f33 100755
--- a/adapters/spi/jboss-adapter-core/pom.xml
+++ b/adapters/spi/jboss-adapter-core/pom.xml
@@ -67,7 +67,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/adapters/spi/servlet-adapter-spi/pom.xml b/adapters/spi/servlet-adapter-spi/pom.xml
index d6f82e2..c4c944b 100755
--- a/adapters/spi/servlet-adapter-spi/pom.xml
+++ b/adapters/spi/servlet-adapter-spi/pom.xml
@@ -45,7 +45,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/adapters/spi/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/FilterSessionStore.java b/adapters/spi/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/FilterSessionStore.java
index 2470ef4..6a17c8e 100755
--- a/adapters/spi/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/FilterSessionStore.java
+++ b/adapters/spi/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/FilterSessionStore.java
@@ -23,7 +23,6 @@ import org.keycloak.adapters.spi.KeycloakAccount;
import org.keycloak.common.util.Encode;
import org.keycloak.common.util.MultivaluedHashMap;
-import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
@@ -174,54 +173,11 @@ public class FilterSessionStore implements AdapterSessionStore {
public ServletInputStream getInputStream() throws IOException {
if (needRequestRestore && body != null) {
+ final ByteArrayInputStream is = new ByteArrayInputStream(body);
return new ServletInputStream() {
- private int lastIndex = 0;
- private ReadListener readListener = null;
-
- @Override
- public boolean isFinished() {
- return lastIndex == body.length;
- }
-
- @Override
- public boolean isReady() {
- return true;
- }
-
- @Override
- public void setReadListener(ReadListener readListener) {
- this.readListener = readListener;
- if (!isFinished()) {
- try {
- readListener.onDataAvailable();
- } catch (IOException e) {
- readListener.onError(e);
- }
- } else {
- try {
- readListener.onAllDataRead();
- } catch (IOException e) {
- readListener.onError(e);
- }
- }
- }
-
@Override
public int read() throws IOException {
- int i = -1;
- if (!isFinished()) {
- i = body[lastIndex];
- lastIndex++;
- if (isFinished() && readListener != null) {
- try {
- readListener.onAllDataRead();
- } catch (IOException e) {
- readListener.onError(e);
- throw e;
- }
- }
- }
- return i;
+ return is.read();
}
};
}
diff --git a/adapters/spi/undertow-adapter-spi/pom.xml b/adapters/spi/undertow-adapter-spi/pom.xml
index 1aae136..7912f1d 100755
--- a/adapters/spi/undertow-adapter-spi/pom.xml
+++ b/adapters/spi/undertow-adapter-spi/pom.xml
@@ -47,7 +47,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/core/src/main/java/org/keycloak/representations/idm/AbstractAuthenticationExecutionRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/AbstractAuthenticationExecutionRepresentation.java
new file mode 100644
index 0000000..8cd4f28
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/AbstractAuthenticationExecutionRepresentation.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.representations.idm;
+
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class AbstractAuthenticationExecutionRepresentation implements Serializable {
+
+ private String authenticatorConfig;
+ private String authenticator;
+ private boolean authenticatorFlow;
+ private String requirement;
+ private int priority;
+
+ public String getAuthenticatorConfig() {
+ return authenticatorConfig;
+ }
+
+ public void setAuthenticatorConfig(String authenticatorConfig) {
+ this.authenticatorConfig = authenticatorConfig;
+ }
+
+ public String getAuthenticator() {
+ return authenticator;
+ }
+
+ public void setAuthenticator(String authenticator) {
+ this.authenticator = authenticator;
+ }
+
+ public String getRequirement() {
+ return requirement;
+ }
+
+ public void setRequirement(String requirement) {
+ this.requirement = requirement;
+ }
+
+ public int getPriority() {
+ return priority;
+ }
+
+ public void setPriority(int priority) {
+ this.priority = priority;
+ }
+
+ /**
+ * Is the referenced authenticator a flow?
+ *
+ * @return
+ */
+ public boolean isAutheticatorFlow() {
+ return authenticatorFlow;
+ }
+
+ public void setAutheticatorFlow(boolean autheticatorFlow) {
+ this.authenticatorFlow = autheticatorFlow;
+ }
+}
diff --git a/core/src/main/java/org/keycloak/representations/idm/AuthenticationExecutionExportRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/AuthenticationExecutionExportRepresentation.java
new file mode 100755
index 0000000..74a8358
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/AuthenticationExecutionExportRepresentation.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.representations.idm;
+
+/**
+* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+* @version $Revision: 1 $
+*/
+public class AuthenticationExecutionExportRepresentation extends AbstractAuthenticationExecutionRepresentation {
+
+ private String flowAlias;
+ private boolean userSetupAllowed;
+
+
+ public boolean isUserSetupAllowed() {
+ return userSetupAllowed;
+ }
+
+ public void setUserSetupAllowed(boolean userSetupAllowed) {
+ this.userSetupAllowed = userSetupAllowed;
+ }
+
+ /**
+ * If this execution is a flow, this is the flowId pointing to an AuthenticationFlowModel
+ *
+ * @return
+ */
+ public String getFlowAlias() {
+ return flowAlias;
+ }
+
+ public void setFlowAlias(String flowId) {
+ this.flowAlias = flowId;
+ }
+}
diff --git a/core/src/main/java/org/keycloak/representations/idm/AuthenticationExecutionInfoRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/AuthenticationExecutionInfoRepresentation.java
new file mode 100755
index 0000000..1ff76a6
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/AuthenticationExecutionInfoRepresentation.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.representations.idm;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+* @version $Revision: 1 $
+*/
+public class AuthenticationExecutionInfoRepresentation implements Serializable {
+
+ protected String id;
+ protected String requirement;
+ protected String displayName;
+ protected List<String> requirementChoices;
+ protected Boolean configurable;
+ protected Boolean authenticationFlow;
+ protected String providerId;
+ protected String authenticationConfig;
+ protected String flowId;
+ protected int level;
+ protected int index;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String execution) {
+ this.id = execution;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public void setDisplayName(String displayName) {
+ this.displayName = displayName;
+ }
+
+ public String getRequirement() {
+ return requirement;
+ }
+
+ public void setRequirement(String requirement) {
+ this.requirement = requirement;
+ }
+
+ public List<String> getRequirementChoices() {
+ return requirementChoices;
+ }
+
+ public void setRequirementChoices(List<String> requirementChoices) {
+ this.requirementChoices = requirementChoices;
+ }
+
+ public Boolean getConfigurable() {
+ return configurable;
+ }
+
+ public void setConfigurable(Boolean configurable) {
+ this.configurable = configurable;
+ }
+
+ public String getProviderId() {
+ return providerId;
+ }
+
+ public void setProviderId(String providerId) {
+ this.providerId = providerId;
+ }
+
+ public String getAuthenticationConfig() {
+ return authenticationConfig;
+ }
+
+ public void setAuthenticationConfig(String authenticationConfig) {
+ this.authenticationConfig = authenticationConfig;
+ }
+
+ public Boolean getAuthenticationFlow() {
+ return authenticationFlow;
+ }
+
+ public void setAuthenticationFlow(Boolean authenticationFlow) {
+ this.authenticationFlow = authenticationFlow;
+ }
+
+ public int getLevel() {
+ return level;
+ }
+
+ public void setLevel(int level) {
+ this.level = level;
+ }
+
+ public int getIndex() {
+ return index;
+ }
+
+ public void setIndex(int index) {
+ this.index = index;
+ }
+
+ public String getFlowId() {
+ return flowId;
+ }
+
+ public void setFlowId(String flowId) {
+ this.flowId = flowId;
+ }
+}
diff --git a/core/src/main/java/org/keycloak/representations/idm/AuthenticationExecutionRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/AuthenticationExecutionRepresentation.java
old mode 100755
new mode 100644
index 7b5ee76..532166c
--- a/core/src/main/java/org/keycloak/representations/idm/AuthenticationExecutionRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/AuthenticationExecutionRepresentation.java
@@ -17,87 +17,36 @@
package org.keycloak.representations.idm;
-import java.io.Serializable;
-import java.util.Comparator;
-
/**
-* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
-* @version $Revision: 1 $
-*/
-public class AuthenticationExecutionRepresentation implements Serializable {
-
- private String authenticatorConfig;
- private String authenticator;
- private String flowAlias;
- private boolean autheticatorFlow;
- private String requirement;
- private boolean userSetupAllowed;
- private int priority;
-
- public String getAuthenticatorConfig() {
- return authenticatorConfig;
- }
-
- public void setAuthenticatorConfig(String authenticatorConfig) {
- this.authenticatorConfig = authenticatorConfig;
- }
-
- public String getAuthenticator() {
- return authenticator;
- }
-
- public void setAuthenticator(String authenticator) {
- this.authenticator = authenticator;
- }
-
- public String getRequirement() {
- return requirement;
- }
-
- public void setRequirement(String requirement) {
- this.requirement = requirement;
- }
-
- public int getPriority() {
- return priority;
- }
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class AuthenticationExecutionRepresentation extends AbstractAuthenticationExecutionRepresentation {
- public void setPriority(int priority) {
- this.priority = priority;
- }
+ private String id;
+ private String flowId;
+ private String parentFlow;
- public boolean isUserSetupAllowed() {
- return userSetupAllowed;
+ public String getId() {
+ return id;
}
- public void setUserSetupAllowed(boolean userSetupAllowed) {
- this.userSetupAllowed = userSetupAllowed;
+ public void setId(String id) {
+ this.id = id;
}
- /**
- * If this execution is a flow, this is the flowId pointing to an AuthenticationFlowModel
- *
- * @return
- */
- public String getFlowAlias() {
- return flowAlias;
+ public String getFlowId() {
+ return flowId;
}
- public void setFlowAlias(String flowId) {
- this.flowAlias = flowId;
+ public void setFlowId(String flowId) {
+ this.flowId = flowId;
}
- /**
- * Is the referenced authenticator a flow?
- *
- * @return
- */
- public boolean isAutheticatorFlow() {
- return autheticatorFlow;
+ public String getParentFlow() {
+ return parentFlow;
}
- public void setAutheticatorFlow(boolean autheticatorFlow) {
- this.autheticatorFlow = autheticatorFlow;
+ public void setParentFlow(String parentFlow) {
+ this.parentFlow = parentFlow;
}
-
}
diff --git a/core/src/main/java/org/keycloak/representations/idm/AuthenticationFlowRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/AuthenticationFlowRepresentation.java
index 0e7e4fb..ee58fc4 100755
--- a/core/src/main/java/org/keycloak/representations/idm/AuthenticationFlowRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/AuthenticationFlowRepresentation.java
@@ -26,12 +26,21 @@ import java.util.List;
*/
public class AuthenticationFlowRepresentation implements Serializable {
+ private String id;
private String alias;
private String description;
private String providerId;
private boolean topLevel;
private boolean builtIn;
- protected List<AuthenticationExecutionRepresentation> authenticationExecutions;
+ protected List<AuthenticationExecutionExportRepresentation> authenticationExecutions;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
public String getAlias() {
return alias;
@@ -73,11 +82,11 @@ public class AuthenticationFlowRepresentation implements Serializable {
this.builtIn = builtIn;
}
- public List<AuthenticationExecutionRepresentation> getAuthenticationExecutions() {
+ public List<AuthenticationExecutionExportRepresentation> getAuthenticationExecutions() {
return authenticationExecutions;
}
- public void setAuthenticationExecutions(List<AuthenticationExecutionRepresentation> authenticationExecutions) {
+ public void setAuthenticationExecutions(List<AuthenticationExecutionExportRepresentation> authenticationExecutions) {
this.authenticationExecutions = authenticationExecutions;
}
}
diff --git a/core/src/main/java/org/keycloak/representations/idm/AuthenticatorConfigInfoRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/AuthenticatorConfigInfoRepresentation.java
new file mode 100644
index 0000000..a26998b
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/AuthenticatorConfigInfoRepresentation.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.representations.idm;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class AuthenticatorConfigInfoRepresentation {
+
+ protected String name;
+ protected String providerId;
+ protected String helpText;
+
+ protected List<ConfigPropertyRepresentation> properties;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getHelpText() {
+ return helpText;
+ }
+
+ public String getProviderId() {
+ return providerId;
+ }
+
+ public void setProviderId(String providerId) {
+ this.providerId = providerId;
+ }
+
+ public void setHelpText(String helpText) {
+ this.helpText = helpText;
+ }
+
+ public List<ConfigPropertyRepresentation> getProperties() {
+ return properties;
+ }
+
+ public void setProperties(List<ConfigPropertyRepresentation> properties) {
+ this.properties = properties;
+ }
+}
+
diff --git a/distribution/demo-dist/src/main/xslt/standalone.xsl b/distribution/demo-dist/src/main/xslt/standalone.xsl
index c8a02b0..2b67a54 100755
--- a/distribution/demo-dist/src/main/xslt/standalone.xsl
+++ b/distribution/demo-dist/src/main/xslt/standalone.xsl
@@ -89,6 +89,9 @@
<local-cache name="sessions"/>
<local-cache name="offlineSessions"/>
<local-cache name="loginFailures"/>
+ <local-cache name="realmVersions">
+ <transaction mode="BATCH" locking="PESSIMISTIC"/>
+ </local-cache>
</cache-container>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json b/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json
old mode 100644
new mode 100755
index b96d61b..41dab85
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json
@@ -28,12 +28,6 @@
}
},
- "realmCache": {
- "infinispan" : {
- "enabled": true
- }
- },
-
"userSessionPersister": {
"provider": "jpa"
},
@@ -67,8 +61,16 @@
}
},
+ "realmCache": {
+ "provider": "infinispan-locking",
+ "infinispan-locking" : {
+ "enabled": true
+ }
+ },
+
"connectionsInfinispan": {
- "default" : {
+ "provider": "locking",
+ "locking": {
"cacheContainer" : "java:comp/env/infinispan/Keycloak"
}
}
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/client-registration.xml b/docbook/auth-server-docs/reference/en/en-US/modules/client-registration.xml
index c1f0ad4..d60c1cd 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/client-registration.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/client-registration.xml
@@ -27,7 +27,7 @@
<para>
The Client Registration Service provides built-in support for Keycloak Client Representations, OpenID Connect
Client Meta Data and SAML Entity Descriptors. It's also possible to plugin custom client registration providers
- if required. The Client Registration Service endpoint is <literal><KEYCLOAK URL>/realms/<realm>/clients/<provider></literal>.
+ if required. The Client Registration Service endpoint is <literal><KEYCLOAK URL>/realms/<realm>/clients-registrations/<provider></literal>.
</para>
<para>
The built-in supported <literal>providers</literal> are:
@@ -123,23 +123,23 @@ Authorization: bearer eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiJmMjJmNzQyYy04ZjNlLTQ2M....
</para>
<para>
To create a client create a Client Representation (JSON) then do a HTTP POST to:
- <literal><KEYCLOAK URL>/realms/<realm>/clients/default</literal>. It will return a Client Representation
+ <literal><KEYCLOAK URL>/realms/<realm>/clients-registrations/default</literal>. It will return a Client Representation
that also includes the registration access token. You should save the registration access token somewhere
if you want to retrieve the config, update or delete the client later.
</para>
<para>
To retrieve the Client Representation then do a HTTP GET to:
- <literal><KEYCLOAK URL>/realms/<realm>/clients/default/<client id></literal>. It will also
+ <literal><KEYCLOAK URL>/realms/<realm>/clients-registrations/default/<client id></literal>. It will also
return a new registration access token.
</para>
<para>
To update the Client Representation then do a HTTP PUT to with the updated Client Representation to:
- <literal><KEYCLOAK URL>/realms/<realm>/clients/default/<client id></literal>. It will also
+ <literal><KEYCLOAK URL>/realms/<realm>/clients-registrations/default/<client id></literal>. It will also
return a new registration access token.
</para>
<para>
To delete the Client Representation then do a HTTP DELETE to:
- <literal><KEYCLOAK URL>/realms/<realm>/clients/default/<client id></literal>
+ <literal><KEYCLOAK URL>/realms/<realm>/clients-registrations/default/<client id></literal>
</para>
</section>
@@ -155,7 +155,7 @@ Authorization: basic BASE64(client-id + ':' + client-secret)
</para>
<para>
To retrieve the Adapter Configuration then do a HTTP GET to:
- <literal><KEYCLOAK URL>//realms/<realm>/clients/installation/<client id></literal>
+ <literal><KEYCLOAK URL>//realms/<realm>/clients-registrations/installation/<client id></literal>
</para>
<para>
No authentication is required for public clients. This means that for the JavaScript adapter you can
@@ -172,7 +172,7 @@ Authorization: basic BASE64(client-id + ':' + client-secret)
</para>
<para>
The endpoint to use these specifications to register clients in Keycloak is:
- <literal><KEYCLOAK URL>/realms/<realm>/clients/oidc[/<client id>]</literal>.
+ <literal><KEYCLOAK URL>/realms/<realm>/clients-registrations/oidc[/<client id>]</literal>.
</para>
<para>
This endpoints can also be found in the OpenID Connect Discovery endpoint for the realm:
@@ -190,7 +190,7 @@ Authorization: basic BASE64(client-id + ':' + client-secret)
</para>
<para>
To create a client do a HTTP POST with the SAML Entity Descriptor to:
- <literal><KEYCLOAK URL>/realms/<realm>/clients/saml2-entity-descriptor</literal>.
+ <literal><KEYCLOAK URL>/realms/<realm>/clients-registrations/saml2-entity-descriptor</literal>.
</para>
</section>
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml b/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml
index b942709..30bd80c 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml
@@ -108,6 +108,12 @@
</para>
</simplesect>
<simplesect>
+ <title>Client Registration service endpoints moved</title>
+ <para>
+ The Client Registration service endpoints have been moved from <literal>{realm}/clients</literal> to <literal>{realm}/clients-registrations</literal>.
+ </para>
+ </simplesect>
+ <simplesect>
<title>Deprecated OpenID Connect endpoints</title>
<para>
In 1.2 we deprecated a number of endpoints that where not consistent with the OpenID Connect
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
index edf3f36..aceff9b 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml
@@ -396,7 +396,7 @@ bin/add-user.[sh|bat] -r master -u <username> -p <password>
<term>connection-pool-size</term>
<listitem>
<para>
- How many connections can be in the pool.
+ How many connections can be in the pool (128 by default).
</para>
</listitem>
</varlistentry>
@@ -404,7 +404,24 @@ bin/add-user.[sh|bat] -r master -u <username> -p <password>
<term>max-pooled-per-route</term>
<listitem>
<para>
- How many connections can be pooled per host.
+ How many connections can be pooled per host (64 by default).
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>connection-ttl-millis</term>
+ <listitem>
+ <para>
+ Maximum connection time to live in milliseconds. Not set by default.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>max-connection-idle-time-millis</term>
+ <listitem>
+ <para>
+ Maximum time the connection might stay idle in the connection pool (900 seconds by default). Will start background cleaner thread of Apache HTTP client.
+ Set to -1 to disable this checking and the background thread.
</para>
</listitem>
</varlistentry>
examples/basic-auth/pom.xml 2(+1 -1)
diff --git a/examples/basic-auth/pom.xml b/examples/basic-auth/pom.xml
index a1ef3c2..a73db62 100755
--- a/examples/basic-auth/pom.xml
+++ b/examples/basic-auth/pom.xml
@@ -50,7 +50,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/examples/broker/twitter-authentication/pom.xml b/examples/broker/twitter-authentication/pom.xml
index 7a92b08..83ea633 100755
--- a/examples/broker/twitter-authentication/pom.xml
+++ b/examples/broker/twitter-authentication/pom.xml
@@ -58,7 +58,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/examples/cors/database-service/pom.xml b/examples/cors/database-service/pom.xml
index 44262ed..2c22c0b 100755
--- a/examples/cors/database-service/pom.xml
+++ b/examples/cors/database-service/pom.xml
@@ -48,7 +48,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/examples/demo-template/admin-access-app/pom.xml b/examples/demo-template/admin-access-app/pom.xml
index b3f3001..cfabf9b 100755
--- a/examples/demo-template/admin-access-app/pom.xml
+++ b/examples/demo-template/admin-access-app/pom.xml
@@ -42,7 +42,7 @@
<dependencies>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/examples/demo-template/angular2-product-app/pom.xml b/examples/demo-template/angular2-product-app/pom.xml
new file mode 100644
index 0000000..d4bd4d2
--- /dev/null
+++ b/examples/demo-template/angular2-product-app/pom.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+ ~ and other contributors as indicated by the @author tags.
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-examples-demo-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.Final-SNAPSHOT</version>
+ </parent>
+
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.keycloak.example.demo</groupId>
+ <artifactId>angular2-product-example</artifactId>
+ <packaging>war</packaging>
+ <name>Angular2 Product Portal JS</name>
+ <description/>
+
+ <build>
+ <finalName>angular2-product</finalName>
+ <plugins>
+ <plugin>
+ <groupId>org.jboss.as.plugins</groupId>
+ <artifactId>jboss-as-maven-plugin</artifactId>
+ <configuration>
+ <skip>false</skip>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.wildfly.plugins</groupId>
+ <artifactId>wildfly-maven-plugin</artifactId>
+ <configuration>
+ <skip>false</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/app/app.ts b/examples/demo-template/angular2-product-app/src/main/webapp/app/app.ts
new file mode 100644
index 0000000..8630f00
--- /dev/null
+++ b/examples/demo-template/angular2-product-app/src/main/webapp/app/app.ts
@@ -0,0 +1,78 @@
+import {Http, Headers,
+ RequestOptions, Response} from 'angular2/http';
+import {Component} from 'angular2/core';
+import {Observable} from 'rxjs/Observable';
+import {KeycloakService} from './keycloak';
+
+
+
+
+@Component({
+ selector: 'my-app',
+ template:
+`
+<div id="content-area" class="col-md-9" role="main">
+ <div id="content">
+ <h1>Angular2 Product (Beta)</h1>
+ <h2><span>Products</span></h2>
+
+ <button type="button" (click)="logout()">Sign Out</button>
+ <button type="button" (click)="reloadData()">Reload</button>
+ <table class="table" [hidden]="!products.length">
+ <thead>
+ <tr>
+ <th>Product Listing</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr *ngFor="#p of products">
+ <td>{{p}}</td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+</div>
+`
+})
+export class AppComponent {
+
+ constructor(private _kc:KeycloakService, private http:Http){ }
+
+ products : string[] = [];
+
+ logout(){
+ this._kc.logout();
+ }
+
+ reloadData() {
+ //angular dont have http interceptor yet
+
+ this._kc.getToken().then(
+ token=>{
+ let headers = new Headers({
+ 'Accept': 'application/json',
+ 'Authorization': 'Bearer ' + token
+ });
+
+ let options = new RequestOptions({ headers: headers });
+
+ this.http.get('/database/products', options)
+ .map(res => <string[]> res.json())
+ .subscribe(
+ prods => this.products = prods,
+ error => console.log(error));
+
+ },
+ error=>{
+ console.log(error);
+ }
+ );
+
+ }
+
+ private handleError (error: Response) {
+ console.error(error);
+ return Observable.throw(error.json().error || 'Server error');
+ }
+
+}
diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.ts b/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.ts
new file mode 100644
index 0000000..64242ae
--- /dev/null
+++ b/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.ts
@@ -0,0 +1,49 @@
+import {Injectable} from 'angular2/core';
+
+
+declare var Keycloak: any;
+
+@Injectable()
+export class KeycloakService {
+
+ static auth : any = {};
+
+ static init() : Promise<any>{
+ let keycloakAuth : any = new Keycloak('keycloak.json');
+ KeycloakService.auth.loggedIn = false;
+
+ return new Promise((resolve,reject)=>{
+ keycloakAuth.init({ onLoad: 'login-required' })
+ .success( () => {
+ KeycloakService.auth.loggedIn = true;
+ KeycloakService.auth.authz = keycloakAuth;
+ KeycloakService.auth.logoutUrl = keycloakAuth.authServerUrl + "/realms/demo/tokens/logout?redirect_uri=/angular2-product/index.html";
+ resolve(null);
+ })
+ .error(()=> {
+ reject(null);
+ });
+ });
+ }
+
+ logout(){
+ console.log('*** LOGOUT');
+ KeycloakService.auth.loggedIn = false;
+ KeycloakService.auth.authz = null;
+
+ window.location.href = KeycloakService.auth.logoutUrl;
+ }
+
+ getToken(): Promise<string>{
+ return new Promise<string>((resolve,reject)=>{
+ if (KeycloakService.auth.authz.token) {
+ KeycloakService.auth.authz.updateToken(5).success(function() {
+ resolve(<string>KeycloakService.auth.authz.token);
+ })
+ .error(function() {
+ reject('Failed to refresh token');
+ });
+ }
+ });
+ }
+}
diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/app/main.ts b/examples/demo-template/angular2-product-app/src/main/webapp/app/main.ts
new file mode 100644
index 0000000..73613b2
--- /dev/null
+++ b/examples/demo-template/angular2-product-app/src/main/webapp/app/main.ts
@@ -0,0 +1,14 @@
+import 'rxjs/Rx';
+import {bootstrap} from 'angular2/platform/browser';
+import {HTTP_BINDINGS} from 'angular2/http';
+import {KeycloakService} from './keycloak';
+import {AppComponent} from './app';
+
+KeycloakService.init().then(
+ o=>{
+ bootstrap(AppComponent,[HTTP_BINDINGS, KeycloakService]);
+ },
+ x=>{
+ window.location.reload();
+ }
+);
\ No newline at end of file
diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/index.html b/examples/demo-template/angular2-product-app/src/main/webapp/index.html
new file mode 100644
index 0000000..2da600c
--- /dev/null
+++ b/examples/demo-template/angular2-product-app/src/main/webapp/index.html
@@ -0,0 +1,49 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Angular 2 QuickStart</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+
+
+
+
+
+
+ </head>
+
+ <!-- 3. Display the application -->
+ <body>
+ <my-app>Loading...</my-app>
+
+
+
+ <!-- 1. Load libraries -->
+ <!-- IE required polyfills, in this exact order -->
+ <script src="node_modules/es6-shim/es6-shim.min.js"></script>
+ <script src="node_modules/systemjs/dist/system-polyfills.js"></script>
+
+ <script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
+ <script src="node_modules/systemjs/dist/system.src.js"></script>
+ <script src="node_modules/rxjs/bundles/Rx.js"></script>
+ <script src="node_modules/angular2/bundles/angular2.dev.js"></script>
+ <script src="node_modules/angular2/bundles/http.js"></script>
+
+
+ <script src="/auth/js/keycloak.js"></script>
+
+ <!-- 2. Configure SystemJS -->
+ <script>
+ System.config({
+ packages: {
+ app: {
+ format: 'register',
+ defaultExtension: 'js'
+ }
+ }
+ });
+ System.import('app/main')
+ .then(null, console.error.bind(console));
+ </script>
+ </body>
+
+</html>
diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/keycloak.json b/examples/demo-template/angular2-product-app/src/main/webapp/keycloak.json
new file mode 100644
index 0000000..ddd1f2e
--- /dev/null
+++ b/examples/demo-template/angular2-product-app/src/main/webapp/keycloak.json
@@ -0,0 +1,8 @@
+{
+ "realm": "demo",
+ "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+ "auth-server-url": "/auth",
+ "ssl-required": "external",
+ "resource": "angular2-product",
+ "public-client": true
+}
\ No newline at end of file
diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/package.json b/examples/demo-template/angular2-product-app/src/main/webapp/package.json
new file mode 100644
index 0000000..f66b44c
--- /dev/null
+++ b/examples/demo-template/angular2-product-app/src/main/webapp/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "angular2-product-app",
+ "version": "1.0.0",
+ "scripts": {
+ "tsc": "tsc",
+ "tsc:w": "tsc -w",
+ "lite": "lite-server",
+ "start": "concurrent \"npm run tsc:w\" \"npm run lite\" "
+ },
+ "license": "ISC",
+ "dependencies": {
+ "angular2": "2.0.0-beta.3",
+ "systemjs": "0.19.6",
+ "es6-promise": "^3.0.2",
+ "es6-shim": "^0.33.3",
+ "reflect-metadata": "0.1.2",
+ "rxjs": "5.0.0-beta.0",
+ "zone.js": "0.5.11"
+ },
+ "devDependencies": {
+ "concurrently": "^1.0.0",
+ "lite-server": "^2.0.1",
+ "typescript": "^1.7.5"
+ }
+}
diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/tsconfig.json b/examples/demo-template/angular2-product-app/src/main/webapp/tsconfig.json
new file mode 100644
index 0000000..52c77a5
--- /dev/null
+++ b/examples/demo-template/angular2-product-app/src/main/webapp/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "module": "system",
+ "moduleResolution": "node",
+ "sourceMap": false,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "removeComments": false,
+ "noImplicitAny": false
+ },
+ "exclude": [
+ "node_modules"
+ ]
+}
\ No newline at end of file
diff --git a/examples/demo-template/customer-app/pom.xml b/examples/demo-template/customer-app/pom.xml
index 1fdf6db..4c52e05 100755
--- a/examples/demo-template/customer-app/pom.xml
+++ b/examples/demo-template/customer-app/pom.xml
@@ -42,7 +42,7 @@
<dependencies>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/examples/demo-template/customer-app-filter/pom.xml b/examples/demo-template/customer-app-filter/pom.xml
index 758d6fb..fb17b6b 100755
--- a/examples/demo-template/customer-app-filter/pom.xml
+++ b/examples/demo-template/customer-app-filter/pom.xml
@@ -42,7 +42,7 @@
<dependencies>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/examples/demo-template/database-service/pom.xml b/examples/demo-template/database-service/pom.xml
index a864cee..f3465e7 100755
--- a/examples/demo-template/database-service/pom.xml
+++ b/examples/demo-template/database-service/pom.xml
@@ -48,7 +48,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/examples/demo-template/offline-access-app/pom.xml b/examples/demo-template/offline-access-app/pom.xml
index 3cefb0d..8c715dd 100644
--- a/examples/demo-template/offline-access-app/pom.xml
+++ b/examples/demo-template/offline-access-app/pom.xml
@@ -42,7 +42,7 @@
<dependencies>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/examples/demo-template/product-app/pom.xml b/examples/demo-template/product-app/pom.xml
index a44de34..93d2425 100755
--- a/examples/demo-template/product-app/pom.xml
+++ b/examples/demo-template/product-app/pom.xml
@@ -42,7 +42,7 @@
<dependencies>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
examples/demo-template/README.md 27(+24 -3)
diff --git a/examples/demo-template/README.md b/examples/demo-template/README.md
index fa46d6c..46211f9 100755
--- a/examples/demo-template/README.md
+++ b/examples/demo-template/README.md
@@ -202,7 +202,28 @@ An Angular JS example using Keycloak to secure it.
If you are already logged in, you will not be asked for a username and password, but you will be redirected to
an oauth grant page. This page asks you if you want to grant certain permissions to the third-part app.
-Step 9: Pure HTML5/Javascript Example
+Step 10: Angular2 JS Example
+----------------------------------
+An Angular2 JS example using Keycloak to secure it. Angular2 is in beta version yet.
+
+To install angular2
+```
+$ cd keycloak/examples/demo-template/angular2-product-app/src/main/webapp/
+$ npm install
+```
+
+Transpile TypeScript to JavaScript before running the application.
+```
+$ npm run tsc
+```
+
+[http://localhost:8080/angular2-product](http://localhost:8080/angular2-product)
+
+If you are already logged in, you will not be asked for a username and password, but you will be redirected to
+an oauth grant page. This page asks you if you want to grant certain permissions to the third-part app.
+
+
+Step 11: Pure HTML5/Javascript Example
----------------------------------
An pure HTML5/Javascript example using Keycloak to secure it.
@@ -211,7 +232,7 @@ An pure HTML5/Javascript example using Keycloak to secure it.
If you are already logged in, you will not be asked for a username and password, but you will be redirected to
an oauth grant page. This page asks you if you want to grant certain permissions to the third-part app.
-Step 10: Service Account Example
+Step 12: Service Account Example
================================
An example for retrieve service account dedicated to the Client Application itself (not to any user).
@@ -222,7 +243,7 @@ Client authentication is done with OAuth2 Client Credentials Grant in out-of-bou
The example also shows different methods of client authentication. There is ProductSAClientSecretServlet using traditional authentication with clientId and client_secret,
but there is also ProductSAClientSignedJWTServlet using client authentication with JWT signed by client private key.
-Step 11: Offline Access Example
+Step 13: Offline Access Example
===============================
An example for retrieve offline token, which is then saved to the database and can be used by application anytime later. Offline token
is valid even if user is already logged out from SSO. Server restart also won't invalidate offline token. Offline token can be revoked by the user in
diff --git a/examples/demo-template/service-account/pom.xml b/examples/demo-template/service-account/pom.xml
index 68e69c8..33f9e5e 100644
--- a/examples/demo-template/service-account/pom.xml
+++ b/examples/demo-template/service-account/pom.xml
@@ -34,7 +34,7 @@
<dependencies>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/examples/demo-template/testrealm.json b/examples/demo-template/testrealm.json
index 20cd615..fdebfc7 100755
--- a/examples/demo-template/testrealm.json
+++ b/examples/demo-template/testrealm.json
@@ -148,6 +148,15 @@
"/angular-product/*"
]
},
+ {
+ "clientId": "angular2-product",
+ "enabled": true,
+ "publicClient": true,
+ "baseUrl": "/angular2-product/index.html",
+ "redirectUris": [
+ "/angular2-product/*"
+ ]
+ },
{
"clientId": "customer-portal-cli",
"enabled": true,
diff --git a/examples/demo-template/third-party/pom.xml b/examples/demo-template/third-party/pom.xml
index ebbecaf..7a7ba61 100755
--- a/examples/demo-template/third-party/pom.xml
+++ b/examples/demo-template/third-party/pom.xml
@@ -34,7 +34,7 @@
<dependencies>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/examples/demo-template/third-party-cdi/pom.xml b/examples/demo-template/third-party-cdi/pom.xml
index 5dd08c6..9c25818 100755
--- a/examples/demo-template/third-party-cdi/pom.xml
+++ b/examples/demo-template/third-party-cdi/pom.xml
@@ -34,7 +34,7 @@
<dependencies>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/examples/fuse/customer-app-fuse/pom.xml b/examples/fuse/customer-app-fuse/pom.xml
index 0dea1c4..196227c 100755
--- a/examples/fuse/customer-app-fuse/pom.xml
+++ b/examples/fuse/customer-app-fuse/pom.xml
@@ -52,7 +52,7 @@
<dependencies>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/examples/fuse/product-app-fuse/pom.xml b/examples/fuse/product-app-fuse/pom.xml
index c24b5d1..5fa2684 100755
--- a/examples/fuse/product-app-fuse/pom.xml
+++ b/examples/fuse/product-app-fuse/pom.xml
@@ -53,7 +53,7 @@
<dependencies>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
examples/kerberos/pom.xml 2(+1 -1)
diff --git a/examples/kerberos/pom.xml b/examples/kerberos/pom.xml
index 3252d92..9d9dc25 100755
--- a/examples/kerberos/pom.xml
+++ b/examples/kerberos/pom.xml
@@ -44,7 +44,7 @@
<dependencies>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
examples/ldap/pom.xml 2(+1 -1)
diff --git a/examples/ldap/pom.xml b/examples/ldap/pom.xml
index 2412222..d3767e3 100644
--- a/examples/ldap/pom.xml
+++ b/examples/ldap/pom.xml
@@ -42,7 +42,7 @@
<dependencies>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
examples/multi-tenant/pom.xml 2(+1 -1)
diff --git a/examples/multi-tenant/pom.xml b/examples/multi-tenant/pom.xml
index 757e0b5..f046804 100755
--- a/examples/multi-tenant/pom.xml
+++ b/examples/multi-tenant/pom.xml
@@ -47,7 +47,7 @@
<dependencies>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/AuthenticationManagementResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/AuthenticationManagementResource.java
new file mode 100644
index 0000000..197aeb6
--- /dev/null
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/AuthenticationManagementResource.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.admin.client.resource;
+
+import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
+import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
+import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
+import org.keycloak.representations.idm.AuthenticatorConfigInfoRepresentation;
+import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
+import org.keycloak.representations.idm.ConfigPropertyRepresentation;
+import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public interface AuthenticationManagementResource {
+
+ @GET
+ @Path("/form-providers")
+ @Produces(MediaType.APPLICATION_JSON)
+ List<Map<String, Object>> getFormProviders();
+
+ @Path("/authenticator-providers")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ List<Map<String, Object>> getAuthenticatorProviders();
+
+ @Path("/client-authenticator-providers")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ List<Map<String, Object>> getClientAuthenticatorProviders();
+
+ @Path("/form-action-providers")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ List<Map<String, Object>> getFormActionProviders();
+
+ @Path("/flows")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ List<AuthenticationFlowRepresentation> getFlows();
+
+ @Path("/flows")
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ Response createFlow(AuthenticationFlowRepresentation model);
+
+ @Path("/flows/{id}")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ AuthenticationFlowRepresentation getFlow(@PathParam("id") String id);
+
+ @Path("/flows/{id}")
+ @DELETE
+ void deleteFlow(@PathParam("id") String id);
+
+ @Path("/flows/{flowAlias}/copy")
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ Response copy(@PathParam("flowAlias") String flowAlias, Map<String, String> data);
+
+ @Path("/flows/{flowAlias}/executions/flow")
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ void addExecutionFlow(@PathParam("flowAlias") String flowAlias, Map<String, String> data);
+
+ @Path("/flows/{flowAlias}/executions/execution")
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ void addExecution(@PathParam("flowAlias") String flowAlias, Map<String, String> data);
+
+ @Path("/flows/{flowAlias}/executions")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ Response getExecutions(@PathParam("flowAlias") String flowAlias);
+
+ @Path("/flows/{flowAlias}/executions")
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ void updateExecutions(@PathParam("flowAlias") String flowAlias, AuthenticationExecutionInfoRepresentation rep);
+
+ @Path("/executions")
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ Response addExecution(AuthenticationExecutionRepresentation model);
+
+ @Path("/executions/{executionId}/raise-priority")
+ @POST
+ void raisePriority(@PathParam("executionId") String execution);
+
+ @Path("/executions/{executionId}/lower-priority")
+ @POST
+ void lowerPriority(@PathParam("executionId") String execution);
+
+ @Path("/executions/{executionId}")
+ @DELETE
+ void removeExecution(@PathParam("executionId") String execution);
+
+ @Path("/executions/{executionId}/config")
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ Response newExecutionConfig(@PathParam("executionId") String execution, AuthenticatorConfigRepresentation config);
+
+ @Path("/executions/{executionId}/config/{id}")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ AuthenticatorConfigRepresentation getAuthenticatorConfig(@PathParam("executionId") String execution,@PathParam("id") String id);
+
+ @Path("unregistered-required-actions")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ List<Map<String, String>> getUnregisteredRequiredActions();
+
+ @Path("register-required-action")
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ void registereRequiredAction(Map<String, String> data);
+
+ @Path("required-actions")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ List<RequiredActionProviderRepresentation> getRequiredActions();
+
+ @Path("required-actions/{alias}")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ RequiredActionProviderRepresentation getRequiredAction(@PathParam("alias") String alias);
+
+ @Path("required-actions/{alias}")
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ void updateRequiredAction(@PathParam("alias") String alias, RequiredActionProviderRepresentation rep);
+
+ @Path("required-actions/{alias}")
+ @DELETE
+ void updateRequiredAction(@PathParam("alias") String alias);
+
+ @Path("config-description/{providerId}")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ AuthenticatorConfigInfoRepresentation getAuthenticatorConfigDescription(@PathParam("providerId") String providerId);
+
+ @Path("per-client-config-description")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ Map<String, List<ConfigPropertyRepresentation>> getPerClientConfigDescription();
+
+ @Path("config")
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ Response createAuthenticatorConfig(AuthenticatorConfigRepresentation config);
+
+ @Path("config/{id}")
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ AuthenticatorConfigRepresentation getAuthenticatorConfig(@PathParam("id") String id);
+
+ @Path("config/{id}")
+ @DELETE
+ void removeAuthenticatorConfig(@PathParam("id") String id);
+
+ @Path("config/{id}")
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ void updateAuthenticatorConfig(@PathParam("id") String id, AuthenticatorConfigRepresentation config);
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
index 87f6763..498b3ac 100755
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
@@ -142,4 +142,8 @@ public interface RealmResource {
@Path("clients-initial-access")
ClientInitialAccessResource clientInitialAccess();
+ @Path("authentication")
+ @Consumes(MediaType.APPLICATION_JSON)
+ AuthenticationManagementResource flows();
+
}
diff --git a/integration/client-registration/src/main/java/org/keycloak/client/registration/ClientRegistration.java b/integration/client-registration/src/main/java/org/keycloak/client/registration/ClientRegistration.java
index b83e652..8961aad 100644
--- a/integration/client-registration/src/main/java/org/keycloak/client/registration/ClientRegistration.java
+++ b/integration/client-registration/src/main/java/org/keycloak/client/registration/ClientRegistration.java
@@ -177,7 +177,7 @@ public class ClientRegistration {
}
public ClientRegistrationBuilder url(String authUrl, String realm) {
- url = HttpUtil.getUrl(authUrl, "realms", realm, "clients");
+ url = HttpUtil.getUrl(authUrl, "realms", realm, "clients-registrations");
return this;
}
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 45ce0d2..b90ef33 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
@@ -37,11 +37,11 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
protected static final Logger logger = Logger.getLogger(DefaultInfinispanConnectionProviderFactory.class);
- private Config.Scope config;
+ protected Config.Scope config;
- private EmbeddedCacheManager cacheManager;
+ protected EmbeddedCacheManager cacheManager;
- private boolean containerManaged;
+ protected boolean containerManaged;
@Override
public InfinispanConnectionProvider create(KeycloakSession session) {
@@ -73,7 +73,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
}
- private void lazyInit() {
+ protected void lazyInit() {
if (cacheManager == null) {
synchronized (this) {
if (cacheManager == null) {
@@ -88,7 +88,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
}
}
- private void initContainerManaged(String cacheContainerLookup) {
+ protected void initContainerManaged(String cacheContainerLookup) {
try {
cacheManager = (EmbeddedCacheManager) new InitialContext().lookup(cacheContainerLookup);
containerManaged = true;
@@ -99,7 +99,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
}
}
- private void initEmbedded() {
+ protected void initEmbedded() {
GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
boolean clustered = config.getBoolean("clustered", false);
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
index e5a3472..a9145b2 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
@@ -546,7 +546,9 @@ public class ClientAdapter implements ClientModel {
public RoleModel getRole(String name) {
if (updated != null) return updated.getRole(name);
String id = cached.getRoles().get(name);
- if (id == null) return null;
+ if (id == null) {
+ return null;
+ }
return cacheSession.getRoleById(id, cachedRealm);
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedClient.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedClient.java
new file mode 100755
index 0000000..c07725e
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedClient.java
@@ -0,0 +1,41 @@
+package org.keycloak.models.cache.infinispan.counter.entities;
+
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RealmProvider;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.cache.RealmCache;
+import org.keycloak.models.cache.entities.CachedClient;
+import org.keycloak.models.cache.entities.CachedClientRole;
+import org.keycloak.models.cache.infinispan.counter.Revisioned;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RevisionedCachedClient extends CachedClient implements Revisioned {
+
+ public RevisionedCachedClient(Long revision, RealmCache cache, RealmProvider delegate, RealmModel realm, ClientModel model) {
+ super(cache, delegate, realm, model);
+ this.revision = revision;
+ }
+
+ private Long revision;
+
+ @Override
+ public Long getRevision() {
+ return revision;
+ }
+
+ @Override
+ public void setRevision(Long revision) {
+ this.revision = revision;
+ }
+
+ @Override
+ protected void cacheRoles(RealmCache cache, RealmModel realm, ClientModel model) {
+ for (RoleModel role : model.getRoles()) {
+ roles.put(role.getName(), role.getId());
+ }
+ }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedClientRole.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedClientRole.java
new file mode 100755
index 0000000..2bdb176
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedClientRole.java
@@ -0,0 +1,32 @@
+package org.keycloak.models.cache.infinispan.counter.entities;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.cache.entities.CachedClientRole;
+import org.keycloak.models.cache.infinispan.counter.Revisioned;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RevisionedCachedClientRole extends CachedClientRole implements Revisioned {
+
+ public RevisionedCachedClientRole(Long revision, String idClient, RoleModel model, RealmModel realm) {
+ super(idClient, model, realm);
+ this.revision = revision;
+ }
+
+ private Long revision;
+
+ @Override
+ public Long getRevision() {
+ return revision;
+ }
+
+ @Override
+ public void setRevision(Long revision) {
+ this.revision = revision;
+ }
+
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedClientTemplate.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedClientTemplate.java
new file mode 100755
index 0000000..eeca1f6
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedClientTemplate.java
@@ -0,0 +1,34 @@
+package org.keycloak.models.cache.infinispan.counter.entities;
+
+import org.keycloak.models.ClientTemplateModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RealmProvider;
+import org.keycloak.models.cache.RealmCache;
+import org.keycloak.models.cache.entities.CachedClientTemplate;
+import org.keycloak.models.cache.infinispan.counter.Revisioned;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RevisionedCachedClientTemplate extends CachedClientTemplate implements Revisioned {
+
+ public RevisionedCachedClientTemplate(Long revision, RealmCache cache, RealmProvider delegate, RealmModel realm, ClientTemplateModel model) {
+ super(cache, delegate, realm, model);
+ this.revision = revision;
+ }
+
+ private Long revision;
+
+ @Override
+ public Long getRevision() {
+ return revision;
+ }
+
+ @Override
+ public void setRevision(Long revision) {
+ this.revision = revision;
+ }
+
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedGroup.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedGroup.java
new file mode 100755
index 0000000..32529ea
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedGroup.java
@@ -0,0 +1,31 @@
+package org.keycloak.models.cache.infinispan.counter.entities;
+
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.cache.entities.CachedGroup;
+import org.keycloak.models.cache.infinispan.counter.Revisioned;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RevisionedCachedGroup extends CachedGroup implements Revisioned {
+ public RevisionedCachedGroup(Long revision, RealmModel realm, GroupModel group) {
+ super(realm, group);
+ this.revision = revision;
+ }
+
+ private Long revision;
+
+ @Override
+ public Long getRevision() {
+ return revision;
+ }
+
+ @Override
+ public void setRevision(Long revision) {
+ this.revision = revision;
+ }
+
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedRealm.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedRealm.java
new file mode 100755
index 0000000..c527ea5
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedRealm.java
@@ -0,0 +1,59 @@
+package org.keycloak.models.cache.infinispan.counter.entities;
+
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientTemplateModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RealmProvider;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.cache.RealmCache;
+import org.keycloak.models.cache.entities.CachedClient;
+import org.keycloak.models.cache.entities.CachedClientTemplate;
+import org.keycloak.models.cache.entities.CachedRealm;
+import org.keycloak.models.cache.entities.CachedRealmRole;
+import org.keycloak.models.cache.entities.CachedRole;
+import org.keycloak.models.cache.infinispan.counter.Revisioned;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RevisionedCachedRealm extends CachedRealm implements Revisioned {
+
+ public RevisionedCachedRealm(Long revision, RealmCache cache, RealmProvider delegate, RealmModel model) {
+ super(cache, delegate, model);
+ this.revision = revision;
+ }
+
+ private Long revision;
+
+ @Override
+ public Long getRevision() {
+ return revision;
+ }
+
+ @Override
+ public void setRevision(Long revision) {
+ this.revision = revision;
+ }
+
+ @Override
+ protected void cacheClientTemplates(RealmCache cache, RealmProvider delegate, RealmModel model) {
+ for (ClientTemplateModel template : model.getClientTemplates()) {
+ clientTemplates.add(template.getId());
+ }
+ }
+
+ @Override
+ protected void cacheClients(RealmCache cache, RealmProvider delegate, RealmModel model) {
+ for (ClientModel client : model.getClients()) {
+ clients.put(client.getClientId(), client.getId());
+ }
+ }
+
+ @Override
+ protected void cacheRealmRoles(RealmCache cache, RealmModel model) {
+ for (RoleModel role : model.getRoles()) {
+ realmRoles.put(role.getName(), role.getId());
+ }
+ }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedRealmRole.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedRealmRole.java
new file mode 100755
index 0000000..776b885
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedRealmRole.java
@@ -0,0 +1,31 @@
+package org.keycloak.models.cache.infinispan.counter.entities;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.cache.entities.CachedRealmRole;
+import org.keycloak.models.cache.infinispan.counter.Revisioned;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RevisionedCachedRealmRole extends CachedRealmRole implements Revisioned {
+
+ public RevisionedCachedRealmRole(Long revision, RoleModel model, RealmModel realm) {
+ super(model, realm);
+ this.revision = revision;
+ }
+
+ private Long revision;
+
+ @Override
+ public Long getRevision() {
+ return revision;
+ }
+
+ @Override
+ public void setRevision(Long revision) {
+ this.revision = revision;
+ }
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedUser.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedUser.java
new file mode 100755
index 0000000..f281bef
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedUser.java
@@ -0,0 +1,30 @@
+package org.keycloak.models.cache.infinispan.counter.entities;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.cache.entities.CachedUser;
+import org.keycloak.models.cache.infinispan.counter.Revisioned;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RevisionedCachedUser extends CachedUser implements Revisioned {
+ public RevisionedCachedUser(Long revision, RealmModel realm, UserModel user) {
+ super(realm, user);
+ this.revision = revision;
+ }
+
+ private Long revision;
+
+ @Override
+ public Long getRevision() {
+ return revision;
+ }
+
+ @Override
+ public void setRevision(Long revision) {
+ this.revision = revision;
+ }
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/Revisioned.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/Revisioned.java
new file mode 100755
index 0000000..f4d4c7a
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/Revisioned.java
@@ -0,0 +1,10 @@
+package org.keycloak.models.cache.infinispan.counter;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface Revisioned {
+ Long getRevision();
+ void setRevision(Long revision);
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProvider.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProvider.java
new file mode 100755
index 0000000..d241519
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProvider.java
@@ -0,0 +1,401 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.cache.infinispan.counter;
+
+import org.jboss.logging.Logger;
+import org.keycloak.migration.MigrationModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientTemplateModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakTransaction;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RealmProvider;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.cache.CacheRealmProvider;
+import org.keycloak.models.cache.RealmCache;
+import org.keycloak.models.cache.entities.CachedClient;
+import org.keycloak.models.cache.entities.CachedClientRole;
+import org.keycloak.models.cache.entities.CachedClientTemplate;
+import org.keycloak.models.cache.entities.CachedGroup;
+import org.keycloak.models.cache.entities.CachedRealm;
+import org.keycloak.models.cache.entities.CachedRealmRole;
+import org.keycloak.models.cache.entities.CachedRole;
+import org.keycloak.models.cache.infinispan.ClientAdapter;
+import org.keycloak.models.cache.infinispan.ClientTemplateAdapter;
+import org.keycloak.models.cache.infinispan.GroupAdapter;
+import org.keycloak.models.cache.infinispan.RealmAdapter;
+import org.keycloak.models.cache.infinispan.RoleAdapter;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClient;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClientRole;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClientTemplate;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedGroup;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedRealm;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedRealmRole;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RevisionedCacheRealmProvider implements CacheRealmProvider {
+ protected static final Logger logger = Logger.getLogger(RevisionedCacheRealmProvider.class);
+ protected RevisionedRealmCache cache;
+ protected KeycloakSession session;
+ protected RealmProvider delegate;
+ protected boolean transactionActive;
+ protected boolean setRollbackOnly;
+
+ protected Set<String> realmInvalidations = new HashSet<>();
+ protected Set<String> appInvalidations = new HashSet<>();
+ protected Set<String> clientTemplateInvalidations = new HashSet<>();
+ protected Set<String> roleInvalidations = new HashSet<>();
+ protected Set<String> groupInvalidations = new HashSet<>();
+ protected Map<String, RealmModel> managedRealms = new HashMap<>();
+ protected Map<String, ClientModel> managedApplications = new HashMap<>();
+ protected Map<String, ClientTemplateModel> managedClientTemplates = new HashMap<>();
+ protected Map<String, RoleModel> managedRoles = new HashMap<>();
+ protected Map<String, GroupModel> managedGroups = new HashMap<>();
+
+ protected boolean clearAll;
+
+ public RevisionedCacheRealmProvider(RevisionedRealmCache cache, KeycloakSession session) {
+ this.cache = cache;
+ this.session = session;
+
+ session.getTransaction().enlistAfterCompletion(getTransaction());
+ }
+
+ @Override
+ public void clear() {
+ cache.clear();
+ }
+
+ @Override
+ public MigrationModel getMigrationModel() {
+ return getDelegate().getMigrationModel();
+ }
+
+ @Override
+ public RealmProvider getDelegate() {
+ if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction");
+ if (delegate != null) return delegate;
+ delegate = session.getProvider(RealmProvider.class);
+ return delegate;
+ }
+
+ @Override
+ public void registerRealmInvalidation(String id) {
+ realmInvalidations.add(id);
+ }
+
+ @Override
+ public void registerApplicationInvalidation(String id) {
+ appInvalidations.add(id);
+ }
+ @Override
+ public void registerClientTemplateInvalidation(String id) {
+ clientTemplateInvalidations.add(id);
+ }
+
+ @Override
+ public void registerRoleInvalidation(String id) {
+ roleInvalidations.add(id);
+ }
+
+ @Override
+ public void registerGroupInvalidation(String id) {
+ groupInvalidations.add(id);
+
+ }
+
+ protected void runInvalidations() {
+ for (String id : realmInvalidations) {
+ cache.invalidateCachedRealmById(id);
+ }
+ for (String id : roleInvalidations) {
+ cache.invalidateRoleById(id);
+ }
+ for (String id : groupInvalidations) {
+ cache.invalidateGroupById(id);
+ }
+ for (String id : appInvalidations) {
+ cache.invalidateCachedApplicationById(id);
+ }
+ for (String id : clientTemplateInvalidations) {
+ cache.invalidateCachedClientTemplateById(id);
+ }
+ }
+
+ private KeycloakTransaction getTransaction() {
+ return new KeycloakTransaction() {
+ @Override
+ public void begin() {
+ transactionActive = true;
+ }
+
+ @Override
+ public void commit() {
+ if (delegate == null) return;
+ if (clearAll) {
+ cache.clear();
+ }
+ runInvalidations();
+ transactionActive = false;
+ }
+
+ @Override
+ public void rollback() {
+ setRollbackOnly = true;
+ runInvalidations();
+ transactionActive = false;
+ }
+
+ @Override
+ public void setRollbackOnly() {
+ setRollbackOnly = true;
+ }
+
+ @Override
+ public boolean getRollbackOnly() {
+ return setRollbackOnly;
+ }
+
+ @Override
+ public boolean isActive() {
+ return transactionActive;
+ }
+ };
+ }
+
+ @Override
+ public RealmModel createRealm(String name) {
+ RealmModel realm = getDelegate().createRealm(name);
+ registerRealmInvalidation(realm.getId());
+ return realm;
+ }
+
+ @Override
+ public RealmModel createRealm(String id, String name) {
+ RealmModel realm = getDelegate().createRealm(id, name);
+ registerRealmInvalidation(realm.getId());
+ return realm;
+ }
+
+ @Override
+ public RealmModel getRealm(String id) {
+ CachedRealm cached = cache.getCachedRealm(id);
+ if (cached != null) {
+ logger.tracev("by id cache hit: {0}", cached.getName());
+ }
+ if (cached == null) {
+ Long loaded = UpdateCounter.current();
+ RealmModel model = getDelegate().getRealm(id);
+ if (model == null) return null;
+ if (realmInvalidations.contains(id)) return model;
+ cached = new RevisionedCachedRealm(loaded, cache, this, model);
+ logger.tracev("try caching realm: {0} {1}", cached.getName(), loaded);
+ cache.addCachedRealm(cached);
+ } else if (realmInvalidations.contains(id)) {
+ return getDelegate().getRealm(id);
+ } else if (managedRealms.containsKey(id)) {
+ return managedRealms.get(id);
+ }
+ RealmAdapter adapter = new RealmAdapter(cached, this);
+ managedRealms.put(id, adapter);
+ return adapter;
+ }
+
+ @Override
+ public RealmModel getRealmByName(String name) {
+ CachedRealm cached = cache.getCachedRealmByName(name);
+ if (cached != null) {
+ logger.tracev("by name cache hit: {0}", cached.getName());
+ }
+ if (cached == null) {
+ Long loaded = UpdateCounter.current();
+ RealmModel model = getDelegate().getRealmByName(name);
+ if (model == null) return null;
+ if (realmInvalidations.contains(model.getId())) return model;
+ cached = new RevisionedCachedRealm(loaded, cache, this, model);
+ logger.tracev("try caching realm: {0}", cached.getName());
+ cache.addCachedRealm(cached);
+ } else if (realmInvalidations.contains(cached.getId())) {
+ return getDelegate().getRealmByName(name);
+ } else if (managedRealms.containsKey(cached.getId())) {
+ return managedRealms.get(cached.getId());
+ }
+ RealmAdapter adapter = new RealmAdapter(cached, this);
+ managedRealms.put(cached.getId(), adapter);
+ return adapter;
+ }
+
+ @Override
+ public List<RealmModel> getRealms() {
+ // Retrieve realms from backend
+ List<RealmModel> backendRealms = getDelegate().getRealms();
+
+ // Return cache delegates to ensure cache invalidated during write operations
+ List<RealmModel> cachedRealms = new LinkedList<RealmModel>();
+ for (RealmModel realm : backendRealms) {
+ RealmModel cached = getRealm(realm.getId());
+ cachedRealms.add(cached);
+ }
+ return cachedRealms;
+ }
+
+ @Override
+ public boolean removeRealm(String id) {
+ cache.invalidateCachedRealmById(id);
+
+ RealmModel realm = getDelegate().getRealm(id);
+ Set<RoleModel> realmRoles = null;
+ if (realm != null) {
+ realmRoles = realm.getRoles();
+ }
+
+ boolean didIt = getDelegate().removeRealm(id);
+ realmInvalidations.add(id);
+
+ // TODO: Temporary workaround to invalidate cached realm roles
+ if (didIt && realmRoles != null) {
+ for (RoleModel role : realmRoles) {
+ roleInvalidations.add(role.getId());
+ }
+ }
+
+ return didIt;
+ }
+
+ @Override
+ public void close() {
+ if (delegate != null) delegate.close();
+ }
+
+ @Override
+ public RoleModel getRoleById(String id, RealmModel realm) {
+ CachedRole cached = cache.getRole(id);
+ if (cached != null && !cached.getRealm().equals(realm.getId())) {
+ cached = null;
+ }
+
+ if (cached == null) {
+ Long loaded = UpdateCounter.current();
+ RoleModel model = getDelegate().getRoleById(id, realm);
+ if (model == null) return null;
+ if (roleInvalidations.contains(id)) return model;
+ if (model.getContainer() instanceof ClientModel) {
+ cached = new RevisionedCachedClientRole(loaded, ((ClientModel) model.getContainer()).getId(), model, realm);
+ } else {
+ cached = new RevisionedCachedRealmRole(loaded, model, realm);
+ }
+ cache.addCachedRole(cached);
+
+ } else if (roleInvalidations.contains(id)) {
+ return getDelegate().getRoleById(id, realm);
+ } else if (managedRoles.containsKey(id)) {
+ return managedRoles.get(id);
+ }
+ RoleAdapter adapter = new RoleAdapter(cached, cache, this, realm);
+ managedRoles.put(id, adapter);
+ return adapter;
+ }
+
+ @Override
+ public GroupModel getGroupById(String id, RealmModel realm) {
+ CachedGroup cached = cache.getGroup(id);
+ if (cached != null && !cached.getRealm().equals(realm.getId())) {
+ cached = null;
+ }
+
+ if (cached == null) {
+ Long loaded = UpdateCounter.current();
+ GroupModel model = getDelegate().getGroupById(id, realm);
+ if (model == null) return null;
+ if (groupInvalidations.contains(id)) return model;
+ cached = new RevisionedCachedGroup(loaded, realm, model);
+ cache.addCachedGroup(cached);
+
+ } else if (groupInvalidations.contains(id)) {
+ return getDelegate().getGroupById(id, realm);
+ } else if (managedGroups.containsKey(id)) {
+ return managedGroups.get(id);
+ }
+ GroupAdapter adapter = new GroupAdapter(cached, this, session, realm);
+ managedGroups.put(id, adapter);
+ return adapter;
+ }
+
+ @Override
+ public ClientModel getClientById(String id, RealmModel realm) {
+ CachedClient cached = cache.getApplication(id);
+ if (cached != null && !cached.getRealm().equals(realm.getId())) {
+ cached = null;
+ }
+ if (cached != null) {
+ logger.tracev("client by id cache hit: {0}", cached.getClientId());
+ }
+
+ if (cached == null) {
+ Long loaded = UpdateCounter.current();
+ ClientModel model = getDelegate().getClientById(id, realm);
+ if (model == null) return null;
+ if (appInvalidations.contains(id)) return model;
+ cached = new RevisionedCachedClient(loaded, cache, getDelegate(), realm, model);
+ cache.addCachedClient(cached);
+ } else if (appInvalidations.contains(id)) {
+ return getDelegate().getClientById(id, realm);
+ } else if (managedApplications.containsKey(id)) {
+ return managedApplications.get(id);
+ }
+ ClientAdapter adapter = new ClientAdapter(realm, cached, this, cache);
+ managedApplications.put(id, adapter);
+ return adapter;
+ }
+ @Override
+ public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) {
+ CachedClientTemplate cached = cache.getClientTemplate(id);
+ if (cached != null && !cached.getRealm().equals(realm.getId())) {
+ cached = null;
+ }
+
+ if (cached == null) {
+ Long loaded = UpdateCounter.current();
+ ClientTemplateModel model = getDelegate().getClientTemplateById(id, realm);
+ if (model == null) return null;
+ if (clientTemplateInvalidations.contains(id)) return model;
+ cached = new RevisionedCachedClientTemplate(loaded, cache, getDelegate(), realm, model);
+ cache.addCachedClientTemplate(cached);
+ } else if (clientTemplateInvalidations.contains(id)) {
+ return getDelegate().getClientTemplateById(id, realm);
+ } else if (managedClientTemplates.containsKey(id)) {
+ return managedClientTemplates.get(id);
+ }
+ ClientTemplateModel adapter = new ClientTemplateAdapter(realm, cached, this, cache);
+ managedClientTemplates.put(id, adapter);
+ return adapter;
+ }
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProviderFactory.java
new file mode 100755
index 0000000..d50eb0b
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProviderFactory.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.cache.infinispan.counter;
+
+import org.infinispan.Cache;
+import org.infinispan.notifications.Listener;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntriesEvicted;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryInvalidated;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryRemoved;
+import org.infinispan.notifications.cachelistener.event.CacheEntriesEvictedEvent;
+import org.infinispan.notifications.cachelistener.event.CacheEntryCreatedEvent;
+import org.infinispan.notifications.cachelistener.event.CacheEntryInvalidatedEvent;
+import org.infinispan.notifications.cachelistener.event.CacheEntryRemovedEvent;
+import org.jboss.logging.Logger;
+import org.keycloak.Config;
+import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.cache.CacheRealmProvider;
+import org.keycloak.models.cache.CacheRealmProviderFactory;
+import org.keycloak.models.cache.entities.CachedClient;
+import org.keycloak.models.cache.entities.CachedRealm;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RevisionedCacheRealmProviderFactory implements CacheRealmProviderFactory {
+
+ private static final Logger log = Logger.getLogger(RevisionedCacheRealmProviderFactory.class);
+
+ protected volatile RevisionedRealmCache realmCache;
+
+ protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<>();
+
+ @Override
+ public CacheRealmProvider create(KeycloakSession session) {
+ lazyInit(session);
+ return new RevisionedCacheRealmProvider(realmCache, session);
+ }
+
+ private void lazyInit(KeycloakSession session) {
+ if (realmCache == null) {
+ synchronized (this) {
+ if (realmCache == null) {
+ Cache<String, Object> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
+ Cache<String, Long> counterCache = session.getProvider(InfinispanConnectionProvider.class).getCache(RevisionedConnectionProviderFactory.VERSION_CACHE_NAME);
+ cache.addListener(new CacheListener());
+ realmCache = new RevisionedRealmCache(cache, counterCache, realmLookup);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public String getId() {
+ return "infinispan-revisioned";
+ }
+
+ @Listener
+ public class CacheListener {
+
+ @CacheEntryCreated
+ public void created(CacheEntryCreatedEvent<String, Object> event) {
+ if (!event.isPre()) {
+ Object object = event.getValue();
+ if (object != null) {
+ if (object instanceof CachedRealm) {
+ CachedRealm realm = (CachedRealm) object;
+ realmLookup.put(realm.getName(), realm.getId());
+ log.tracev("Realm added realm={0}", realm.getName());
+ }
+ }
+ }
+ }
+
+ @CacheEntryRemoved
+ public void removed(CacheEntryRemovedEvent<String, Object> event) {
+ if (event.isPre()) {
+ Object object = event.getValue();
+ if (object != null) {
+ remove(object);
+ }
+ }
+ }
+
+ @CacheEntryInvalidated
+ public void removed(CacheEntryInvalidatedEvent<String, Object> event) {
+ if (event.isPre()) {
+ Object object = event.getValue();
+ if (object != null) {
+ remove(object);
+ }
+ }
+ }
+
+ @CacheEntriesEvicted
+ public void userEvicted(CacheEntriesEvictedEvent<String, Object> event) {
+ for (Object object : event.getEntries().values()) {
+ remove(object);
+ }
+ }
+
+ private void remove(Object object) {
+ if (object instanceof CachedRealm) {
+ CachedRealm realm = (CachedRealm) object;
+
+ realmLookup.remove(realm.getName());
+
+ for (String r : realm.getRealmRoles().values()) {
+ realmCache.evictCachedRoleById(r);
+ }
+
+ for (String c : realm.getClients().values()) {
+ realmCache.evictCachedApplicationById(c);
+ }
+
+ log.tracev("Realm removed realm={0}", realm.getName());
+ } else if (object instanceof CachedClient) {
+ CachedClient client = (CachedClient) object;
+
+ for (String r : client.getRoles().values()) {
+ realmCache.evictCachedRoleById(r);
+ }
+
+ log.tracev("Client removed client={0}", client.getId());
+ }
+ }
+ }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedConnectionProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedConnectionProviderFactory.java
new file mode 100755
index 0000000..845a907
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedConnectionProviderFactory.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.cache.infinispan.counter;
+
+import org.infinispan.configuration.cache.Configuration;
+import org.infinispan.configuration.cache.ConfigurationBuilder;
+import org.jboss.logging.Logger;
+import org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RevisionedConnectionProviderFactory extends DefaultInfinispanConnectionProviderFactory {
+ public static final String VERSION_CACHE_NAME = "realmVersions";
+
+ protected static final Logger logger = Logger.getLogger(RevisionedConnectionProviderFactory.class);
+
+ @Override
+ public String getId() {
+ return "revisioned";
+ }
+
+
+ protected void initEmbedded() {
+ super.initEmbedded();
+ ConfigurationBuilder counterConfigBuilder = new ConfigurationBuilder();
+ Configuration counterCacheConfiguration = counterConfigBuilder.build();
+
+ cacheManager.defineConfiguration(VERSION_CACHE_NAME, counterCacheConfiguration);
+ }
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedRealmCache.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedRealmCache.java
new file mode 100755
index 0000000..f32836e
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedRealmCache.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.cache.infinispan.counter;
+
+import org.infinispan.Cache;
+import org.jboss.logging.Logger;
+import org.keycloak.models.cache.RealmCache;
+import org.keycloak.models.cache.entities.CachedClient;
+import org.keycloak.models.cache.entities.CachedClientTemplate;
+import org.keycloak.models.cache.entities.CachedGroup;
+import org.keycloak.models.cache.entities.CachedRealm;
+import org.keycloak.models.cache.entities.CachedRole;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RevisionedRealmCache implements RealmCache {
+
+ protected static final Logger logger = Logger.getLogger(RevisionedRealmCache.class);
+
+ protected final Cache<String, Long> revisions;
+ protected final Cache<String, Object> cache;
+
+ protected final ConcurrentHashMap<String, String> realmLookup;
+
+ public RevisionedRealmCache(Cache<String, Object> cache, Cache<String, Long> revisions, ConcurrentHashMap<String, String> realmLookup) {
+ this.cache = cache;
+ this.realmLookup = realmLookup;
+ this.revisions = revisions;
+ }
+
+ public Cache<String, Object> getCache() {
+ return cache;
+ }
+
+ private <T> T get(String id, Class<T> type) {
+ Revisioned o = (Revisioned)cache.get(id);
+ if (o == null) {
+ return null;
+ }
+ Long rev = revisions.get(id);
+ if (rev == null) {
+ logger.tracev("get() missing rev");
+ return null;
+ }
+ long oRev = o.getRevision() == null ? -1L : o.getRevision().longValue();
+ if (rev > oRev) {
+ logger.tracev("stale rev: {0} o.rev: {1}", rev.longValue(), oRev);
+ return null;
+ }
+ return o != null && type.isInstance(o) ? type.cast(o) : null;
+ }
+
+ protected Object invalidateObject(String id) {
+ Object removed = cache.remove(id);
+ revisions.put(id, UpdateCounter.next());
+ return removed;
+ }
+
+ protected void addRevisioned(String id, Revisioned object) {
+ Long rev = revisions.get(id);
+ if (rev == null) {
+ logger.tracev("rev was null in addRevisioned, adding one");
+ rev = UpdateCounter.next();
+ revisions.put(id, rev);
+ return;
+ }
+ cache.putForExternalRead(id, object);
+ }
+
+
+
+
+
+
+
+ @Override
+ public void clear() {
+ cache.clear();
+ }
+
+ @Override
+ public CachedRealm getCachedRealm(String id) {
+ return get(id, CachedRealm.class);
+ }
+
+ @Override
+ public void invalidateCachedRealm(CachedRealm realm) {
+ logger.tracev("Invalidating realm {0}", realm.getId());
+ invalidateObject(realm.getId());
+ realmLookup.remove(realm.getName());
+ }
+
+ @Override
+ public void invalidateCachedRealmById(String id) {
+ CachedRealm cached = (CachedRealm) invalidateObject(id);
+ if (cached != null) realmLookup.remove(cached.getName());
+ }
+
+ @Override
+ public void addCachedRealm(CachedRealm realm) {
+ logger.tracev("Adding realm {0}", realm.getId());
+ addRevisioned(realm.getId(), (Revisioned) realm);
+ realmLookup.put(realm.getName(), realm.getId());
+ }
+
+ @Override
+ public CachedRealm getCachedRealmByName(String name) {
+ String id = realmLookup.get(name);
+ return id != null ? getCachedRealm(id) : null;
+ }
+
+ @Override
+ public CachedClient getApplication(String id) {
+ return get(id, CachedClient.class);
+ }
+
+ @Override
+ public void invalidateApplication(CachedClient app) {
+ logger.tracev("Removing application {0}", app.getId());
+ invalidateObject(app.getId());
+ }
+
+ @Override
+ public void addCachedClient(CachedClient app) {
+ logger.tracev("Adding application {0}", app.getId());
+ addRevisioned(app.getId(), (Revisioned) app);
+ }
+
+ @Override
+ public void invalidateCachedApplicationById(String id) {
+ CachedClient client = (CachedClient)invalidateObject(id);
+ if (client != null) logger.tracev("Removing application {0}", client.getClientId());
+ }
+
+ @Override
+ public void evictCachedApplicationById(String id) {
+ logger.tracev("Evicting application {0}", id);
+ cache.evict(id);
+ }
+
+ @Override
+ public CachedGroup getGroup(String id) {
+ return get(id, CachedGroup.class);
+ }
+
+ @Override
+ public void invalidateGroup(CachedGroup role) {
+ logger.tracev("Removing group {0}", role.getId());
+ invalidateObject(role.getId());
+ }
+
+ @Override
+ public void addCachedGroup(CachedGroup role) {
+ logger.tracev("Adding group {0}", role.getId());
+ addRevisioned(role.getId(), (Revisioned) role);
+ }
+
+ @Override
+ public void invalidateCachedGroupById(String id) {
+ logger.tracev("Removing group {0}", id);
+ invalidateObject(id);
+
+ }
+
+ @Override
+ public void invalidateGroupById(String id) {
+ logger.tracev("Removing group {0}", id);
+ invalidateObject(id);
+ }
+
+ @Override
+ public CachedRole getRole(String id) {
+ return get(id, CachedRole.class);
+ }
+
+ @Override
+ public void invalidateRole(CachedRole role) {
+ logger.tracev("Removing role {0}", role.getId());
+ invalidateObject(role.getId());
+ }
+
+ @Override
+ public void invalidateRoleById(String id) {
+ logger.tracev("Removing role {0}", id);
+ invalidateObject(id);
+ }
+
+ @Override
+ public void evictCachedRoleById(String id) {
+ logger.tracev("Evicting role {0}", id);
+ cache.evict(id);
+ }
+
+ @Override
+ public void addCachedRole(CachedRole role) {
+ logger.tracev("Adding role {0}", role.getId());
+ addRevisioned(role.getId(), (Revisioned) role);
+ }
+
+ @Override
+ public void invalidateCachedRoleById(String id) {
+ logger.tracev("Removing role {0}", id);
+ invalidateObject(id);
+ }
+
+ @Override
+ public CachedClientTemplate getClientTemplate(String id) {
+ return get(id, CachedClientTemplate.class);
+ }
+
+ @Override
+ public void invalidateClientTemplate(CachedClientTemplate app) {
+ logger.tracev("Removing client template {0}", app.getId());
+ invalidateObject(app.getId());
+ }
+
+ @Override
+ public void addCachedClientTemplate(CachedClientTemplate app) {
+ logger.tracev("Adding client template {0}", app.getId());
+ addRevisioned(app.getId(), (Revisioned) app);
+ }
+
+ @Override
+ public void invalidateCachedClientTemplateById(String id) {
+ logger.tracev("Removing client template {0}", id);
+ invalidateObject(id);
+ }
+
+ @Override
+ public void evictCachedClientTemplateById(String id) {
+ logger.tracev("Evicting client template {0}", id);
+ cache.evict(id);
+ }
+
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/UpdateCounter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/UpdateCounter.java
new file mode 100755
index 0000000..88d598b
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/UpdateCounter.java
@@ -0,0 +1,20 @@
+package org.keycloak.models.cache.infinispan.counter;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class UpdateCounter {
+
+ private static final AtomicLong counter = new AtomicLong();
+
+ public static long current() {
+ return counter.get();
+ }
+
+ public static long next() {
+ return counter.incrementAndGet();
+ }
+
+}
\ No newline at end of file
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java
new file mode 100755
index 0000000..ea451b0
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java
@@ -0,0 +1,463 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.cache.infinispan.locking;
+
+import org.jboss.logging.Logger;
+import org.keycloak.migration.MigrationModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientTemplateModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakTransaction;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RealmProvider;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.cache.CacheRealmProvider;
+import org.keycloak.models.cache.entities.CachedClient;
+import org.keycloak.models.cache.entities.CachedClientTemplate;
+import org.keycloak.models.cache.entities.CachedGroup;
+import org.keycloak.models.cache.entities.CachedRealm;
+import org.keycloak.models.cache.entities.CachedRole;
+import org.keycloak.models.cache.infinispan.ClientAdapter;
+import org.keycloak.models.cache.infinispan.ClientTemplateAdapter;
+import org.keycloak.models.cache.infinispan.GroupAdapter;
+import org.keycloak.models.cache.infinispan.RealmAdapter;
+import org.keycloak.models.cache.infinispan.RoleAdapter;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClient;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClientRole;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClientTemplate;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedGroup;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedRealm;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedRealmRole;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class LockingCacheRealmProvider implements CacheRealmProvider {
+ protected static final Logger logger = Logger.getLogger(LockingCacheRealmProvider.class);
+ protected LockingRealmCache cache;
+ protected KeycloakSession session;
+ protected RealmProvider delegate;
+ protected boolean transactionActive;
+ protected boolean setRollbackOnly;
+
+ protected Set<String> realmInvalidations = new HashSet<>();
+ protected Set<String> appInvalidations = new HashSet<>();
+ protected Set<String> clientTemplateInvalidations = new HashSet<>();
+ protected Set<String> roleInvalidations = new HashSet<>();
+ protected Set<String> groupInvalidations = new HashSet<>();
+ protected Map<String, RealmModel> managedRealms = new HashMap<>();
+ protected Map<String, ClientModel> managedApplications = new HashMap<>();
+ protected Map<String, ClientTemplateModel> managedClientTemplates = new HashMap<>();
+ protected Map<String, RoleModel> managedRoles = new HashMap<>();
+ protected Map<String, GroupModel> managedGroups = new HashMap<>();
+
+ protected boolean clearAll;
+
+ public LockingCacheRealmProvider(LockingRealmCache cache, KeycloakSession session) {
+ this.cache = cache;
+ this.session = session;
+
+ session.getTransaction().enlistPrepare(getPrepareTransaction());
+ session.getTransaction().enlistAfterCompletion(getAfterTransaction());
+ }
+
+ @Override
+ public void clear() {
+ cache.clear();
+ }
+
+ @Override
+ public MigrationModel getMigrationModel() {
+ return getDelegate().getMigrationModel();
+ }
+
+ @Override
+ public RealmProvider getDelegate() {
+ if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction");
+ if (delegate != null) return delegate;
+ delegate = session.getProvider(RealmProvider.class);
+ return delegate;
+ }
+
+ @Override
+ public void registerRealmInvalidation(String id) {
+ realmInvalidations.add(id);
+ }
+
+ @Override
+ public void registerApplicationInvalidation(String id) {
+ appInvalidations.add(id);
+ }
+ @Override
+ public void registerClientTemplateInvalidation(String id) {
+ clientTemplateInvalidations.add(id);
+ }
+
+ @Override
+ public void registerRoleInvalidation(String id) {
+ roleInvalidations.add(id);
+ }
+
+ @Override
+ public void registerGroupInvalidation(String id) {
+ groupInvalidations.add(id);
+
+ }
+
+ protected void runInvalidations() {
+ for (String id : realmInvalidations) {
+ cache.invalidateCachedRealmById(id);
+ }
+ for (String id : roleInvalidations) {
+ cache.invalidateRoleById(id);
+ }
+ for (String id : groupInvalidations) {
+ cache.invalidateGroupById(id);
+ }
+ for (String id : appInvalidations) {
+ cache.invalidateCachedApplicationById(id);
+ }
+ for (String id : clientTemplateInvalidations) {
+ cache.invalidateCachedClientTemplateById(id);
+ }
+ }
+
+ private KeycloakTransaction getPrepareTransaction() {
+ return new KeycloakTransaction() {
+ @Override
+ public void begin() {
+ transactionActive = true;
+ }
+
+ @Override
+ public void commit() {
+ if (delegate == null) return;
+ List<String> invalidates = new LinkedList<>();
+ for (String id : realmInvalidations) {
+ invalidates.add(id);
+ }
+ for (String id : roleInvalidations) {
+ invalidates.add(id);
+ }
+ for (String id : groupInvalidations) {
+ invalidates.add(id);
+ }
+ for (String id : appInvalidations) {
+ invalidates.add(id);
+ }
+ for (String id : clientTemplateInvalidations) {
+ invalidates.add(id);
+ }
+
+ Collections.sort(invalidates); // lock ordering
+ cache.getRevisions().startBatch();
+ for (String id : invalidates) {
+ cache.getRevisions().getAdvancedCache().lock(id);
+ }
+
+ }
+
+ @Override
+ public void rollback() {
+ setRollbackOnly = true;
+ transactionActive = false;
+ }
+
+ @Override
+ public void setRollbackOnly() {
+ setRollbackOnly = true;
+ }
+
+ @Override
+ public boolean getRollbackOnly() {
+ return setRollbackOnly;
+ }
+
+ @Override
+ public boolean isActive() {
+ return transactionActive;
+ }
+ };
+ }
+
+ private KeycloakTransaction getAfterTransaction() {
+ return new KeycloakTransaction() {
+ @Override
+ public void begin() {
+ transactionActive = true;
+ }
+
+ @Override
+ public void commit() {
+ try {
+ if (delegate == null) return;
+ if (clearAll) {
+ cache.clear();
+ }
+ runInvalidations();
+ transactionActive = false;
+ } finally {
+ cache.endRevisionBatch();
+ }
+ }
+
+ @Override
+ public void rollback() {
+ try {
+ setRollbackOnly = true;
+ runInvalidations();
+ transactionActive = false;
+ } finally {
+ cache.endRevisionBatch();
+ }
+ }
+
+ @Override
+ public void setRollbackOnly() {
+ setRollbackOnly = true;
+ }
+
+ @Override
+ public boolean getRollbackOnly() {
+ return setRollbackOnly;
+ }
+
+ @Override
+ public boolean isActive() {
+ return transactionActive;
+ }
+ };
+ }
+
+ @Override
+ public RealmModel createRealm(String name) {
+ RealmModel realm = getDelegate().createRealm(name);
+ registerRealmInvalidation(realm.getId());
+ return realm;
+ }
+
+ @Override
+ public RealmModel createRealm(String id, String name) {
+ RealmModel realm = getDelegate().createRealm(id, name);
+ registerRealmInvalidation(realm.getId());
+ return realm;
+ }
+
+ @Override
+ public RealmModel getRealm(String id) {
+ CachedRealm cached = cache.getCachedRealm(id);
+ if (cached != null) {
+ logger.tracev("by id cache hit: {0}", cached.getName());
+ }
+ if (cached == null) {
+ Long loaded = cache.getCurrentRevision(id);
+ RealmModel model = getDelegate().getRealm(id);
+ if (model == null) return null;
+ if (realmInvalidations.contains(id)) return model;
+ cached = new RevisionedCachedRealm(loaded, cache, this, model);
+ cache.addCachedRealm(cached);
+ } else if (realmInvalidations.contains(id)) {
+ return getDelegate().getRealm(id);
+ } else if (managedRealms.containsKey(id)) {
+ return managedRealms.get(id);
+ }
+ RealmAdapter adapter = new RealmAdapter(cached, this);
+ managedRealms.put(id, adapter);
+ return adapter;
+ }
+
+ @Override
+ public RealmModel getRealmByName(String name) {
+ CachedRealm cached = cache.getCachedRealmByName(name);
+ if (cached != null) {
+ logger.tracev("by name cache hit: {0}", cached.getName());
+ }
+ if (cached == null) {
+ RealmModel model = getDelegate().getRealmByName(name);
+ if (model == null) return null;
+ if (realmInvalidations.contains(model.getId())) return model;
+ cached = new RevisionedCachedRealm(null, cache, this, model);
+ cache.addCachedRealm(cached);
+ } else if (realmInvalidations.contains(cached.getId())) {
+ return getDelegate().getRealmByName(name);
+ } else if (managedRealms.containsKey(cached.getId())) {
+ return managedRealms.get(cached.getId());
+ }
+ RealmAdapter adapter = new RealmAdapter(cached, this);
+ managedRealms.put(cached.getId(), adapter);
+ return adapter;
+ }
+
+ @Override
+ public List<RealmModel> getRealms() {
+ // Retrieve realms from backend
+ List<RealmModel> backendRealms = getDelegate().getRealms();
+
+ // Return cache delegates to ensure cache invalidated during write operations
+ List<RealmModel> cachedRealms = new LinkedList<RealmModel>();
+ for (RealmModel realm : backendRealms) {
+ RealmModel cached = getRealm(realm.getId());
+ cachedRealms.add(cached);
+ }
+ return cachedRealms;
+ }
+
+ @Override
+ public boolean removeRealm(String id) {
+ cache.invalidateCachedRealmById(id);
+
+ RealmModel realm = getDelegate().getRealm(id);
+ Set<RoleModel> realmRoles = null;
+ if (realm != null) {
+ realmRoles = realm.getRoles();
+ }
+
+ boolean didIt = getDelegate().removeRealm(id);
+ realmInvalidations.add(id);
+
+ // TODO: Temporary workaround to invalidate cached realm roles
+ if (didIt && realmRoles != null) {
+ for (RoleModel role : realmRoles) {
+ roleInvalidations.add(role.getId());
+ }
+ }
+
+ return didIt;
+ }
+
+ @Override
+ public void close() {
+ if (delegate != null) delegate.close();
+ }
+
+ @Override
+ public RoleModel getRoleById(String id, RealmModel realm) {
+ CachedRole cached = cache.getRole(id);
+ if (cached != null && !cached.getRealm().equals(realm.getId())) {
+ cached = null;
+ }
+
+ if (cached == null) {
+ Long loaded = cache.getCurrentRevision(id);
+ RoleModel model = getDelegate().getRoleById(id, realm);
+ if (model == null) return null;
+ if (roleInvalidations.contains(id)) return model;
+ if (model.getContainer() instanceof ClientModel) {
+ cached = new RevisionedCachedClientRole(loaded, ((ClientModel) model.getContainer()).getId(), model, realm);
+ } else {
+ cached = new RevisionedCachedRealmRole(loaded, model, realm);
+ }
+ cache.addCachedRole(cached);
+
+ } else if (roleInvalidations.contains(id)) {
+ return getDelegate().getRoleById(id, realm);
+ } else if (managedRoles.containsKey(id)) {
+ return managedRoles.get(id);
+ }
+ RoleAdapter adapter = new RoleAdapter(cached, cache, this, realm);
+ managedRoles.put(id, adapter);
+ return adapter;
+ }
+
+ @Override
+ public GroupModel getGroupById(String id, RealmModel realm) {
+ CachedGroup cached = cache.getGroup(id);
+ if (cached != null && !cached.getRealm().equals(realm.getId())) {
+ cached = null;
+ }
+
+ if (cached == null) {
+ Long loaded = cache.getCurrentRevision(id);
+ GroupModel model = getDelegate().getGroupById(id, realm);
+ if (model == null) return null;
+ if (groupInvalidations.contains(id)) return model;
+ cached = new RevisionedCachedGroup(loaded, realm, model);
+ cache.addCachedGroup(cached);
+
+ } else if (groupInvalidations.contains(id)) {
+ return getDelegate().getGroupById(id, realm);
+ } else if (managedGroups.containsKey(id)) {
+ return managedGroups.get(id);
+ }
+ GroupAdapter adapter = new GroupAdapter(cached, this, session, realm);
+ managedGroups.put(id, adapter);
+ return adapter;
+ }
+
+ @Override
+ public ClientModel getClientById(String id, RealmModel realm) {
+ CachedClient cached = cache.getApplication(id);
+ if (cached != null && !cached.getRealm().equals(realm.getId())) {
+ cached = null;
+ }
+ if (cached != null && cached.getClientId().equals("client")) {
+ logger.tracev("client by id cache hit: {0}", cached.getClientId());
+ }
+
+ if (cached == null) {
+ Long loaded = cache.getCurrentRevision(id);
+ ClientModel model = getDelegate().getClientById(id, realm);
+ if (model == null) return null;
+ if (appInvalidations.contains(id)) return model;
+ cached = new RevisionedCachedClient(loaded, cache, getDelegate(), realm, model);
+ cache.addCachedClient(cached);
+ } else if (appInvalidations.contains(id)) {
+ return getDelegate().getClientById(id, realm);
+ } else if (managedApplications.containsKey(id)) {
+ return managedApplications.get(id);
+ }
+ ClientAdapter adapter = new ClientAdapter(realm, cached, this, cache);
+ managedApplications.put(id, adapter);
+ return adapter;
+ }
+ @Override
+ public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) {
+ CachedClientTemplate cached = cache.getClientTemplate(id);
+ if (cached != null && !cached.getRealm().equals(realm.getId())) {
+ cached = null;
+ }
+
+ if (cached == null) {
+ Long loaded = cache.getCurrentRevision(id);
+ ClientTemplateModel model = getDelegate().getClientTemplateById(id, realm);
+ if (model == null) return null;
+ if (clientTemplateInvalidations.contains(id)) return model;
+ cached = new RevisionedCachedClientTemplate(loaded, cache, getDelegate(), realm, model);
+ cache.addCachedClientTemplate(cached);
+ } else if (clientTemplateInvalidations.contains(id)) {
+ return getDelegate().getClientTemplateById(id, realm);
+ } else if (managedClientTemplates.containsKey(id)) {
+ return managedClientTemplates.get(id);
+ }
+ ClientTemplateModel adapter = new ClientTemplateAdapter(realm, cached, this, cache);
+ managedClientTemplates.put(id, adapter);
+ return adapter;
+ }
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProviderFactory.java
new file mode 100755
index 0000000..f143ea3
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProviderFactory.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.cache.infinispan.locking;
+
+import org.infinispan.Cache;
+import org.infinispan.notifications.Listener;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntriesEvicted;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryInvalidated;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryRemoved;
+import org.infinispan.notifications.cachelistener.event.CacheEntriesEvictedEvent;
+import org.infinispan.notifications.cachelistener.event.CacheEntryCreatedEvent;
+import org.infinispan.notifications.cachelistener.event.CacheEntryInvalidatedEvent;
+import org.infinispan.notifications.cachelistener.event.CacheEntryRemovedEvent;
+import org.jboss.logging.Logger;
+import org.keycloak.Config;
+import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.cache.CacheRealmProvider;
+import org.keycloak.models.cache.CacheRealmProviderFactory;
+import org.keycloak.models.cache.entities.CachedClient;
+import org.keycloak.models.cache.entities.CachedRealm;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LockingCacheRealmProviderFactory implements CacheRealmProviderFactory {
+
+ private static final Logger log = Logger.getLogger(LockingCacheRealmProviderFactory.class);
+
+ protected volatile LockingRealmCache realmCache;
+
+ protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<>();
+
+ @Override
+ public CacheRealmProvider create(KeycloakSession session) {
+ lazyInit(session);
+ return new LockingCacheRealmProvider(realmCache, session);
+ }
+
+ private void lazyInit(KeycloakSession session) {
+ if (realmCache == null) {
+ synchronized (this) {
+ if (realmCache == null) {
+ Cache<String, Object> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
+ Cache<String, Long> counterCache = session.getProvider(InfinispanConnectionProvider.class).getCache(LockingConnectionProviderFactory.VERSION_CACHE_NAME);
+ cache.addListener(new CacheListener());
+ realmCache = new LockingRealmCache(cache, counterCache, realmLookup);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public String getId() {
+ return "infinispan-locking";
+ }
+
+ @Listener
+ public class CacheListener {
+
+ @CacheEntryCreated
+ public void created(CacheEntryCreatedEvent<String, Object> event) {
+ if (!event.isPre()) {
+ Object object = event.getValue();
+ if (object != null) {
+ if (object instanceof CachedRealm) {
+ CachedRealm realm = (CachedRealm) object;
+ realmLookup.put(realm.getName(), realm.getId());
+ log.tracev("Realm added realm={0}", realm.getName());
+ }
+ }
+ }
+ }
+
+ @CacheEntryRemoved
+ public void removed(CacheEntryRemovedEvent<String, Object> event) {
+ if (event.isPre()) {
+ Object object = event.getValue();
+ if (object != null) {
+ remove(object);
+ }
+ }
+ }
+
+ @CacheEntryInvalidated
+ public void removed(CacheEntryInvalidatedEvent<String, Object> event) {
+ if (event.isPre()) {
+ Object object = event.getValue();
+ if (object != null) {
+ remove(object);
+ }
+ }
+ }
+
+ @CacheEntriesEvicted
+ public void userEvicted(CacheEntriesEvictedEvent<String, Object> event) {
+ for (Object object : event.getEntries().values()) {
+ remove(object);
+ }
+ }
+
+ private void remove(Object object) {
+ if (object instanceof CachedRealm) {
+ CachedRealm realm = (CachedRealm) object;
+
+ realmLookup.remove(realm.getName());
+
+ for (String r : realm.getRealmRoles().values()) {
+ realmCache.evictCachedRoleById(r);
+ }
+
+ for (String c : realm.getClients().values()) {
+ realmCache.evictCachedApplicationById(c);
+ }
+
+ log.tracev("Realm removed realm={0}", realm.getName());
+ } else if (object instanceof CachedClient) {
+ CachedClient client = (CachedClient) object;
+
+ for (String r : client.getRoles().values()) {
+ realmCache.evictCachedRoleById(r);
+ }
+
+ log.tracev("Client removed client={0}", client.getId());
+ }
+ }
+ }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingConnectionProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingConnectionProviderFactory.java
new file mode 100755
index 0000000..68da34d
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingConnectionProviderFactory.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.cache.infinispan.locking;
+
+import org.infinispan.configuration.cache.Configuration;
+import org.infinispan.configuration.cache.ConfigurationBuilder;
+import org.infinispan.transaction.LockingMode;
+import org.infinispan.transaction.TransactionMode;
+import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
+import org.jboss.logging.Logger;
+import org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LockingConnectionProviderFactory extends DefaultInfinispanConnectionProviderFactory {
+ public static final String VERSION_CACHE_NAME = "realmVersions";
+
+ protected static final Logger logger = Logger.getLogger(LockingConnectionProviderFactory.class);
+
+ @Override
+ public String getId() {
+ return "locking";
+ }
+
+
+ protected void initEmbedded() {
+ super.initEmbedded();
+ ConfigurationBuilder counterConfigBuilder = new ConfigurationBuilder();
+ counterConfigBuilder.invocationBatching().enable()
+ .transaction().transactionMode(TransactionMode.TRANSACTIONAL);
+ counterConfigBuilder.transaction().transactionManagerLookup(new DummyTransactionManagerLookup());
+ counterConfigBuilder.transaction().lockingMode(LockingMode.PESSIMISTIC);
+ Configuration counterCacheConfiguration = counterConfigBuilder.build();
+
+ cacheManager.defineConfiguration(VERSION_CACHE_NAME, counterCacheConfiguration);
+ }
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java
new file mode 100755
index 0000000..9c8e784
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.cache.infinispan.locking;
+
+import org.infinispan.Cache;
+import org.jboss.logging.Logger;
+import org.keycloak.models.cache.RealmCache;
+import org.keycloak.models.cache.entities.CachedClient;
+import org.keycloak.models.cache.entities.CachedClientTemplate;
+import org.keycloak.models.cache.entities.CachedGroup;
+import org.keycloak.models.cache.entities.CachedRealm;
+import org.keycloak.models.cache.entities.CachedRole;
+import org.keycloak.models.cache.infinispan.counter.Revisioned;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LockingRealmCache implements RealmCache {
+
+ protected static final Logger logger = Logger.getLogger(LockingRealmCache.class);
+
+ protected final Cache<String, Long> revisions;
+ protected final Cache<String, Object> cache;
+ final AtomicLong realmCounter = new AtomicLong();
+ final AtomicLong clientCounter = new AtomicLong();
+ final AtomicLong clientTemplateCounter = new AtomicLong();
+ final AtomicLong roleCounter = new AtomicLong();
+ final AtomicLong groupCounter = new AtomicLong();
+
+ protected final ConcurrentHashMap<String, String> realmLookup;
+
+ public LockingRealmCache(Cache<String, Object> cache, Cache<String, Long> revisions, ConcurrentHashMap<String, String> realmLookup) {
+ this.cache = cache;
+ this.realmLookup = realmLookup;
+ this.revisions = revisions;
+ }
+
+ public Cache<String, Object> getCache() {
+ return cache;
+ }
+
+ public Cache<String, Long> getRevisions() {
+ return revisions;
+ }
+
+ public void startRevisionBatch() {
+ revisions.startBatch();
+ }
+
+ public void endRevisionBatch() {
+ try {
+ revisions.endBatch(true);
+ } catch (Exception e) {
+ }
+
+ }
+
+ private <T> T get(String id, Class<T> type) {
+ Revisioned o = (Revisioned)cache.get(id);
+ if (o == null) {
+ return null;
+ }
+ Long rev = revisions.get(id);
+ if (rev == null) {
+ logger.tracev("get() missing rev");
+ return null;
+ }
+ long oRev = o.getRevision() == null ? -1L : o.getRevision().longValue();
+ if (rev > oRev) {
+ logger.tracev("get() rev: {0} o.rev: {1}", rev.longValue(), oRev);
+ return null;
+ }
+ return o != null && type.isInstance(o) ? type.cast(o) : null;
+ }
+
+ protected Object invalidateObject(String id, AtomicLong counter) {
+ Object removed = cache.remove(id);
+ revisions.put(id, counter.incrementAndGet());
+ return removed;
+ }
+
+ protected void addRevisioned(String id, Revisioned object, AtomicLong counter) {
+ //startRevisionBatch();
+ try {
+ //revisions.getAdvancedCache().lock(id);
+ Long rev = revisions.get(id);
+ if (rev == null) {
+ rev = counter.incrementAndGet();
+ revisions.put(id, rev);
+ return;
+ }
+ revisions.startBatch();
+ revisions.getAdvancedCache().lock(id);
+ rev = revisions.get(id);
+ if (rev == null) {
+ rev = counter.incrementAndGet();
+ revisions.put(id, rev);
+ return;
+ }
+ if (rev.equals(object.getRevision())) {
+ cache.putForExternalRead(id, object);
+ }
+ } finally {
+ endRevisionBatch();
+ }
+
+ }
+
+
+
+
+ public Long getCurrentRevision(String id) {
+ return revisions.get(id);
+ }
+ @Override
+ public void clear() {
+ cache.clear();
+ }
+
+ @Override
+ public CachedRealm getCachedRealm(String id) {
+ return get(id, CachedRealm.class);
+ }
+
+ @Override
+ public void invalidateCachedRealm(CachedRealm realm) {
+ logger.tracev("Invalidating realm {0}", realm.getId());
+ invalidateObject(realm.getId(), realmCounter);
+ realmLookup.remove(realm.getName());
+ }
+
+ @Override
+ public void invalidateCachedRealmById(String id) {
+ CachedRealm cached = (CachedRealm) invalidateObject(id, realmCounter);
+ if (cached != null) realmLookup.remove(cached.getName());
+ }
+
+ @Override
+ public void addCachedRealm(CachedRealm realm) {
+ logger.tracev("Adding realm {0}", realm.getId());
+ addRevisioned(realm.getId(), (Revisioned) realm, realmCounter);
+ realmLookup.put(realm.getName(), realm.getId());
+ }
+
+
+ @Override
+ public CachedRealm getCachedRealmByName(String name) {
+ String id = realmLookup.get(name);
+ return id != null ? getCachedRealm(id) : null;
+ }
+
+ @Override
+ public CachedClient getApplication(String id) {
+ return get(id, CachedClient.class);
+ }
+
+ @Override
+ public void invalidateApplication(CachedClient app) {
+ logger.tracev("Removing application {0}", app.getId());
+ invalidateObject(app.getId(), clientCounter);
+ }
+
+ @Override
+ public void addCachedClient(CachedClient app) {
+ logger.tracev("Adding application {0}", app.getId());
+ addRevisioned(app.getId(), (Revisioned) app, clientCounter);
+ }
+
+ @Override
+ public void invalidateCachedApplicationById(String id) {
+ CachedClient client = (CachedClient)invalidateObject(id, clientCounter);
+ if (client != null) logger.tracev("Removing application {0}", client.getClientId());
+ }
+
+ @Override
+ public void evictCachedApplicationById(String id) {
+ logger.tracev("Evicting application {0}", id);
+ cache.evict(id);
+ }
+
+ @Override
+ public CachedGroup getGroup(String id) {
+ return get(id, CachedGroup.class);
+ }
+
+ @Override
+ public void invalidateGroup(CachedGroup role) {
+ logger.tracev("Removing group {0}", role.getId());
+ invalidateObject(role.getId(), groupCounter);
+ }
+
+ @Override
+ public void addCachedGroup(CachedGroup role) {
+ logger.tracev("Adding group {0}", role.getId());
+ addRevisioned(role.getId(), (Revisioned) role, groupCounter);
+ }
+
+ @Override
+ public void invalidateCachedGroupById(String id) {
+ logger.tracev("Removing group {0}", id);
+ invalidateObject(id, groupCounter);
+
+ }
+
+ @Override
+ public void invalidateGroupById(String id) {
+ logger.tracev("Removing group {0}", id);
+ invalidateObject(id, groupCounter);
+ }
+
+ @Override
+ public CachedRole getRole(String id) {
+ return get(id, CachedRole.class);
+ }
+
+ @Override
+ public void invalidateRole(CachedRole role) {
+ logger.tracev("Removing role {0}", role.getId());
+ invalidateObject(role.getId(), roleCounter);
+ }
+
+ @Override
+ public void invalidateRoleById(String id) {
+ logger.tracev("Removing role {0}", id);
+ invalidateObject(id, roleCounter);
+ }
+
+ @Override
+ public void evictCachedRoleById(String id) {
+ logger.tracev("Evicting role {0}", id);
+ cache.evict(id);
+ }
+
+ @Override
+ public void addCachedRole(CachedRole role) {
+ logger.tracev("Adding role {0}", role.getId());
+ addRevisioned(role.getId(), (Revisioned) role, roleCounter);
+ }
+
+ @Override
+ public void invalidateCachedRoleById(String id) {
+ logger.tracev("Removing role {0}", id);
+ invalidateObject(id, roleCounter);
+ }
+
+ @Override
+ public CachedClientTemplate getClientTemplate(String id) {
+ return get(id, CachedClientTemplate.class);
+ }
+
+ @Override
+ public void invalidateClientTemplate(CachedClientTemplate app) {
+ logger.tracev("Removing client template {0}", app.getId());
+ invalidateObject(app.getId(), clientTemplateCounter);
+ }
+
+ @Override
+ public void addCachedClientTemplate(CachedClientTemplate app) {
+ logger.tracev("Adding client template {0}", app.getId());
+ addRevisioned(app.getId(), (Revisioned) app, clientTemplateCounter);
+ }
+
+ @Override
+ public void invalidateCachedClientTemplateById(String id) {
+ logger.tracev("Removing client template {0}", id);
+ invalidateObject(id, clientTemplateCounter);
+ }
+
+ @Override
+ public void evictCachedClientTemplateById(String id) {
+ logger.tracev("Evicting client template {0}", id);
+ cache.evict(id);
+ }
+
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewCacheRealmProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewCacheRealmProviderFactory.java
new file mode 100755
index 0000000..7dd49c5
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewCacheRealmProviderFactory.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.cache.infinispan.skewed;
+
+import org.infinispan.Cache;
+import org.infinispan.notifications.Listener;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntriesEvicted;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryInvalidated;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryRemoved;
+import org.infinispan.notifications.cachelistener.event.CacheEntriesEvictedEvent;
+import org.infinispan.notifications.cachelistener.event.CacheEntryCreatedEvent;
+import org.infinispan.notifications.cachelistener.event.CacheEntryInvalidatedEvent;
+import org.infinispan.notifications.cachelistener.event.CacheEntryRemovedEvent;
+import org.jboss.logging.Logger;
+import org.keycloak.Config;
+import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.cache.CacheRealmProvider;
+import org.keycloak.models.cache.CacheRealmProviderFactory;
+import org.keycloak.models.cache.entities.CachedClient;
+import org.keycloak.models.cache.entities.CachedRealm;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RepeatableReadWriteSkewCacheRealmProviderFactory implements CacheRealmProviderFactory {
+
+ private static final Logger log = Logger.getLogger(RepeatableReadWriteSkewCacheRealmProviderFactory.class);
+
+ protected volatile RepeatableReadWriteSkewRealmCache realmCache;
+
+ protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<>();
+
+ @Override
+ public CacheRealmProvider create(KeycloakSession session) {
+ lazyInit(session);
+ return new RepeatableReadWriteSkewRealmCacheProvider(realmCache, session);
+ }
+
+ private void lazyInit(KeycloakSession session) {
+ if (realmCache == null) {
+ synchronized (this) {
+ if (realmCache == null) {
+ Cache<String, Object> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
+ cache.addListener(new CacheListener());
+ realmCache = new RepeatableReadWriteSkewRealmCache(cache, realmLookup);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public String getId() {
+ return "infinispan-versioned";
+ }
+
+ @Listener
+ public class CacheListener {
+
+ @CacheEntryCreated
+ public void created(CacheEntryCreatedEvent<String, Object> event) {
+ if (!event.isPre()) {
+ Object object = event.getValue();
+ if (object != null) {
+ if (object instanceof CachedRealm) {
+ CachedRealm realm = (CachedRealm) object;
+ realmLookup.put(realm.getName(), realm.getId());
+ log.tracev("Realm added realm={0}", realm.getName());
+ }
+ }
+ }
+ }
+
+ @CacheEntryRemoved
+ public void removed(CacheEntryRemovedEvent<String, Object> event) {
+ if (event.isPre()) {
+ Object object = event.getValue();
+ if (object != null) {
+ remove(object);
+ }
+ }
+ }
+
+ @CacheEntryInvalidated
+ public void removed(CacheEntryInvalidatedEvent<String, Object> event) {
+ if (event.isPre()) {
+ Object object = event.getValue();
+ if (object != null) {
+ remove(object);
+ }
+ }
+ }
+
+ @CacheEntriesEvicted
+ public void userEvicted(CacheEntriesEvictedEvent<String, Object> event) {
+ for (Object object : event.getEntries().values()) {
+ remove(object);
+ }
+ }
+
+ private void remove(Object object) {
+ if (object instanceof CachedRealm) {
+ CachedRealm realm = (CachedRealm) object;
+
+ realmLookup.remove(realm.getName());
+
+ for (String r : realm.getRealmRoles().values()) {
+ realmCache.evictCachedRoleById(r);
+ }
+
+ for (String c : realm.getClients().values()) {
+ realmCache.evictCachedApplicationById(c);
+ }
+
+ log.tracev("Realm removed realm={0}", realm.getName());
+ } else if (object instanceof CachedClient) {
+ CachedClient client = (CachedClient) object;
+
+ for (String r : client.getRoles().values()) {
+ realmCache.evictCachedRoleById(r);
+ }
+
+ log.tracev("Client removed client={0}", client.getId());
+ }
+ }
+ }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewConnectionProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewConnectionProviderFactory.java
new file mode 100755
index 0000000..b389347
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewConnectionProviderFactory.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.cache.infinispan.skewed;
+
+import org.infinispan.configuration.cache.CacheMode;
+import org.infinispan.configuration.cache.Configuration;
+import org.infinispan.configuration.cache.ConfigurationBuilder;
+import org.infinispan.configuration.cache.VersioningScheme;
+import org.infinispan.configuration.global.GlobalConfigurationBuilder;
+import org.infinispan.manager.DefaultCacheManager;
+import org.infinispan.manager.EmbeddedCacheManager;
+import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
+import org.infinispan.util.concurrent.IsolationLevel;
+import org.jboss.logging.Logger;
+import org.keycloak.Config;
+import org.keycloak.connections.infinispan.DefaultInfinispanConnectionProvider;
+import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
+import org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+
+import javax.naming.InitialContext;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RepeatableReadWriteSkewConnectionProviderFactory implements InfinispanConnectionProviderFactory {
+
+ protected static final Logger logger = Logger.getLogger(RepeatableReadWriteSkewConnectionProviderFactory.class);
+
+ private Config.Scope config;
+
+ private EmbeddedCacheManager cacheManager;
+
+ private boolean containerManaged;
+
+ @Override
+ public InfinispanConnectionProvider create(KeycloakSession session) {
+ lazyInit();
+
+ return new DefaultInfinispanConnectionProvider(cacheManager);
+ }
+
+ @Override
+ public void close() {
+ if (cacheManager != null && !containerManaged) {
+ cacheManager.stop();
+ }
+ cacheManager = null;
+ }
+
+ @Override
+ public String getId() {
+ return "versioned";
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+ this.config = config;
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+ private void lazyInit() {
+ if (cacheManager == null) {
+ synchronized (this) {
+ if (cacheManager == null) {
+ String cacheContainer = config.get("cacheContainer");
+ if (cacheContainer != null) {
+ initContainerManaged(cacheContainer);
+ } else {
+ initEmbedded();
+ }
+ }
+ }
+ }
+ }
+
+ private void initContainerManaged(String cacheContainerLookup) {
+ try {
+ cacheManager = (EmbeddedCacheManager) new InitialContext().lookup(cacheContainerLookup);
+ containerManaged = true;
+
+ logger.debugv("Using container managed Infinispan cache container, lookup={1}", cacheContainerLookup);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to retrieve cache container", e);
+ }
+ }
+
+ private void initEmbedded() {
+ GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
+
+ boolean clustered = config.getBoolean("clustered", false);
+ boolean async = config.getBoolean("async", true);
+ boolean allowDuplicateJMXDomains = config.getBoolean("allowDuplicateJMXDomains", true);
+
+ if (clustered) {
+ gcb.transport().defaultTransport();
+ }
+ gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
+
+ cacheManager = new DefaultCacheManager(gcb.build());
+ containerManaged = false;
+
+ logger.debug("Started embedded Infinispan cache container");
+
+ ConfigurationBuilder invalidationConfigBuilder = new ConfigurationBuilder();
+ if (clustered) {
+ invalidationConfigBuilder.clustering().cacheMode(async ? CacheMode.INVALIDATION_ASYNC : CacheMode.INVALIDATION_SYNC);
+ }
+
+ invalidationConfigBuilder.transaction().transactionManagerLookup(new DummyTransactionManagerLookup());
+ invalidationConfigBuilder.locking().isolationLevel(IsolationLevel.REPEATABLE_READ).writeSkewCheck(true).versioning().enable().scheme(VersioningScheme.SIMPLE);
+ cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_CACHE_NAME, invalidationConfigBuilder.build());
+
+ ConfigurationBuilder userConfigBuilder = new ConfigurationBuilder();
+ if (clustered) {
+ userConfigBuilder.clustering().cacheMode(async ? CacheMode.INVALIDATION_ASYNC : CacheMode.INVALIDATION_SYNC);
+ }
+ Configuration userCacheConfiguration = userConfigBuilder.build();
+
+ cacheManager.defineConfiguration(InfinispanConnectionProvider.USER_CACHE_NAME, userCacheConfiguration);
+
+ ConfigurationBuilder sessionConfigBuilder = new ConfigurationBuilder();
+ if (clustered) {
+ String sessionsMode = config.get("sessionsMode", "distributed");
+ if (sessionsMode.equalsIgnoreCase("replicated")) {
+ sessionConfigBuilder.clustering().cacheMode(async ? CacheMode.REPL_ASYNC : CacheMode.REPL_SYNC);
+ } else if (sessionsMode.equalsIgnoreCase("distributed")) {
+ sessionConfigBuilder.clustering().cacheMode(async ? CacheMode.DIST_ASYNC : CacheMode.DIST_SYNC);
+ } else {
+ throw new RuntimeException("Invalid value for sessionsMode");
+ }
+
+ sessionConfigBuilder.clustering().hash()
+ .numOwners(config.getInt("sessionsOwners", 2))
+ .numSegments(config.getInt("sessionsSegments", 60)).build();
+ }
+
+ Configuration sessionCacheConfiguration = sessionConfigBuilder.build();
+ cacheManager.defineConfiguration(InfinispanConnectionProvider.SESSION_CACHE_NAME, sessionCacheConfiguration);
+ cacheManager.defineConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, sessionCacheConfiguration);
+ cacheManager.defineConfiguration(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, sessionCacheConfiguration);
+ }
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewRealmCache.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewRealmCache.java
new file mode 100755
index 0000000..18e5d86
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewRealmCache.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.cache.infinispan.skewed;
+
+import org.infinispan.Cache;
+import org.jboss.logging.Logger;
+import org.keycloak.models.cache.RealmCache;
+import org.keycloak.models.cache.entities.CachedClient;
+import org.keycloak.models.cache.entities.CachedClientTemplate;
+import org.keycloak.models.cache.entities.CachedGroup;
+import org.keycloak.models.cache.entities.CachedRealm;
+import org.keycloak.models.cache.entities.CachedRole;
+
+import javax.transaction.NotSupportedException;
+import javax.transaction.Status;
+import javax.transaction.SystemException;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RepeatableReadWriteSkewRealmCache implements RealmCache {
+
+ protected static final Logger logger = Logger.getLogger(RepeatableReadWriteSkewRealmCache.class);
+
+ protected final Cache<String, Object> cache;
+ protected final ConcurrentHashMap<String, String> realmLookup;
+
+ public RepeatableReadWriteSkewRealmCache(Cache<String, Object> cache, ConcurrentHashMap<String, String> realmLookup) {
+ this.cache = cache;
+ this.realmLookup = realmLookup;
+ }
+
+ public Cache<String, Object> getCache() {
+ return cache;
+ }
+
+ public void startBatch() {
+ logger.trace("*** START BATCH ***");
+ try {
+ if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_NO_TRANSACTION) {
+ cache.getAdvancedCache().getTransactionManager().begin();
+ }
+ } catch (NotSupportedException e) {
+ throw new RuntimeException(e);
+ } catch (SystemException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ public void endBatch(boolean commit) {
+ logger.trace("*** END BATCH ***");
+ try {
+ if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_ACTIVE) {
+ if (commit) {
+ cache.getAdvancedCache().getTransactionManager().commit();
+
+ } else {
+ cache.getAdvancedCache().getTransactionManager().rollback();
+
+ }
+ }
+ } catch (Exception e) {
+ //throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void clear() {
+ cache.clear();
+ }
+
+ @Override
+ public CachedRealm getCachedRealm(String id) {
+ return get(id, CachedRealm.class);
+ }
+
+ @Override
+ public void invalidateCachedRealm(CachedRealm realm) {
+ logger.tracev("Invalidating realm {0}", realm.getId());
+ invalidate(realm.getId());
+ realmLookup.remove(realm.getName());
+ }
+
+ protected Object invalidate(String id) {
+ startBatch();
+ Object rtn = cache.remove(id);
+ logger.trace("*** END BATCH ***");
+ try {
+ if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_ACTIVE) {
+ if (true) {
+ cache.getAdvancedCache().getTransactionManager().commit();
+
+ } else {
+ cache.getAdvancedCache().getTransactionManager().rollback();
+
+ }
+ }
+ } catch (Exception e) {
+ logger.trace("Failed to commit invalidate");
+ }
+ return rtn;
+ }
+
+ @Override
+ public void invalidateCachedRealmById(String id) {
+ CachedRealm cached = (CachedRealm) invalidate(id);
+ if (cached != null) realmLookup.remove(cached.getName());
+ }
+
+ @Override
+ public void addCachedRealm(CachedRealm realm) {
+ logger.tracev("Adding realm {0}", realm.getId());
+ cache.putForExternalRead(realm.getId(), realm);
+ realmLookup.put(realm.getName(), realm.getId());
+ }
+
+ @Override
+ public CachedRealm getCachedRealmByName(String name) {
+ String id = realmLookup.get(name);
+ return id != null ? getCachedRealm(id) : null;
+ }
+
+ @Override
+ public CachedClient getApplication(String id) {
+ return get(id, CachedClient.class);
+ }
+
+ @Override
+ public void invalidateApplication(CachedClient app) {
+ logger.tracev("Removing application {0}", app.getId());
+ invalidate(app.getId());
+ }
+
+ @Override
+ public void addCachedClient(CachedClient app) {
+ logger.tracev("Adding application {0}", app.getId());
+ cache.putForExternalRead(app.getId(), app);
+ }
+
+ @Override
+ public void invalidateCachedApplicationById(String id) {
+ logger.tracev("Removing application {0}", id);
+ invalidate(id);
+ }
+
+ @Override
+ public void evictCachedApplicationById(String id) {
+ logger.tracev("Evicting application {0}", id);
+ cache.evict(id);
+ }
+
+ @Override
+ public CachedGroup getGroup(String id) {
+ return get(id, CachedGroup.class);
+ }
+
+ @Override
+ public void invalidateGroup(CachedGroup role) {
+ logger.tracev("Removing group {0}", role.getId());
+ invalidate(role.getId());
+ }
+
+ @Override
+ public void addCachedGroup(CachedGroup role) {
+ logger.tracev("Adding group {0}", role.getId());
+ cache.putForExternalRead(role.getId(), role);
+ }
+
+ @Override
+ public void invalidateCachedGroupById(String id) {
+ logger.tracev("Removing group {0}", id);
+ invalidate(id);
+
+ }
+
+ @Override
+ public void invalidateGroupById(String id) {
+ logger.tracev("Removing group {0}", id);
+ invalidate(id);
+ }
+
+ @Override
+ public CachedRole getRole(String id) {
+ return get(id, CachedRole.class);
+ }
+
+ @Override
+ public void invalidateRole(CachedRole role) {
+ logger.tracev("Removing role {0}", role.getId());
+ invalidate(role.getId());
+ }
+
+ @Override
+ public void invalidateRoleById(String id) {
+ logger.tracev("Removing role {0}", id);
+ invalidate(id);
+ }
+
+ @Override
+ public void evictCachedRoleById(String id) {
+ logger.tracev("Evicting role {0}", id);
+ cache.evict(id);
+ }
+
+ @Override
+ public void addCachedRole(CachedRole role) {
+ logger.tracev("Adding role {0}", role.getId());
+ cache.putForExternalRead(role.getId(), role);
+ }
+
+ @Override
+ public void invalidateCachedRoleById(String id) {
+ logger.tracev("Removing role {0}", id);
+ invalidate(id);
+ }
+
+ private <T> T get(String id, Class<T> type) {
+ Object o = cache.get(id);
+ return o != null && type.isInstance(o) ? type.cast(o) : null;
+ }
+
+ @Override
+ public CachedClientTemplate getClientTemplate(String id) {
+ return get(id, CachedClientTemplate.class);
+ }
+
+ @Override
+ public void invalidateClientTemplate(CachedClientTemplate app) {
+ logger.tracev("Removing client template {0}", app.getId());
+ invalidate(app.getId());
+ }
+
+ @Override
+ public void addCachedClientTemplate(CachedClientTemplate app) {
+ logger.tracev("Adding client template {0}", app.getId());
+ cache.putForExternalRead(app.getId(), app);
+ }
+
+ @Override
+ public void invalidateCachedClientTemplateById(String id) {
+ logger.tracev("Removing client template {0}", id);
+ invalidate(id);
+ }
+
+ @Override
+ public void evictCachedClientTemplateById(String id) {
+ logger.tracev("Evicting client template {0}", id);
+ invalidate(id);
+ }
+
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewRealmCacheProvider.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewRealmCacheProvider.java
new file mode 100755
index 0000000..5ff2ff8
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewRealmCacheProvider.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.cache.infinispan.skewed;
+
+import org.jboss.logging.Logger;
+import org.keycloak.migration.MigrationModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientTemplateModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakTransaction;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RealmProvider;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.cache.CacheRealmProvider;
+import org.keycloak.models.cache.entities.CachedClient;
+import org.keycloak.models.cache.entities.CachedClientRole;
+import org.keycloak.models.cache.entities.CachedClientTemplate;
+import org.keycloak.models.cache.entities.CachedGroup;
+import org.keycloak.models.cache.entities.CachedRealm;
+import org.keycloak.models.cache.entities.CachedRealmRole;
+import org.keycloak.models.cache.entities.CachedRole;
+import org.keycloak.models.cache.infinispan.ClientAdapter;
+import org.keycloak.models.cache.infinispan.ClientTemplateAdapter;
+import org.keycloak.models.cache.infinispan.GroupAdapter;
+import org.keycloak.models.cache.infinispan.RealmAdapter;
+import org.keycloak.models.cache.infinispan.RoleAdapter;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * DO NOT USE THIS!!
+ *
+ * Tries unsuccessfully to use Infinispan with REPEATABLE_READ, write-skew-checking
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RepeatableReadWriteSkewRealmCacheProvider implements CacheRealmProvider {
+ protected static final Logger logger = Logger.getLogger(RepeatableReadWriteSkewRealmCacheProvider.class);
+
+ protected RepeatableReadWriteSkewRealmCache cache;
+ protected KeycloakSession session;
+ protected RealmProvider delegate;
+ protected boolean transactionActive;
+ protected boolean setRollbackOnly;
+
+ protected Set<String> realmInvalidations = new HashSet<>();
+ protected Set<String> appInvalidations = new HashSet<>();
+ protected Set<String> clientTemplateInvalidations = new HashSet<>();
+ protected Set<String> roleInvalidations = new HashSet<>();
+ protected Set<String> groupInvalidations = new HashSet<>();
+ protected Map<String, RealmModel> managedRealms = new HashMap<>();
+ protected Map<String, ClientModel> managedApplications = new HashMap<>();
+ protected Map<String, ClientTemplateModel> managedClientTemplates = new HashMap<>();
+ protected Map<String, RoleModel> managedRoles = new HashMap<>();
+ protected Map<String, GroupModel> managedGroups = new HashMap<>();
+
+ protected boolean clearAll;
+
+ public RepeatableReadWriteSkewRealmCacheProvider(RepeatableReadWriteSkewRealmCache cache, KeycloakSession session) {
+ this.cache = cache;
+ this.session = session;
+
+ session.getTransaction().enlistAfterCompletion(getTransaction());
+ }
+
+ @Override
+ public void clear() {
+ cache.clear();
+ }
+
+ @Override
+ public MigrationModel getMigrationModel() {
+ return getDelegate().getMigrationModel();
+ }
+
+ @Override
+ public RealmProvider getDelegate() {
+ if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction");
+ if (delegate != null) return delegate;
+ delegate = session.getProvider(RealmProvider.class);
+ return delegate;
+ }
+
+ @Override
+ public void registerRealmInvalidation(String id) {
+ realmInvalidations.add(id);
+ }
+
+ @Override
+ public void registerApplicationInvalidation(String id) {
+ appInvalidations.add(id);
+ }
+ @Override
+ public void registerClientTemplateInvalidation(String id) {
+ clientTemplateInvalidations.add(id);
+ }
+
+ @Override
+ public void registerRoleInvalidation(String id) {
+ roleInvalidations.add(id);
+ }
+
+ @Override
+ public void registerGroupInvalidation(String id) {
+ groupInvalidations.add(id);
+
+ }
+
+ protected void runInvalidations() {
+ for (String id : realmInvalidations) {
+ cache.invalidateCachedRealmById(id);
+ }
+ for (String id : roleInvalidations) {
+ cache.invalidateRoleById(id);
+ }
+ for (String id : groupInvalidations) {
+ cache.invalidateGroupById(id);
+ }
+ for (String id : appInvalidations) {
+ cache.invalidateCachedApplicationById(id);
+ }
+ for (String id : clientTemplateInvalidations) {
+ cache.invalidateCachedClientTemplateById(id);
+ }
+ }
+
+ private KeycloakTransaction getTransaction() {
+ return new KeycloakTransaction() {
+ @Override
+ public void begin() {
+ transactionActive = true;
+ }
+
+ @Override
+ public void commit() {
+ if (delegate == null) return;
+ if (clearAll) {
+ cache.clear();
+ }
+ runInvalidations();
+ transactionActive = false;
+ }
+
+ @Override
+ public void rollback() {
+ setRollbackOnly = true;
+ runInvalidations();
+ transactionActive = false;
+ }
+
+ @Override
+ public void setRollbackOnly() {
+ setRollbackOnly = true;
+ }
+
+ @Override
+ public boolean getRollbackOnly() {
+ return setRollbackOnly;
+ }
+
+ @Override
+ public boolean isActive() {
+ return transactionActive;
+ }
+ };
+ }
+
+ @Override
+ public RealmModel createRealm(String name) {
+ RealmModel realm = getDelegate().createRealm(name);
+ registerRealmInvalidation(realm.getId());
+ return realm;
+ }
+
+ @Override
+ public RealmModel createRealm(String id, String name) {
+ RealmModel realm = getDelegate().createRealm(id, name);
+ registerRealmInvalidation(realm.getId());
+ return realm;
+ }
+
+ @Override
+ public RealmModel getRealm(String id) {
+ //cache.startBatch();
+ cache.startBatch();
+ boolean batchEnded = false;
+ try {
+ CachedRealm cached = cache.getCachedRealm(id);
+ boolean wasNull = cached == null;
+ if (cached == null) {
+ RealmModel model = getDelegate().getRealm(id);
+ if (model == null) return null;
+ if (realmInvalidations.contains(id)) return model;
+ cached = new CachedRealm(cache, this, model);
+ cache.addCachedRealm(cached);
+ try {
+ batchEnded = true;
+ cache.endBatch(true);
+ logger.trace("returning new cached realm");
+ } catch (Exception exception) {
+ logger.trace("failed to add to cache", exception);
+ return model;
+ }
+ } else if (realmInvalidations.contains(id)) {
+ return getDelegate().getRealm(id);
+ } else if (managedRealms.containsKey(id)) {
+ return managedRealms.get(id);
+ }
+ if (!wasNull) logger.trace("returning cached realm: " + cached.getName());
+ RealmAdapter adapter = new RealmAdapter(cached, this);
+ managedRealms.put(id, adapter);
+ return adapter;
+ } finally {
+ if (!batchEnded) cache.endBatch(true);
+ }
+ }
+
+ @Override
+ public RealmModel getRealmByName(String name) {
+ cache.startBatch();
+ boolean batchEnded = false;
+ try {
+ CachedRealm cached = cache.getCachedRealmByName(name);
+ boolean wasNull = cached == null;
+ if (cached == null) {
+ RealmModel model = getDelegate().getRealmByName(name);
+ if (model == null) return null;
+ if (realmInvalidations.contains(model.getId())) return model;
+ cached = new CachedRealm(cache, this, model);
+ cache.addCachedRealm(cached);
+ try {
+ batchEnded = true;
+ cache.endBatch(true);
+ logger.trace("returning new cached realm: " + cached.getName());
+ } catch (Exception exception) {
+ logger.trace("failed to add to cache", exception);
+ return model;
+ }
+ } else if (realmInvalidations.contains(cached.getId())) {
+ return getDelegate().getRealmByName(name);
+ } else if (managedRealms.containsKey(cached.getId())) {
+ return managedRealms.get(cached.getId());
+ }
+ if (!wasNull) logger.trace("returning cached realm: " + cached.getName());
+ RealmAdapter adapter = new RealmAdapter(cached, this);
+ managedRealms.put(cached.getId(), adapter);
+ return adapter;
+ } finally {
+ if (!batchEnded) cache.endBatch(true);
+
+ }
+ }
+
+ @Override
+ public List<RealmModel> getRealms() {
+ // Retrieve realms from backend
+ List<RealmModel> backendRealms = getDelegate().getRealms();
+
+ // Return cache delegates to ensure cache invalidated during write operations
+ List<RealmModel> cachedRealms = new LinkedList<RealmModel>();
+ for (RealmModel realm : backendRealms) {
+ RealmModel cached = getRealm(realm.getId());
+ cachedRealms.add(cached);
+ }
+ return cachedRealms;
+ }
+
+ @Override
+ public boolean removeRealm(String id) {
+ cache.invalidateCachedRealmById(id);
+
+ RealmModel realm = getDelegate().getRealm(id);
+ Set<RoleModel> realmRoles = null;
+ if (realm != null) {
+ realmRoles = realm.getRoles();
+ }
+
+ boolean didIt = getDelegate().removeRealm(id);
+ realmInvalidations.add(id);
+
+ // TODO: Temporary workaround to invalidate cached realm roles
+ if (didIt && realmRoles != null) {
+ for (RoleModel role : realmRoles) {
+ roleInvalidations.add(role.getId());
+ }
+ }
+
+ return didIt;
+ }
+
+ @Override
+ public void close() {
+ if (delegate != null) delegate.close();
+ }
+
+ @Override
+ public RoleModel getRoleById(String id, RealmModel realm) {
+ cache.startBatch();
+ boolean batchEnded = false;
+ try {
+ CachedRole cached = cache.getRole(id);
+ if (cached != null && !cached.getRealm().equals(realm.getId())) {
+ cached = null;
+ }
+
+ if (cached == null) {
+ RoleModel model = getDelegate().getRoleById(id, realm);
+ if (model == null) return null;
+ if (roleInvalidations.contains(id)) return model;
+ if (model.getContainer() instanceof ClientModel) {
+ cached = new CachedClientRole(((ClientModel) model.getContainer()).getId(), model, realm);
+ } else {
+ cached = new CachedRealmRole(model, realm);
+ }
+ cache.addCachedRole(cached);
+ try {
+ batchEnded = true;
+ cache.endBatch(true);
+ } catch (Exception exception) {
+ logger.trace("failed to add to cache", exception);
+ return model;
+ }
+
+ } else if (roleInvalidations.contains(id)) {
+ return getDelegate().getRoleById(id, realm);
+ } else if (managedRoles.containsKey(id)) {
+ return managedRoles.get(id);
+ }
+ RoleAdapter adapter = new RoleAdapter(cached, cache, this, realm);
+ managedRoles.put(id, adapter);
+ return adapter;
+ } finally {
+ if (!batchEnded) cache.endBatch(true);
+
+ }
+ }
+
+ @Override
+ public GroupModel getGroupById(String id, RealmModel realm) {
+ cache.startBatch();
+ boolean batchEnded = false;
+ try {
+ CachedGroup cached = cache.getGroup(id);
+ if (cached != null && !cached.getRealm().equals(realm.getId())) {
+ cached = null;
+ }
+
+ if (cached == null) {
+ GroupModel model = getDelegate().getGroupById(id, realm);
+ if (model == null) return null;
+ if (groupInvalidations.contains(id)) return model;
+ cached = new CachedGroup(realm, model);
+ cache.addCachedGroup(cached);
+ try {
+ batchEnded = true;
+ cache.endBatch(true);
+ } catch (Exception exception) {
+ logger.trace("failed to add to cache", exception);
+ return model;
+ }
+
+ } else if (groupInvalidations.contains(id)) {
+ return getDelegate().getGroupById(id, realm);
+ } else if (managedGroups.containsKey(id)) {
+ return managedGroups.get(id);
+ }
+ GroupAdapter adapter = new GroupAdapter(cached, this, session, realm);
+ managedGroups.put(id, adapter);
+ return adapter;
+ } finally {
+ if (!batchEnded) cache.endBatch(true);
+
+ }
+ }
+
+ @Override
+ public ClientModel getClientById(String id, RealmModel realm) {
+ cache.startBatch();
+ boolean batchEnded = false;
+ CachedClient cached = cache.getApplication(id);
+ try {
+ if (cached != null && !cached.getRealm().equals(realm.getId())) {
+ cached = null;
+ }
+
+ if (cached == null) {
+ ClientModel model = getDelegate().getClientById(id, realm);
+ if (model == null) return null;
+ if (appInvalidations.contains(id)) return model;
+ cached = new CachedClient(cache, getDelegate(), realm, model);
+ cache.addCachedClient(cached);
+ try {
+ batchEnded = true;
+ cache.endBatch(true);
+ } catch (Exception exception) {
+ logger.trace("failed to add to cache", exception);
+ return model;
+ }
+ } else if (appInvalidations.contains(id)) {
+ return getDelegate().getClientById(id, realm);
+ } else if (managedApplications.containsKey(id)) {
+ return managedApplications.get(id);
+ }
+ ClientAdapter adapter = new ClientAdapter(realm, cached, this, cache);
+ managedApplications.put(id, adapter);
+ return adapter;
+ } finally {
+ if (!batchEnded) cache.endBatch(true);
+
+ }
+ }
+ @Override
+ public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) {
+ cache.startBatch();
+ boolean batchEnded = false;
+ try {
+ CachedClientTemplate cached = cache.getClientTemplate(id);
+ if (cached != null && !cached.getRealm().equals(realm.getId())) {
+ cached = null;
+ }
+
+ if (cached == null) {
+ ClientTemplateModel model = getDelegate().getClientTemplateById(id, realm);
+ if (model == null) return null;
+ if (clientTemplateInvalidations.contains(id)) return model;
+ cached = new CachedClientTemplate(cache, getDelegate(), realm, model);
+ cache.addCachedClientTemplate(cached);
+ try {
+ batchEnded = true;
+ cache.endBatch(true);
+ } catch (Exception exception) {
+ logger.trace("failed to add to cache", exception);
+ return model;
+ }
+ } else if (clientTemplateInvalidations.contains(id)) {
+ return getDelegate().getClientTemplateById(id, realm);
+ } else if (managedClientTemplates.containsKey(id)) {
+ return managedClientTemplates.get(id);
+ }
+ ClientTemplateModel adapter = new ClientTemplateAdapter(realm, cached, this, cache);
+ managedClientTemplates.put(id, adapter);
+ return adapter;
+ } finally {
+ if (!batchEnded) cache.endBatch(true);
+
+ }
+ }
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
index 9cf19c2..1fc04de 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
@@ -24,6 +24,7 @@ import org.keycloak.common.util.Time;
import org.keycloak.models.*;
import org.keycloak.models.session.UserSessionPersisterProvider;
import org.keycloak.models.sessions.infinispan.entities.*;
+import org.keycloak.models.sessions.infinispan.initializer.TimeAwareInitializerState;
import org.keycloak.models.sessions.infinispan.stream.*;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.RealmInfoUtil;
@@ -411,6 +412,19 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
@Override
+ public int getClusterStartupTime() {
+ TimeAwareInitializerState state = (TimeAwareInitializerState) offlineSessionCache.get(InfinispanUserSessionProviderFactory.SESSION_INITIALIZER_STATE_KEY);
+ int startTime;
+ if (state == null) {
+ log.warn("Cluster startup time not yet available. Fallback to local startup time");
+ startTime = (int)(session.getKeycloakSessionFactory().getServerStartupTimestamp() / 1000);
+ } else {
+ startTime = state.getClusterStartupTime();
+ }
+ return startTime;
+ }
+
+ @Override
public void close() {
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java
index 7f20f0e..1bb82a1 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java
@@ -34,6 +34,9 @@ import org.keycloak.provider.ProviderEventListener;
public class InfinispanUserSessionProviderFactory implements UserSessionProviderFactory {
+ private static final String STATE_KEY_PREFIX = "initializerState";
+ public static final String SESSION_INITIALIZER_STATE_KEY = STATE_KEY_PREFIX + "::offlineUserSessions";
+
private static final Logger log = Logger.getLogger(InfinispanUserSessionProviderFactory.class);
private Config.Scope config;
@@ -84,7 +87,7 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
Cache<String, SessionEntity> cache = connections.getCache(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME);
- InfinispanUserSessionInitializer initializer = new InfinispanUserSessionInitializer(sessionFactory, cache, new OfflineUserSessionLoader(), maxErrors, sessionsPerSegment, "offlineUserSessions");
+ InfinispanUserSessionInitializer initializer = new InfinispanUserSessionInitializer(sessionFactory, cache, new OfflineUserSessionLoader(), maxErrors, sessionsPerSegment, SESSION_INITIALIZER_STATE_KEY);
initializer.initCache();
initializer.loadPersistentSessions();
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/InfinispanUserSessionInitializer.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/InfinispanUserSessionInitializer.java
index 2ce4ac2..deae897 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/InfinispanUserSessionInitializer.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/InfinispanUserSessionInitializer.java
@@ -35,6 +35,7 @@ import org.infinispan.notifications.cachemanagerlistener.annotation.ViewChanged;
import org.infinispan.notifications.cachemanagerlistener.event.ViewChangedEvent;
import org.infinispan.remoting.transport.Transport;
import org.jboss.logging.Logger;
+import org.keycloak.common.util.Time;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakSessionTask;
@@ -51,8 +52,6 @@ public class InfinispanUserSessionInitializer {
private static final Logger log = Logger.getLogger(InfinispanUserSessionInitializer.class);
- private static final String STATE_KEY_PREFIX = "initializerState";
-
private final KeycloakSessionFactory sessionFactory;
private final Cache<String, SessionEntity> cache;
private final SessionLoader sessionLoader;
@@ -60,21 +59,18 @@ public class InfinispanUserSessionInitializer {
private final int sessionsPerSegment;
private final String stateKey;
- private volatile CountDownLatch latch = new CountDownLatch(1);
-
- public InfinispanUserSessionInitializer(KeycloakSessionFactory sessionFactory, Cache<String, SessionEntity> cache, SessionLoader sessionLoader, int maxErrors, int sessionsPerSegment, String stateKeySuffix) {
+ public InfinispanUserSessionInitializer(KeycloakSessionFactory sessionFactory, Cache<String, SessionEntity> cache, SessionLoader sessionLoader, int maxErrors, int sessionsPerSegment, String stateKey) {
this.sessionFactory = sessionFactory;
this.cache = cache;
this.sessionLoader = sessionLoader;
this.maxErrors = maxErrors;
this.sessionsPerSegment = sessionsPerSegment;
- this.stateKey = STATE_KEY_PREFIX + "::" + stateKeySuffix;
+ this.stateKey = stateKey;
}
public void initCache() {
this.cache.getAdvancedCache().getComponentRegistry().registerComponent(sessionFactory, KeycloakSessionFactory.class);
- cache.getCacheManager().addListener(new ViewChangeListener());
}
@@ -86,7 +82,7 @@ public class InfinispanUserSessionInitializer {
while (!isFinished()) {
if (!isCoordinator()) {
try {
- latch.await(500, TimeUnit.MILLISECONDS);
+ Thread.sleep(1000);
} catch (InterruptedException ie) {
log.error("Interrupted", ie);
}
@@ -104,8 +100,10 @@ public class InfinispanUserSessionInitializer {
private InitializerState getOrCreateInitializerState() {
- InitializerState state = (InitializerState) cache.get(stateKey);
+ TimeAwareInitializerState state = (TimeAwareInitializerState) cache.get(stateKey);
if (state == null) {
+ int startTime = (int)(sessionFactory.getServerStartupTimestamp() / 1000);
+
final int[] count = new int[1];
// Rather use separate transactions for update and counting
@@ -113,7 +111,7 @@ public class InfinispanUserSessionInitializer {
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
@Override
public void run(KeycloakSession session) {
- sessionLoader.init(session);
+ sessionLoader.init(session, startTime);
}
});
@@ -126,8 +124,9 @@ public class InfinispanUserSessionInitializer {
});
- state = new InitializerState();
+ state = new TimeAwareInitializerState();
state.init(count[0], sessionsPerSegment);
+ state.setClusterStartupTime(startTime);
saveStateToCache(state);
}
return state;
@@ -251,24 +250,6 @@ public class InfinispanUserSessionInitializer {
}
}
-
- @Listener
- public class ViewChangeListener {
-
- @ViewChanged
- public void viewChanged(ViewChangedEvent event) {
- boolean isCoordinator = isCoordinator();
- log.debug("View Changed: is coordinator: " + isCoordinator);
-
- if (isCoordinator) {
- latch.countDown();
- latch = new CountDownLatch(1);
- }
- }
-
- }
-
-
public static class WorkerResult implements Serializable {
private Integer segment;
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/OfflineUserSessionLoader.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/OfflineUserSessionLoader.java
index c847d51..8b651c0 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/OfflineUserSessionLoader.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/OfflineUserSessionLoader.java
@@ -33,14 +33,13 @@ public class OfflineUserSessionLoader implements SessionLoader {
private static final Logger log = Logger.getLogger(OfflineUserSessionLoader.class);
@Override
- public void init(KeycloakSession session) {
+ public void init(KeycloakSession session, int clusterStartupTime) {
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
- int startTime = (int)(session.getKeycloakSessionFactory().getServerStartupTimestamp() / 1000);
- log.debugf("Clearing detached sessions from persistent storage and updating timestamps to %d", startTime);
+ log.debugf("Clearing detached sessions from persistent storage and updating timestamps to %d", clusterStartupTime);
persister.clearDetachedUserSessions();
- persister.updateAllTimestamps(startTime);
+ persister.updateAllTimestamps(clusterStartupTime);
}
@Override
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/SessionLoader.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/SessionLoader.java
index 3185a39..b8aa0f8 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/SessionLoader.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/SessionLoader.java
@@ -26,7 +26,7 @@ import org.keycloak.models.KeycloakSession;
*/
public interface SessionLoader extends Serializable {
- void init(KeycloakSession session);
+ void init(KeycloakSession session, int clusterStartupTime);
int getSessionsCount(KeycloakSession session);
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/TimeAwareInitializerState.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/TimeAwareInitializerState.java
new file mode 100644
index 0000000..f5b7f04
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/TimeAwareInitializerState.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.sessions.infinispan.initializer;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class TimeAwareInitializerState extends InitializerState {
+
+ private int clusterStartupTime;
+
+ public int getClusterStartupTime() {
+ return clusterStartupTime;
+ }
+
+ public void setClusterStartupTime(int clusterStartupTime) {
+ this.clusterStartupTime = clusterStartupTime;
+ }
+}
diff --git a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory
old mode 100644
new mode 100755
index 58a6bda..ef880ec
--- a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory
+++ b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory
@@ -15,4 +15,6 @@
# limitations under the License.
#
-org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory
\ No newline at end of file
+org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory
+org.keycloak.models.cache.infinispan.counter.RevisionedConnectionProviderFactory
+org.keycloak.models.cache.infinispan.locking.LockingConnectionProviderFactory
\ No newline at end of file
diff --git a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory
index 1b79a2f..6554765 100755
--- a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory
+++ b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory
@@ -15,4 +15,6 @@
# limitations under the License.
#
-org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory
\ No newline at end of file
+org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory
+org.keycloak.models.cache.infinispan.counter.RevisionedCacheRealmProviderFactory
+org.keycloak.models.cache.infinispan.locking.LockingCacheRealmProviderFactory
\ No newline at end of file
diff --git a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ConcurrencyLockingTest.java b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ConcurrencyLockingTest.java
new file mode 100755
index 0000000..aa26b5f
--- /dev/null
+++ b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ConcurrencyLockingTest.java
@@ -0,0 +1,88 @@
+package org.keycloak.models.sessions.infinispan.initializer;
+
+import org.infinispan.Cache;
+import org.infinispan.configuration.cache.CacheMode;
+import org.infinispan.configuration.cache.Configuration;
+import org.infinispan.configuration.cache.ConfigurationBuilder;
+import org.infinispan.configuration.cache.VersioningScheme;
+import org.infinispan.configuration.global.GlobalConfigurationBuilder;
+import org.infinispan.manager.DefaultCacheManager;
+import org.infinispan.manager.EmbeddedCacheManager;
+import org.infinispan.transaction.LockingMode;
+import org.infinispan.transaction.TransactionMode;
+import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
+import org.infinispan.util.concurrent.IsolationLevel;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@Ignore
+public class ConcurrencyLockingTest {
+
+ @Test
+ public void testLocking() throws Exception {
+ final DefaultCacheManager cacheManager = getVersionedCacheManager();
+ Cache<String, String> cache = cacheManager.getCache("COUNTER_CACHE");
+ cache.put("key", "init");
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ Cache<String, String> cache = cacheManager.getCache("COUNTER_CACHE");
+ cache.startBatch();
+ System.out.println("thread lock");
+ cache.getAdvancedCache().lock("key");
+ try {
+ Thread.sleep(100000);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ cache.endBatch(true);
+
+ }
+ });
+ Thread.sleep(10);
+ cache.startBatch();
+ cache.getAdvancedCache().lock("key");
+ cache.put("key", "1234");
+ System.out.println("after put");
+ cache.endBatch(true);
+
+ Thread.sleep(1000000);
+
+
+
+ }
+
+ protected DefaultCacheManager getVersionedCacheManager() {
+ GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
+
+
+ boolean allowDuplicateJMXDomains = true;
+
+ gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
+
+ final DefaultCacheManager cacheManager = new DefaultCacheManager(gcb.build());
+ ConfigurationBuilder invalidationConfigBuilder = new ConfigurationBuilder();
+ Configuration invalidationCacheConfiguration = invalidationConfigBuilder.build();
+ cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_CACHE_NAME, invalidationCacheConfiguration);
+
+ ConfigurationBuilder counterConfigBuilder = new ConfigurationBuilder();
+ counterConfigBuilder.invocationBatching().enable();
+ counterConfigBuilder.transaction().transactionManagerLookup(new DummyTransactionManagerLookup());
+ counterConfigBuilder.transaction().lockingMode(LockingMode.PESSIMISTIC);
+ Configuration counterCacheConfiguration = counterConfigBuilder.build();
+
+ cacheManager.defineConfiguration("COUNTER_CACHE", counterCacheConfiguration);
+ return cacheManager;
+ }
+
+}
diff --git a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ConcurrencyVersioningTest.java b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ConcurrencyVersioningTest.java
new file mode 100755
index 0000000..48a0e36
--- /dev/null
+++ b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ConcurrencyVersioningTest.java
@@ -0,0 +1,269 @@
+package org.keycloak.models.sessions.infinispan.initializer;
+
+import org.infinispan.Cache;
+import org.infinispan.commons.CacheException;
+import org.infinispan.configuration.cache.CacheMode;
+import org.infinispan.configuration.cache.Configuration;
+import org.infinispan.configuration.cache.ConfigurationBuilder;
+import org.infinispan.configuration.cache.VersioningScheme;
+import org.infinispan.configuration.global.GlobalConfigurationBuilder;
+import org.infinispan.manager.DefaultCacheManager;
+import org.infinispan.manager.EmbeddedCacheManager;
+import org.infinispan.transaction.TransactionMode;
+import org.infinispan.transaction.TransactionProtocol;
+import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
+import org.infinispan.util.concurrent.IsolationLevel;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
+
+import javax.transaction.NotSupportedException;
+import javax.transaction.Status;
+import javax.transaction.SystemException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Unit tests to make sure our model caching concurrency model will work.
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@Ignore
+public class ConcurrencyVersioningTest {
+
+ public static abstract class AbstractThread implements Runnable {
+ EmbeddedCacheManager cacheManager;
+ boolean success;
+ CountDownLatch latch = new CountDownLatch(1);
+
+ public AbstractThread(EmbeddedCacheManager cacheManager) {
+ this.cacheManager = cacheManager;
+ }
+
+ public boolean isSuccess() {
+ return success;
+ }
+
+ public CountDownLatch getLatch() {
+ return latch;
+ }
+ }
+
+ public static class RemoveThread extends AbstractThread {
+ public RemoveThread(EmbeddedCacheManager cacheManager) {
+ super(cacheManager);
+ }
+
+ public void run() {
+ Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
+ try {
+ startBatch(cache);
+ cache.remove("key");
+ //cache.getAdvancedCache().getTransactionManager().commit();
+ endBatch(cache);
+ success = true;
+ } catch (Exception e) {
+ success = false;
+ }
+ latch.countDown();
+ }
+
+ }
+
+
+ public static class UpdateThread extends AbstractThread {
+ public UpdateThread(EmbeddedCacheManager cacheManager) {
+ super(cacheManager);
+ }
+
+ public void run() {
+ Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
+ try {
+ startBatch(cache);
+ cache.putForExternalRead("key", "value2");
+ //cache.getAdvancedCache().getTransactionManager().commit();
+ endBatch(cache);
+ success = true;
+ } catch (Exception e) {
+ success = false;
+ }
+ latch.countDown();
+ }
+
+ }
+
+ /**
+ * Tests that if remove executes before put, then put still succeeds.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testGetRemovePutOnNonExisting() throws Exception {
+ final DefaultCacheManager cacheManager = getVersionedCacheManager();
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ RemoveThread removeThread = new RemoveThread(cacheManager);
+
+ Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
+ cache.remove("key");
+ startBatch(cache);
+ cache.get("key");
+ executor.execute(removeThread);
+ removeThread.getLatch().await();
+ cache.putForExternalRead("key", "value1");
+ endBatch(cache);
+ Assert.assertEquals(cache.get("key"), "value1");
+ Assert.assertTrue(removeThread.isSuccess());
+ }
+
+
+ /**
+ * Test that if a put of an existing key is removed after the put and before tx commit, it is evicted
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testGetRemovePutOnExisting() throws Exception {
+ final DefaultCacheManager cacheManager = getVersionedCacheManager();
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ RemoveThread removeThread = new RemoveThread(cacheManager);
+
+ Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
+ cache.put("key", "value0");
+ startBatch(cache);
+ cache.get("key");
+ executor.execute(removeThread);
+ removeThread.getLatch().await();
+ cache.put("key", "value1");
+ try {
+ endBatch(cache);
+ Assert.fail("Write skew should be detected");
+ } catch (Exception e) {
+
+
+ }
+ Assert.assertNull(cache.get("key"));
+ Assert.assertTrue(removeThread.isSuccess());
+ }
+
+ /**
+ * Test that if a put of an existing key is removed after the put and before tx commit, it is evicted
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testGetRemovePutEternalOnExisting() throws Exception {
+ final DefaultCacheManager cacheManager = getVersionedCacheManager();
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ RemoveThread removeThread = new RemoveThread(cacheManager);
+
+ Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
+ cache.put("key", "value0");
+ startBatch(cache);
+ cache.get("key");
+ executor.execute(removeThread);
+ cache.putForExternalRead("key", "value1");
+ removeThread.getLatch().await();
+ try {
+ endBatch(cache);
+// Assert.fail("Write skew should be detected");
+ } catch (Exception e) {
+
+ }
+ Assert.assertNull(cache.get("key"));
+ Assert.assertTrue(removeThread.isSuccess());
+ }
+
+ @Test
+ public void testPutExternalRemoveOnExisting() throws Exception {
+ final DefaultCacheManager cacheManager = getVersionedCacheManager();
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ RemoveThread removeThread = new RemoveThread(cacheManager);
+
+ Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
+ cache.put("key", "value0");
+ startBatch(cache);
+ cache.putForExternalRead("key", "value1");
+ executor.execute(removeThread);
+ removeThread.getLatch().await();
+ try {
+ endBatch(cache);
+// Assert.fail("Write skew should be detected");
+ } catch (Exception e) {
+
+ }
+ Assert.assertNull(cache.get("key"));
+ Assert.assertTrue(removeThread.isSuccess());
+ }
+
+
+ public static void startBatch(Cache<String, String> cache) {
+ try {
+ if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_NO_TRANSACTION) {
+ System.out.println("begin");
+ cache.getAdvancedCache().getTransactionManager().begin();
+ }
+ } catch (NotSupportedException e) {
+ throw new RuntimeException(e);
+ } catch (SystemException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ public static void endBatch(Cache<String, String> cache) {
+ boolean commit = true;
+ try {
+ if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_ACTIVE) {
+ if (commit) {
+ cache.getAdvancedCache().getTransactionManager().commit();
+
+ } else {
+ cache.getAdvancedCache().getTransactionManager().rollback();
+
+ }
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ protected DefaultCacheManager getVersionedCacheManager() {
+ GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
+
+
+ boolean clustered = false;
+ boolean async = false;
+ boolean allowDuplicateJMXDomains = true;
+
+ if (clustered) {
+ gcb.transport().defaultTransport();
+ }
+ gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
+
+ final DefaultCacheManager cacheManager = new DefaultCacheManager(gcb.build());
+ ConfigurationBuilder invalidationConfigBuilder = new ConfigurationBuilder();
+ invalidationConfigBuilder
+ //.invocationBatching().enable()
+ .transaction().transactionMode(TransactionMode.TRANSACTIONAL)
+ .transaction().transactionManagerLookup(new DummyTransactionManagerLookup())
+ .locking().isolationLevel(IsolationLevel.REPEATABLE_READ).writeSkewCheck(true).versioning().enable().scheme(VersioningScheme.SIMPLE);
+
+
+ //invalidationConfigBuilder.locking().isolationLevel(IsolationLevel.REPEATABLE_READ).writeSkewCheck(true).versioning().enable().scheme(VersioningScheme.SIMPLE);
+
+ if (clustered) {
+ invalidationConfigBuilder.clustering().cacheMode(async ? CacheMode.INVALIDATION_ASYNC : CacheMode.INVALIDATION_SYNC);
+ }
+ Configuration invalidationCacheConfiguration = invalidationConfigBuilder.build();
+ cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_CACHE_NAME, invalidationCacheConfiguration);
+ return cacheManager;
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
index 5453164..311540b 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
@@ -633,8 +633,8 @@ public class ClientAdapter implements ClientModel {
roleEntity.setClient(entity);
roleEntity.setClientRole(true);
roleEntity.setRealmId(realm.getId());
- em.persist(roleEntity);
entity.getRoles().add(roleEntity);
+ em.persist(roleEntity);
em.flush();
return new RoleAdapter(realm, em, roleEntity);
}
@@ -667,12 +667,21 @@ public class ClientAdapter implements ClientModel {
@Override
public Set<RoleModel> getRoles() {
Set<RoleModel> list = new HashSet<RoleModel>();
+ /*
Collection<RoleEntity> roles = entity.getRoles();
if (roles == null) return list;
for (RoleEntity entity : roles) {
list.add(new RoleAdapter(realm, em, entity));
}
+ */
+ TypedQuery<RoleEntity> query = em.createNamedQuery("getClientRoles", RoleEntity.class);
+ query.setParameter("client", entity);
+ List<RoleEntity> roles = query.getResultList();
+ for (RoleEntity roleEntity : roles) {
+ list.add(new RoleAdapter(realm, em, roleEntity));
+ }
return list;
+
}
@Override
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationExecutionEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationExecutionEntity.java
index 71ecd94..8e759a4 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationExecutionEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationExecutionEntity.java
@@ -19,6 +19,8 @@ package org.keycloak.models.jpa.entities;
import org.keycloak.models.AuthenticationExecutionModel;
+import javax.persistence.Access;
+import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
@@ -43,6 +45,7 @@ import javax.persistence.Table;
public class AuthenticationExecutionEntity {
@Id
@Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@ManyToOne(fetch = FetchType.LAZY)
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationFlowEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationFlowEntity.java
index 886dd43..402acf1 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationFlowEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationFlowEntity.java
@@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
+import javax.persistence.Access;
+import javax.persistence.AccessType;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
@@ -44,6 +46,7 @@ import java.util.Collection;
public class AuthenticationFlowEntity {
@Id
@Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@ManyToOne(fetch = FetchType.LAZY)
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticatorConfigEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticatorConfigEntity.java
index a35c03e..9057683 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticatorConfigEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticatorConfigEntity.java
@@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
+import javax.persistence.Access;
+import javax.persistence.AccessType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
@@ -40,6 +42,7 @@ import java.util.Map;
public class AuthenticatorConfigEntity {
@Id
@Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@Column(name="ALIAS")
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
index 848401a..15544c6 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
@@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
+import javax.persistence.Access;
+import javax.persistence.AccessType;
import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
@@ -28,6 +30,8 @@ import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.MapKeyColumn;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
@@ -44,10 +48,14 @@ import java.util.Set;
*/
@Entity
@Table(name="CLIENT", uniqueConstraints = {@UniqueConstraint(columnNames = {"REALM_ID", "CLIENT_ID"})})
+@NamedQueries({
+ @NamedQuery(name="getClientsByRealm", query="select client from ClientEntity client where client.realm = :realm"),
+})
public class ClientEntity {
@Id
@Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
private String id;
@Column(name = "NAME")
private String name;
@@ -146,7 +154,7 @@ public class ClientEntity {
@Column(name="NODE_REREG_TIMEOUT")
private int nodeReRegistrationTimeout;
- @OneToMany(fetch = FetchType.EAGER, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "client")
+ @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, mappedBy = "client")
Collection<RoleEntity> roles = new ArrayList<RoleEntity>();
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientTemplateEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientTemplateEntity.java
index 1f27816..280a257 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientTemplateEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientTemplateEntity.java
@@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
+import javax.persistence.Access;
+import javax.persistence.AccessType;
import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
@@ -48,6 +50,7 @@ public class ClientTemplateEntity {
@Id
@Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
private String id;
@Column(name = "NAME")
private String name;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java
index 4e54c51..ceb284c 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java
@@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
+import javax.persistence.Access;
+import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
@@ -42,6 +44,7 @@ import javax.persistence.Table;
public class CredentialEntity {
@Id
@Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@Column(name="TYPE")
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupAttributeEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupAttributeEntity.java
index b784d82..a5dbfc4 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupAttributeEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupAttributeEntity.java
@@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
+import javax.persistence.Access;
+import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
@@ -42,6 +44,7 @@ public class GroupAttributeEntity {
@Id
@Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@ManyToOne(fetch= FetchType.LAZY)
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java
index 420929a..9eadbc8 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/GroupEntity.java
@@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
+import javax.persistence.Access;
+import javax.persistence.AccessType;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
@@ -47,6 +49,7 @@ import java.util.Collection;
public class GroupEntity {
@Id
@Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@Column(name = "NAME")
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java
index 6074663..77ca614 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderEntity.java
@@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
+import javax.persistence.Access;
+import javax.persistence.AccessType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
@@ -44,6 +46,7 @@ public class IdentityProviderEntity {
@Id
@Column(name="INTERNAL_ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String internalId;
@ManyToOne(fetch = FetchType.LAZY)
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderMapperEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderMapperEntity.java
index a7afa8d..2011e93 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderMapperEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/IdentityProviderMapperEntity.java
@@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
+import javax.persistence.Access;
+import javax.persistence.AccessType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
@@ -39,6 +41,7 @@ public class IdentityProviderMapperEntity {
@Id
@Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@Column(name="NAME")
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/MigrationModelEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/MigrationModelEntity.java
index 148d009..ce4d845 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/MigrationModelEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/MigrationModelEntity.java
@@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
+import javax.persistence.Access;
+import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
@@ -37,6 +39,7 @@ public class MigrationModelEntity {
public static final String SINGLETON_ID = "SINGLETON";
@Id
@Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
private String id;
@Column(name="VERSION", length = 36)
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ProtocolMapperEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ProtocolMapperEntity.java
index bb51ad9..b6e3071 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ProtocolMapperEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ProtocolMapperEntity.java
@@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
+import javax.persistence.Access;
+import javax.persistence.AccessType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
@@ -39,6 +41,7 @@ public class ProtocolMapperEntity {
@Id
@Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@Column(name="NAME")
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index da639d4..76aa238 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
+import javax.persistence.Access;
+import javax.persistence.AccessType;
import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
@@ -53,6 +55,7 @@ import java.util.Set;
public class RealmEntity {
@Id
@Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@Column(name="NAME", unique = true)
@@ -138,22 +141,19 @@ public class RealmEntity {
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
Collection<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
- @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true)
- @JoinTable(name="FED_PROVIDERS", joinColumns={ @JoinColumn(name="REALM_ID") }, inverseJoinColumns={ @JoinColumn(name = "USERFEDERATIONPROVIDERS_ID") })
+ @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
List<UserFederationProviderEntity> userFederationProviders = new ArrayList<UserFederationProviderEntity>();
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
Collection<UserFederationMapperEntity> userFederationMappers = new ArrayList<UserFederationMapperEntity>();
- @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
- @JoinTable(name="REALM_CLIENT", joinColumns={ @JoinColumn(name="REALM_ID") }, inverseJoinColumns={ @JoinColumn(name="CLIENT_ID") })
+ @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy="realm")
Collection<ClientEntity> clients = new ArrayList<>();
- @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
- @JoinTable(name="REALM_CLIENT_TEMPLATE", joinColumns={ @JoinColumn(name="REALM_ID") }, inverseJoinColumns={ @JoinColumn(name="CLIENT_TEMPLATE_ID") })
+ @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
Collection<ClientTemplateEntity> clientTemplates = new ArrayList<>();
- @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
+ @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, mappedBy = "realm")
Collection<RoleEntity> roles = new ArrayList<RoleEntity>();
@ElementCollection
@@ -421,7 +421,6 @@ public class RealmEntity {
public void setRequiredCredentials(Collection<RequiredCredentialEntity> requiredCredentials) {
this.requiredCredentials = requiredCredentials;
}
-
public Collection<ClientEntity> getClients() {
return clients;
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RequiredActionProviderEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RequiredActionProviderEntity.java
index 95d3373..d8b43fb 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RequiredActionProviderEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RequiredActionProviderEntity.java
@@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
+import javax.persistence.Access;
+import javax.persistence.AccessType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
@@ -42,6 +44,7 @@ import java.util.Map;
public class RequiredActionProviderEntity {
@Id
@Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@Column(name="ALIAS")
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
index 1b96cae..b94a743 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
@@ -17,6 +17,11 @@
package org.keycloak.models.jpa.entities;
+import org.hibernate.annotations.DynamicInsert;
+import org.hibernate.annotations.DynamicUpdate;
+
+import javax.persistence.Access;
+import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
@@ -37,10 +42,13 @@ import java.util.Collection;
* @version $Revision: 1 $
*/
@Entity
+//@DynamicInsert
+//@DynamicUpdate
@Table(name="KEYCLOAK_ROLE", uniqueConstraints = {
@UniqueConstraint(columnNames = { "NAME", "CLIENT_REALM_CONSTRAINT" })
})
@NamedQueries({
+ @NamedQuery(name="getClientRoles", query="select role from RoleEntity role where role.client = :client"),
@NamedQuery(name="getClientRoleByName", query="select role from RoleEntity role where role.name = :name and role.client = :client"),
@NamedQuery(name="getRealmRoleByName", query="select role from RoleEntity role where role.clientRole = false and role.name = :name and role.realm = :realm")
})
@@ -48,6 +56,7 @@ import java.util.Collection;
public class RoleEntity {
@Id
@Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
private String id;
@Column(name = "NAME")
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserAttributeEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserAttributeEntity.java
index e09c1bb..263757f 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserAttributeEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserAttributeEntity.java
@@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
+import javax.persistence.Access;
+import javax.persistence.AccessType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
@@ -48,6 +50,7 @@ public class UserAttributeEntity {
@Id
@Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@ManyToOne(fetch= FetchType.LAZY)
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserConsentEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserConsentEntity.java
index a349d80..c1098e2 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserConsentEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserConsentEntity.java
@@ -20,6 +20,8 @@ package org.keycloak.models.jpa.entities;
import java.util.ArrayList;
import java.util.Collection;
+import javax.persistence.Access;
+import javax.persistence.AccessType;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
@@ -51,6 +53,7 @@ public class UserConsentEntity {
@Id
@Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@ManyToOne(fetch= FetchType.LAZY)
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
index b1c8c04..d110a17 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
@@ -19,6 +19,8 @@ package org.keycloak.models.jpa.entities;
import org.keycloak.models.utils.KeycloakModelUtils;
+import javax.persistence.Access;
+import javax.persistence.AccessType;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
@@ -59,6 +61,7 @@ import java.util.Collection;
public class UserEntity {
@Id
@Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@Column(name = "USERNAME")
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationMapperEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationMapperEntity.java
index 54989f2..41350f6 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationMapperEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationMapperEntity.java
@@ -19,6 +19,8 @@ package org.keycloak.models.jpa.entities;
import java.util.Map;
+import javax.persistence.Access;
+import javax.persistence.AccessType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
@@ -41,6 +43,7 @@ public class UserFederationMapperEntity {
@Id
@Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@Column(name="NAME")
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationProviderEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationProviderEntity.java
index 7b841ca..f7cab7f 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationProviderEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationProviderEntity.java
@@ -17,6 +17,8 @@
package org.keycloak.models.jpa.entities;
+import javax.persistence.Access;
+import javax.persistence.AccessType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
@@ -39,6 +41,7 @@ public class UserFederationProviderEntity {
@Id
@Column(name="ID", length = 36)
+ @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL
protected String id;
@ManyToOne(fetch = FetchType.LAZY)
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
index be5cd9d..86bc5a2 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
@@ -124,9 +124,18 @@ public class JpaRealmProvider implements RealmProvider {
.setParameter("realm", realm).executeUpdate();
num = em.createNamedQuery("deleteGroupsByRealm")
.setParameter("realm", realm).executeUpdate();
+
+ TypedQuery<ClientEntity> query = em.createNamedQuery("getClientsByRealm", ClientEntity.class);
+ query.setParameter("realm", realm);
+ List<ClientEntity> clients = query.getResultList();
+ for (ClientEntity a : clients) {
+ adapter.removeClient(a.getId());
+ }
+ /*
for (ClientEntity a : new LinkedList<>(realm.getClients())) {
adapter.removeClient(a.getId());
}
+ */
for (ClientTemplateEntity a : new LinkedList<>(realm.getClientTemplates())) {
adapter.removeClientTemplate(a.getId());
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index d93c44c..4d632ec 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -729,12 +729,14 @@ public class RealmAdapter implements RealmModel {
@Override
public List<ClientModel> getClients() {
- List<ClientModel> list = new ArrayList<ClientModel>();
- if (realm.getClients() == null) return list;
- for (ClientEntity entity : realm.getClients()) {
+ List<ClientModel> list = new LinkedList<>();
+ TypedQuery<ClientEntity> query = em.createNamedQuery("getClientsByRealm", ClientEntity.class);
+ query.setParameter("realm", realm);
+ List<ClientEntity> clients = query.getResultList();
+ for (ClientEntity entity : clients) {
list.add(new ClientAdapter(this, em, session, entity));
}
- return list;
+ return list;
}
@Override
@@ -794,11 +796,9 @@ public class RealmAdapter implements RealmModel {
clientEntity = a;
}
}
- if (client == null) {
- return false;
- }
- em.remove(clientEntity);
+ if (clientEntity == null) return false;
em.createNamedQuery("deleteScopeMappingByClient").setParameter("client", clientEntity).executeUpdate();
+ em.remove(clientEntity);
em.flush();
return true;
@@ -1017,6 +1017,7 @@ public class RealmAdapter implements RealmModel {
entity.setFullSyncPeriod(model.getFullSyncPeriod());
entity.setChangedSyncPeriod(model.getChangedSyncPeriod());
entity.setLastSync(model.getLastSync());
+ entity.setRealm(realm);
em.persist(entity);
realm.getUserFederationProviders().add(entity);
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-1.9.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-1.9.0.xml
old mode 100644
new mode 100755
index 38374e7..11fd15e
--- a/model/jpa/src/main/resources/META-INF/jpa-changelog-1.9.0.xml
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-1.9.0.xml
@@ -60,5 +60,18 @@
<where>ACCESS_TOKEN_LIFE_IMPLICIT is NULL</where>
</update>
+ <dropForeignKeyConstraint baseTableName="REALM_CLIENT" constraintName="FK_93S3P0DIUXAWWQQSA528UBY2Q"/>
+ <dropForeignKeyConstraint baseTableName="REALM_CLIENT" constraintName="FK_M6QGA3RFME47335JY8JXYXH3I"/>
+ <dropUniqueConstraint tableName="REALM_CLIENT" constraintName="UK_M6QGA3RFME47335JY8JXYXH3I" />
+ <dropTable tableName="REALM_CLIENT" />
+ <dropForeignKeyConstraint baseTableName="REALM_CLIENT_TEMPLATE" constraintName="FK_RLM_CLI_TMPLT_RLM" />
+ <dropForeignKeyConstraint baseTableName="REALM_CLIENT_TEMPLATE" constraintName="FK_RLM_CLI_TMPLT_CLI"/>
+ <dropTable tableName="REALM_CLIENT_TEMPLATE" />
+
+ <dropForeignKeyConstraint baseTableName="FED_PROVIDERS" constraintName="FK_213LYQ09FKXQ8K8NY8DY3737T"/>
+ <dropForeignKeyConstraint baseTableName="FED_PROVIDERS" constraintName="FK_DCCIRJLIPU1478VQC89DID88C"/>
+ <dropUniqueConstraint tableName="FED_PROVIDERS" constraintName="UK_DCCIRJLIPU1478VQC89DID88C" />
+ <dropTable tableName="FED_PROVIDERS" />
+
</changeSet>
</databaseChangeLog>
\ No newline at end of file
pom.xml 6(+3 -3)
diff --git a/pom.xml b/pom.xml
old mode 100644
new mode 100755
index 58ba936..6212ca8
--- a/pom.xml
+++ b/pom.xml
@@ -65,7 +65,6 @@
<jboss.spec.javax.xml.bind.jboss-jaxb-api_2.2_spec.version>1.0.4.Final</jboss.spec.javax.xml.bind.jboss-jaxb-api_2.2_spec.version>
<log4j.version>1.2.16</log4j.version>
<resteasy.version>3.0.14.Final</resteasy.version>
- <servlet.api.31.version>1.0.0.Final</servlet.api.31.version>
<slf4j.version>1.7.7</slf4j.version>
<sun.istack.version>2.21</sun.istack.version>
<sun.jaxb.version>2.2.11</sun.jaxb.version>
@@ -88,6 +87,7 @@
<osgi.version>4.2.0</osgi.version>
<pax.web.version>3.1.2</pax.web.version>
<postgresql.version>9.3-1100-jdbc41</postgresql.version>
+ <servlet.api.30.version>1.0.2.Final</servlet.api.30.version>
<twitter4j.version>4.0.4</twitter4j.version>
<!-- Test -->
@@ -303,8 +303,8 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
- <version>${servlet.api.31.version}</version>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+ <version>${servlet.api.30.version}</version>
</dependency>
<dependency>
<groupId>org.picketlink</groupId>
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedClient.java b/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
index 3cd37af..ca3e35b 100755
--- a/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
+++ b/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
@@ -39,43 +39,43 @@ import java.util.TreeMap;
*/
public class CachedClient implements Serializable {
- private String id;
- private String clientId;
- private String name;
- private String description;
- private String realm;
- private Set<String> redirectUris = new HashSet<String>();
- private boolean enabled;
- private String clientAuthenticatorType;
- private String secret;
- private String registrationToken;
- private String protocol;
- private Map<String, String> attributes = new HashMap<String, String>();
- private boolean publicClient;
- private boolean fullScopeAllowed;
- private boolean frontchannelLogout;
- private int notBefore;
- private Set<String> scope = new HashSet<String>();
- private Set<String> webOrigins = new HashSet<String>();
- private Set<ProtocolMapperModel> protocolMappers = new HashSet<ProtocolMapperModel>();
- private boolean surrogateAuthRequired;
- private String managementUrl;
- private String rootUrl;
- private String baseUrl;
- private List<String> defaultRoles = new LinkedList<String>();
- private boolean bearerOnly;
- private boolean consentRequired;
- private boolean standardFlowEnabled;
- private boolean implicitFlowEnabled;
- private boolean directAccessGrantsEnabled;
- private boolean serviceAccountsEnabled;
- private Map<String, String> roles = new HashMap<String, String>();
- private int nodeReRegistrationTimeout;
- private Map<String, Integer> registeredNodes;
- private String clientTemplate;
- private boolean useTemplateScope;
- private boolean useTemplateConfig;
- private boolean useTemplateMappers;
+ protected String id;
+ protected String clientId;
+ protected String name;
+ protected String description;
+ protected String realm;
+ protected Set<String> redirectUris = new HashSet<String>();
+ protected boolean enabled;
+ protected String clientAuthenticatorType;
+ protected String secret;
+ protected String registrationToken;
+ protected String protocol;
+ protected Map<String, String> attributes = new HashMap<String, String>();
+ protected boolean publicClient;
+ protected boolean fullScopeAllowed;
+ protected boolean frontchannelLogout;
+ protected int notBefore;
+ protected Set<String> scope = new HashSet<String>();
+ protected Set<String> webOrigins = new HashSet<String>();
+ protected Set<ProtocolMapperModel> protocolMappers = new HashSet<ProtocolMapperModel>();
+ protected boolean surrogateAuthRequired;
+ protected String managementUrl;
+ protected String rootUrl;
+ protected String baseUrl;
+ protected List<String> defaultRoles = new LinkedList<String>();
+ protected boolean bearerOnly;
+ protected boolean consentRequired;
+ protected boolean standardFlowEnabled;
+ protected boolean implicitFlowEnabled;
+ protected boolean directAccessGrantsEnabled;
+ protected boolean serviceAccountsEnabled;
+ protected Map<String, String> roles = new HashMap<String, String>();
+ protected int nodeReRegistrationTimeout;
+ protected Map<String, Integer> registeredNodes;
+ protected String clientTemplate;
+ protected boolean useTemplateScope;
+ protected boolean useTemplateConfig;
+ protected boolean useTemplateMappers;
public CachedClient(RealmCache cache, RealmProvider delegate, RealmModel realm, ClientModel model) {
id = model.getId();
@@ -112,10 +112,7 @@ public class CachedClient implements Serializable {
implicitFlowEnabled = model.isImplicitFlowEnabled();
directAccessGrantsEnabled = model.isDirectAccessGrantsEnabled();
serviceAccountsEnabled = model.isServiceAccountsEnabled();
- for (RoleModel role : model.getRoles()) {
- roles.put(role.getName(), role.getId());
- cache.addCachedRole(new CachedClientRole(id, role, realm));
- }
+ cacheRoles(cache, realm, model);
nodeReRegistrationTimeout = model.getNodeReRegistrationTimeout();
registeredNodes = new TreeMap<String, Integer>(model.getRegisteredNodes());
@@ -126,6 +123,14 @@ public class CachedClient implements Serializable {
useTemplateMappers = model.useTemplateMappers();
useTemplateScope = model.useTemplateScope();
}
+
+ protected void cacheRoles(RealmCache cache, RealmModel realm, ClientModel model) {
+ for (RoleModel role : model.getRoles()) {
+ roles.put(role.getName(), role.getId());
+ cache.addCachedRole(new CachedClientRole(id, role, realm));
+ }
+ }
+
public String getId() {
return id;
}
diff --git a/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
index dd19d6e..42ed3bc 100755
--- a/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
+++ b/server-spi/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
@@ -53,90 +53,90 @@ import java.util.Set;
*/
public class CachedRealm implements Serializable {
- private String id;
- private String name;
- private String displayName;
- private String displayNameHtml;
- private boolean enabled;
- private SslRequired sslRequired;
- private boolean registrationAllowed;
- private boolean registrationEmailAsUsername;
- private boolean rememberMe;
- private boolean verifyEmail;
- private boolean resetPasswordAllowed;
- private boolean identityFederationEnabled;
- private boolean editUsernameAllowed;
+ protected String id;
+ protected String name;
+ protected String displayName;
+ protected String displayNameHtml;
+ protected boolean enabled;
+ protected SslRequired sslRequired;
+ protected boolean registrationAllowed;
+ protected boolean registrationEmailAsUsername;
+ protected boolean rememberMe;
+ protected boolean verifyEmail;
+ protected boolean resetPasswordAllowed;
+ protected boolean identityFederationEnabled;
+ protected boolean editUsernameAllowed;
//--- brute force settings
- private boolean bruteForceProtected;
- private int maxFailureWaitSeconds;
- private int minimumQuickLoginWaitSeconds;
- private int waitIncrementSeconds;
- private long quickLoginCheckMilliSeconds;
- private int maxDeltaTimeSeconds;
- private int failureFactor;
+ protected boolean bruteForceProtected;
+ protected int maxFailureWaitSeconds;
+ protected int minimumQuickLoginWaitSeconds;
+ protected int waitIncrementSeconds;
+ protected long quickLoginCheckMilliSeconds;
+ protected int maxDeltaTimeSeconds;
+ protected int failureFactor;
//--- end brute force settings
- private boolean revokeRefreshToken;
- private int ssoSessionIdleTimeout;
- private int ssoSessionMaxLifespan;
- private int offlineSessionIdleTimeout;
- private int accessTokenLifespan;
- private int accessTokenLifespanForImplicitFlow;
- private int accessCodeLifespan;
- private int accessCodeLifespanUserAction;
- private int accessCodeLifespanLogin;
- private int notBefore;
- private PasswordPolicy passwordPolicy;
- private OTPPolicy otpPolicy;
-
- private String publicKeyPem;
- private String privateKeyPem;
- private String certificatePem;
- private String codeSecret;
-
- private String loginTheme;
- private String accountTheme;
- private String adminTheme;
- private String emailTheme;
- private String masterAdminClient;
-
- private List<RequiredCredentialModel> requiredCredentials = new ArrayList<RequiredCredentialModel>();
- private List<UserFederationProviderModel> userFederationProviders = new ArrayList<UserFederationProviderModel>();
- private MultivaluedHashMap<String, UserFederationMapperModel> userFederationMappers = new MultivaluedHashMap<String, UserFederationMapperModel>();
- private List<IdentityProviderModel> identityProviders = new ArrayList<IdentityProviderModel>();
-
- private Map<String, String> browserSecurityHeaders = new HashMap<String, String>();
- private Map<String, String> smtpConfig = new HashMap<String, String>();
- private Map<String, AuthenticationFlowModel> authenticationFlows = new HashMap<>();
- private Map<String, AuthenticatorConfigModel> authenticatorConfigs = new HashMap<>();
- private Map<String, RequiredActionProviderModel> requiredActionProviders = new HashMap<>();
- private Map<String, RequiredActionProviderModel> requiredActionProvidersByAlias = new HashMap<>();
- private MultivaluedHashMap<String, AuthenticationExecutionModel> authenticationExecutions = new MultivaluedHashMap<>();
- private Map<String, AuthenticationExecutionModel> executionsById = new HashMap<>();
-
- private AuthenticationFlowModel browserFlow;
- private AuthenticationFlowModel registrationFlow;
- private AuthenticationFlowModel directGrantFlow;
- private AuthenticationFlowModel resetCredentialsFlow;
- private AuthenticationFlowModel clientAuthenticationFlow;
-
- private boolean eventsEnabled;
- private long eventsExpiration;
- private Set<String> eventsListeners = new HashSet<String>();
- private Set<String> enabledEventTypes = new HashSet<String>();
+ protected boolean revokeRefreshToken;
+ protected int ssoSessionIdleTimeout;
+ protected int ssoSessionMaxLifespan;
+ protected int offlineSessionIdleTimeout;
+ protected int accessTokenLifespan;
+ protected int accessTokenLifespanForImplicitFlow;
+ protected int accessCodeLifespan;
+ protected int accessCodeLifespanUserAction;
+ protected int accessCodeLifespanLogin;
+ protected int notBefore;
+ protected PasswordPolicy passwordPolicy;
+ protected OTPPolicy otpPolicy;
+
+ protected String publicKeyPem;
+ protected String privateKeyPem;
+ protected String certificatePem;
+ protected String codeSecret;
+
+ protected String loginTheme;
+ protected String accountTheme;
+ protected String adminTheme;
+ protected String emailTheme;
+ protected String masterAdminClient;
+
+ protected List<RequiredCredentialModel> requiredCredentials = new ArrayList<RequiredCredentialModel>();
+ protected List<UserFederationProviderModel> userFederationProviders = new ArrayList<UserFederationProviderModel>();
+ protected MultivaluedHashMap<String, UserFederationMapperModel> userFederationMappers = new MultivaluedHashMap<String, UserFederationMapperModel>();
+ protected List<IdentityProviderModel> identityProviders = new ArrayList<IdentityProviderModel>();
+
+ protected Map<String, String> browserSecurityHeaders = new HashMap<String, String>();
+ protected Map<String, String> smtpConfig = new HashMap<String, String>();
+ protected Map<String, AuthenticationFlowModel> authenticationFlows = new HashMap<>();
+ protected Map<String, AuthenticatorConfigModel> authenticatorConfigs = new HashMap<>();
+ protected Map<String, RequiredActionProviderModel> requiredActionProviders = new HashMap<>();
+ protected Map<String, RequiredActionProviderModel> requiredActionProvidersByAlias = new HashMap<>();
+ protected MultivaluedHashMap<String, AuthenticationExecutionModel> authenticationExecutions = new MultivaluedHashMap<>();
+ protected Map<String, AuthenticationExecutionModel> executionsById = new HashMap<>();
+
+ protected AuthenticationFlowModel browserFlow;
+ protected AuthenticationFlowModel registrationFlow;
+ protected AuthenticationFlowModel directGrantFlow;
+ protected AuthenticationFlowModel resetCredentialsFlow;
+ protected AuthenticationFlowModel clientAuthenticationFlow;
+
+ protected boolean eventsEnabled;
+ protected long eventsExpiration;
+ protected Set<String> eventsListeners = new HashSet<String>();
+ protected Set<String> enabledEventTypes = new HashSet<String>();
protected boolean adminEventsEnabled;
protected Set<String> adminEnabledEventOperations = new HashSet<String>();
protected boolean adminEventsDetailsEnabled;
- private List<String> defaultRoles = new LinkedList<String>();
- private List<String> defaultGroups = new LinkedList<String>();
- private Set<String> groups = new HashSet<String>();
- private Map<String, String> realmRoles = new HashMap<String, String>();
- private Map<String, String> clients = new HashMap<String, String>();
- private List<String> clientTemplates= new LinkedList<>();
- private boolean internationalizationEnabled;
- private Set<String> supportedLocales = new HashSet<String>();
- private String defaultLocale;
- private MultivaluedHashMap<String, IdentityProviderMapperModel> identityProviderMappers = new MultivaluedHashMap<>();
+ protected List<String> defaultRoles = new LinkedList<String>();
+ protected List<String> defaultGroups = new LinkedList<String>();
+ protected Set<String> groups = new HashSet<String>();
+ protected Map<String, String> realmRoles = new HashMap<String, String>();
+ protected Map<String, String> clients = new HashMap<String, String>();
+ protected List<String> clientTemplates= new LinkedList<>();
+ protected boolean internationalizationEnabled;
+ protected Set<String> supportedLocales = new HashSet<String>();
+ protected String defaultLocale;
+ protected MultivaluedHashMap<String, IdentityProviderMapperModel> identityProviderMappers = new MultivaluedHashMap<>();
public CachedRealm() {
}
@@ -221,23 +221,11 @@ public class CachedRealm implements Serializable {
ClientModel masterAdminClient = model.getMasterAdminClient();
this.masterAdminClient = (masterAdminClient != null) ? masterAdminClient.getId() : null;
- for (RoleModel role : model.getRoles()) {
- realmRoles.put(role.getName(), role.getId());
- CachedRole cachedRole = new CachedRealmRole(role, model);
- cache.addCachedRole(cachedRole);
- }
+ cacheRealmRoles(cache, model);
- for (ClientModel client : model.getClients()) {
- clients.put(client.getClientId(), client.getId());
- CachedClient cachedClient = new CachedClient(cache, delegate, model, client);
- cache.addCachedClient(cachedClient);
- }
+ cacheClients(cache, delegate, model);
- for (ClientTemplateModel template : model.getClientTemplates()) {
- clientTemplates.add(template.getId());
- CachedClientTemplate cachedClient = new CachedClientTemplate(cache, delegate, model, template);
- cache.addCachedClientTemplate(cachedClient);
- }
+ cacheClientTemplates(cache, delegate, model);
internationalizationEnabled = model.isInternationalizationEnabled();
supportedLocales.addAll(model.getSupportedLocales());
@@ -273,6 +261,30 @@ public class CachedRealm implements Serializable {
}
+ protected void cacheClientTemplates(RealmCache cache, RealmProvider delegate, RealmModel model) {
+ for (ClientTemplateModel template : model.getClientTemplates()) {
+ clientTemplates.add(template.getId());
+ CachedClientTemplate cachedClient = new CachedClientTemplate(cache, delegate, model, template);
+ cache.addCachedClientTemplate(cachedClient);
+ }
+ }
+
+ protected void cacheClients(RealmCache cache, RealmProvider delegate, RealmModel model) {
+ for (ClientModel client : model.getClients()) {
+ clients.put(client.getClientId(), client.getId());
+ CachedClient cachedClient = new CachedClient(cache, delegate, model, client);
+ cache.addCachedClient(cachedClient);
+ }
+ }
+
+ protected void cacheRealmRoles(RealmCache cache, RealmModel model) {
+ for (RoleModel role : model.getRoles()) {
+ realmRoles.put(role.getName(), role.getId());
+ CachedRole cachedRole = new CachedRealmRole(role, model);
+ cache.addCachedRole(cachedRole);
+ }
+ }
+
public String getId() {
return id;
diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java b/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java
index 2b2a783..0e2dcbe 100755
--- a/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java
+++ b/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java
@@ -26,4 +26,5 @@ public interface KeycloakTransactionManager extends KeycloakTransaction {
void enlist(KeycloakTransaction transaction);
void enlistAfterCompletion(KeycloakTransaction transaction);
+ void enlistPrepare(KeycloakTransaction transaction);
}
diff --git a/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java b/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
index 57cd49c..9d7c413 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
@@ -82,6 +82,9 @@ public interface UserSessionProvider extends Provider {
void removeClientInitialAccessModel(RealmModel realm, String id);
List<ClientInitialAccessModel> listClientInitialAccess(RealmModel realm);
+ // Will use startup time of this server in non-cluster environment
+ int getClusterStartupTime();
+
void close();
}
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 30f7d5f..8b59053 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -46,7 +46,7 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.representations.idm.AdminEventRepresentation;
import org.keycloak.representations.idm.AuthDetailsRepresentation;
-import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
+import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
@@ -680,12 +680,13 @@ public class ModelToRepresentation {
public static AuthenticationFlowRepresentation toRepresentation(RealmModel realm, AuthenticationFlowModel model) {
AuthenticationFlowRepresentation rep = new AuthenticationFlowRepresentation();
+ rep.setId(model.getId());
rep.setBuiltIn(model.isBuiltIn());
rep.setTopLevel(model.isTopLevel());
rep.setProviderId(model.getProviderId());
rep.setAlias(model.getAlias());
rep.setDescription(model.getDescription());
- rep.setAuthenticationExecutions(new LinkedList<AuthenticationExecutionRepresentation>());
+ rep.setAuthenticationExecutions(new LinkedList<AuthenticationExecutionExportRepresentation>());
for (AuthenticationExecutionModel execution : realm.getAuthenticationExecutions(model.getId())) {
rep.getAuthenticationExecutions().add(toRepresentation(realm, execution));
}
@@ -693,8 +694,8 @@ public class ModelToRepresentation {
}
- public static AuthenticationExecutionRepresentation toRepresentation(RealmModel realm, AuthenticationExecutionModel model) {
- AuthenticationExecutionRepresentation rep = new AuthenticationExecutionRepresentation();
+ public static AuthenticationExecutionExportRepresentation toRepresentation(RealmModel realm, AuthenticationExecutionModel model) {
+ AuthenticationExecutionExportRepresentation rep = new AuthenticationExecutionExportRepresentation();
if (model.getAuthenticatorConfig() != null) {
AuthenticatorConfigModel config = realm.getAuthenticatorConfigById(model.getAuthenticatorConfig());
rep.setAuthenticatorConfig(config.getAlias());
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index f144c28..c9d89b4 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -48,7 +48,9 @@ import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
+import org.keycloak.common.util.UriUtils;
import org.keycloak.representations.idm.ApplicationRepresentation;
+import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
@@ -65,13 +67,13 @@ import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.representations.idm.RolesRepresentation;
import org.keycloak.representations.idm.ScopeMappingRepresentation;
import org.keycloak.representations.idm.SocialLinkRepresentation;
import org.keycloak.representations.idm.UserConsentRepresentation;
import org.keycloak.representations.idm.UserFederationMapperRepresentation;
import org.keycloak.representations.idm.UserFederationProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
-import org.keycloak.common.util.UriUtils;
import java.io.IOException;
import java.util.ArrayList;
@@ -83,7 +85,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
-import org.keycloak.representations.idm.RolesRepresentation;
public class RepresentationToModel {
@@ -442,11 +443,13 @@ public class RepresentationToModel {
}
for (AuthenticationFlowRepresentation flowRep : rep.getAuthenticationFlows()) {
AuthenticationFlowModel model = toModel(flowRep);
+ // make sure new id is generated for new AuthenticationFlowModel instance
+ model.setId(null);
model = newRealm.addAuthenticationFlow(model);
}
for (AuthenticationFlowRepresentation flowRep : rep.getAuthenticationFlows()) {
AuthenticationFlowModel model = newRealm.getFlowByAlias(flowRep.getAlias());
- for (AuthenticationExecutionRepresentation exeRep : flowRep.getAuthenticationExecutions()) {
+ for (AuthenticationExecutionExportRepresentation exeRep : flowRep.getAuthenticationExecutions()) {
AuthenticationExecutionModel execution = toModel(newRealm, exeRep);
execution.setParentFlow(model.getId());
newRealm.addAuthenticatorExecution(execution);
@@ -1464,6 +1467,7 @@ public class RepresentationToModel {
public static AuthenticationFlowModel toModel(AuthenticationFlowRepresentation rep) {
AuthenticationFlowModel model = new AuthenticationFlowModel();
+ model.setId(rep.getId());
model.setBuiltIn(rep.isBuiltIn());
model.setTopLevel(rep.isTopLevel());
model.setProviderId(rep.getProviderId());
@@ -1473,7 +1477,7 @@ public class RepresentationToModel {
}
- public static AuthenticationExecutionModel toModel(RealmModel realm, AuthenticationExecutionRepresentation rep) {
+ public static AuthenticationExecutionModel toModel(RealmModel realm, AuthenticationExecutionExportRepresentation rep) {
AuthenticationExecutionModel model = new AuthenticationExecutionModel();
if (rep.getAuthenticatorConfig() != null) {
AuthenticatorConfigModel config = realm.getAuthenticatorConfigByAlias(rep.getAuthenticatorConfig());
@@ -1490,6 +1494,24 @@ public class RepresentationToModel {
return model;
}
+ public static AuthenticationExecutionModel toModel(RealmModel realm, AuthenticationExecutionRepresentation rep) {
+ AuthenticationExecutionModel model = new AuthenticationExecutionModel();
+ model.setId(rep.getId());
+ model.setFlowId(rep.getFlowId());
+
+ model.setAuthenticator(rep.getAuthenticator());
+ model.setPriority(rep.getPriority());
+ model.setParentFlow(rep.getParentFlow());
+ model.setAuthenticatorFlow(rep.isAutheticatorFlow());
+ model.setRequirement(AuthenticationExecutionModel.Requirement.valueOf(rep.getRequirement()));
+
+ if (rep.getAuthenticatorConfig() != null) {
+ AuthenticatorConfigModel cfg = realm.getAuthenticatorConfigByAlias(rep.getAuthenticatorConfig());
+ model.setAuthenticatorConfig(cfg.getId());
+ }
+ return model;
+ }
+
public static AuthenticatorConfigModel toModel(AuthenticatorConfigRepresentation rep) {
AuthenticatorConfigModel model = new AuthenticatorConfigModel();
model.setAlias(rep.getAlias());
services/pom.xml 2(+1 -1)
diff --git a/services/pom.xml b/services/pom.xml
index 425ef53..c2d4392 100755
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -63,7 +63,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
</dependency>
<dependency>
<groupId>org.twitter4j</groupId>
diff --git a/services/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java b/services/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java
index 471b32b..d881382 100755
--- a/services/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java
+++ b/services/src/main/java/org/keycloak/connections/httpclient/DefaultHttpClientFactory.java
@@ -119,8 +119,10 @@ public class DefaultHttpClientFactory implements HttpClientFactory {
if (httpClient == null) {
long socketTimeout = config.getLong("socket-timeout-millis", -1L);
long establishConnectionTimeout = config.getLong("establish-connection-timeout-millis", -1L);
- int maxPooledPerRoute = config.getInt("max-pooled-per-route", 0);
- int connectionPoolSize = config.getInt("connection-pool-size", 200);
+ int maxPooledPerRoute = config.getInt("max-pooled-per-route", 64);
+ int connectionPoolSize = config.getInt("connection-pool-size", 128);
+ long connectionTTL = config.getLong("connection-ttl-millis", -1L);
+ long maxConnectionIdleTime = config.getLong("max-connection-idle-time-millis", 900000L);
boolean disableCookies = config.getBoolean("disable-cookies", true);
String clientKeystore = config.get("client-keystore");
String clientKeystorePassword = config.get("client-keystore-password");
@@ -139,6 +141,8 @@ public class DefaultHttpClientFactory implements HttpClientFactory {
.establishConnectionTimeout(establishConnectionTimeout, TimeUnit.MILLISECONDS)
.maxPooledPerRoute(maxPooledPerRoute)
.connectionPoolSize(connectionPoolSize)
+ .connectionTTL(connectionTTL, TimeUnit.MILLISECONDS)
+ .maxConnectionIdleTime(maxConnectionIdleTime, TimeUnit.MILLISECONDS)
.disableCookies(disableCookies);
if (disableTrustManager) {
diff --git a/services/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java b/services/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java
index 8c5dd4b..e4ac52b 100755
--- a/services/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java
+++ b/services/src/main/java/org/keycloak/connections/httpclient/HttpClientBuilder.java
@@ -92,10 +92,12 @@ public class HttpClientBuilder {
protected boolean disableTrustManager;
protected HostnameVerificationPolicy policy = HostnameVerificationPolicy.WILDCARD;
protected SSLContext sslContext;
- protected int connectionPoolSize = 100;
- protected int maxPooledPerRoute = 0;
+ protected int connectionPoolSize = 128;
+ protected int maxPooledPerRoute = 64;
protected long connectionTTL = -1;
protected TimeUnit connectionTTLUnit = TimeUnit.MILLISECONDS;
+ protected long maxConnectionIdleTime = 900000;
+ protected TimeUnit maxConnectionIdleTimeUnit = TimeUnit.MILLISECONDS;
protected HostnameVerifier verifier = null;
protected long socketTimeout = -1;
protected TimeUnit socketTimeoutUnits = TimeUnit.MILLISECONDS;
@@ -138,6 +140,12 @@ public class HttpClientBuilder {
return this;
}
+ public HttpClientBuilder maxConnectionIdleTime(long maxConnectionIdleTime, TimeUnit unit) {
+ this.maxConnectionIdleTime = maxConnectionIdleTime;
+ this.maxConnectionIdleTimeUnit = unit;
+ return this;
+ }
+
public HttpClientBuilder maxPooledPerRoute(int maxPooledPerRoute) {
this.maxPooledPerRoute = maxPooledPerRoute;
return this;
@@ -272,7 +280,14 @@ public class HttpClientBuilder {
.setDefaultRequestConfig(requestConfig)
.setSSLSocketFactory(sslsf)
.setMaxConnTotal(connectionPoolSize)
- .setMaxConnPerRoute(maxPooledPerRoute);
+ .setMaxConnPerRoute(maxPooledPerRoute)
+ .setConnectionTimeToLive(connectionTTL, connectionTTLUnit);
+
+ if (maxConnectionIdleTime > 0) {
+ // Will start background cleaner thread
+ builder.evictIdleConnections(maxConnectionIdleTime, maxConnectionIdleTimeUnit);
+ }
+
if (disableCookies) builder.disableCookieManagement();
return builder.build();
} catch (Exception e) {
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 45dc043..e4174d9 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -193,9 +193,9 @@ public class TokenManager {
int currentTime = Time.currentTime();
if (realm.isRevokeRefreshToken()) {
- int serverStartupTime = (int)(session.getKeycloakSessionFactory().getServerStartupTimestamp() / 1000);
+ int clusterStartupTime = session.sessions().getClusterStartupTime();
- if (refreshToken.getIssuedAt() < validation.clientSession.getTimestamp() && (serverStartupTime != validation.clientSession.getTimestamp())) {
+ if (refreshToken.getIssuedAt() < validation.clientSession.getTimestamp() && (clusterStartupTime != validation.clientSession.getTimestamp())) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale token");
}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java
index b317600..e9651a9 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java
@@ -17,11 +17,11 @@
package org.keycloak.services.clientregistration;
-import org.jboss.resteasy.spi.NotFoundException;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.KeycloakSession;
import org.keycloak.services.ErrorResponseException;
+import javax.ws.rs.NotFoundException;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context;
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java
index 23ae6fa..fca6a9e 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java
@@ -30,6 +30,7 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
public static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
+ private List<KeycloakTransaction> prepare = new LinkedList<KeycloakTransaction>();
private List<KeycloakTransaction> transactions = new LinkedList<KeycloakTransaction>();
private List<KeycloakTransaction> afterCompletion = new LinkedList<KeycloakTransaction>();
private boolean active;
@@ -54,6 +55,15 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
}
@Override
+ public void enlistPrepare(KeycloakTransaction transaction) {
+ if (active && !transaction.isActive()) {
+ transaction.begin();
+ }
+
+ prepare.add(transaction);
+ }
+
+ @Override
public void begin() {
if (active) {
throw new IllegalStateException("Transaction already active");
@@ -69,6 +79,17 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
@Override
public void commit() {
RuntimeException exception = null;
+ for (KeycloakTransaction tx : prepare) {
+ try {
+ tx.commit();
+ } catch (RuntimeException e) {
+ exception = exception == null ? e : exception;
+ }
+ }
+ if (exception != null) {
+ rollback(exception);
+ return;
+ }
for (KeycloakTransaction tx : transactions) {
try {
tx.commit();
@@ -105,6 +126,10 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
@Override
public void rollback() {
RuntimeException exception = null;
+ rollback(exception);
+ }
+
+ protected void rollback(RuntimeException exception) {
for (KeycloakTransaction tx : transactions) {
try {
tx.rollback();
diff --git a/services/src/main/java/org/keycloak/services/resources/AbstractSecuredLocalService.java b/services/src/main/java/org/keycloak/services/resources/AbstractSecuredLocalService.java
index 8306972..45df982 100755
--- a/services/src/main/java/org/keycloak/services/resources/AbstractSecuredLocalService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AbstractSecuredLocalService.java
@@ -21,6 +21,7 @@ import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.AbstractOAuthClient;
import org.keycloak.common.ClientConnection;
import org.keycloak.OAuth2Constants;
+import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
@@ -113,13 +114,15 @@ public abstract class AbstractSecuredLocalService {
throw new BadRequestException("state not specified");
}
- URI uri = getBaseRedirectUri();
- URI redirectUri = path != null ? uri.resolve(path) : uri;
+ KeycloakUriBuilder redirect = KeycloakUriBuilder.fromUri(getBaseRedirectUri());
+ if (path != null) {
+ redirect.path(path);
+ }
if (referrer != null) {
- redirectUri = redirectUri.resolve("?referrer=" + referrer);
+ redirect.queryParam("referrer", referrer);
}
- return Response.status(302).location(redirectUri).build();
+ return Response.status(302).location(redirect.build()).build();
} finally {
}
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
index cf7024f..6c9bba9 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
@@ -34,9 +34,17 @@ import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderFactory;
+import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
+import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
+import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
+import org.keycloak.representations.idm.AuthenticatorConfigInfoRepresentation;
+import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
import org.keycloak.representations.idm.ConfigPropertyRepresentation;
+import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
import org.keycloak.services.ErrorResponse;
import org.keycloak.services.ServicesLogger;
import org.keycloak.utils.CredentialHelper;
@@ -83,108 +91,6 @@ public class AuthenticationManagementResource {
this.adminEvent = adminEvent;
}
- public static class AuthenticationExecutionRepresentation {
- protected String id;
- protected String requirement;
- protected String displayName;
- protected List<String> requirementChoices;
- protected Boolean configurable;
- protected Boolean authenticationFlow;
- protected String providerId;
- protected String authenticationConfig;
- protected String flowId;
- protected int level;
- protected int index;
-
- public String getId() {
- return id;
- }
-
- public void setId(String execution) {
- this.id = execution;
- }
-
- public String getDisplayName() {
- return displayName;
- }
-
- public void setDisplayName(String displayName) {
- this.displayName = displayName;
- }
-
- public String getRequirement() {
- return requirement;
- }
-
- public void setRequirement(String requirement) {
- this.requirement = requirement;
- }
-
- public List<String> getRequirementChoices() {
- return requirementChoices;
- }
-
- public void setRequirementChoices(List<String> requirementChoices) {
- this.requirementChoices = requirementChoices;
- }
-
- public Boolean getConfigurable() {
- return configurable;
- }
-
- public void setConfigurable(Boolean configurable) {
- this.configurable = configurable;
- }
-
- public String getProviderId() {
- return providerId;
- }
-
- public void setProviderId(String providerId) {
- this.providerId = providerId;
- }
-
- public String getAuthenticationConfig() {
- return authenticationConfig;
- }
-
- public void setAuthenticationConfig(String authenticationConfig) {
- this.authenticationConfig = authenticationConfig;
- }
-
- public Boolean getAuthenticationFlow() {
- return authenticationFlow;
- }
-
- public void setAuthenticationFlow(Boolean authenticationFlow) {
- this.authenticationFlow = authenticationFlow;
- }
-
- public int getLevel() {
- return level;
- }
-
- public void setLevel(int level) {
- this.level = level;
- }
-
- public int getIndex() {
- return index;
- }
-
- public void setIndex(int index) {
- this.index = index;
- }
-
- public String getFlowId() {
- return flowId;
- }
-
- public void setFlowId(String flowId) {
- this.flowId = flowId;
- }
- }
-
/**
* Get form providers
*
@@ -269,12 +175,12 @@ public class AuthenticationManagementResource {
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
- public List<AuthenticationFlowModel> getFlows() {
+ public List<AuthenticationFlowRepresentation> getFlows() {
this.auth.requireView();
- List<AuthenticationFlowModel> flows = new LinkedList<>();
+ List<AuthenticationFlowRepresentation> flows = new LinkedList<>();
for (AuthenticationFlowModel flow : realm.getAuthenticationFlows()) {
if (flow.isTopLevel()) {
- flows.add(flow);
+ flows.add(ModelToRepresentation.toRepresentation(realm, flow));
}
}
return flows;
@@ -283,27 +189,26 @@ public class AuthenticationManagementResource {
/**
* Create a new authentication flow
*
- * @param model Authentication flow model
+ * @param flow Authentication flow representation
* @return
*/
@Path("/flows")
@POST
@NoCache
@Consumes(MediaType.APPLICATION_JSON)
- public Response createFlow(AuthenticationFlowModel model) {
+ public Response createFlow(AuthenticationFlowRepresentation flow) {
this.auth.requireManage();
- if (model.getAlias() == null || model.getAlias().isEmpty()) {
+ if (flow.getAlias() == null || flow.getAlias().isEmpty()) {
return ErrorResponse.exists("Failed to create flow with empty alias name");
}
- if (realm.getFlowByAlias(model.getAlias()) != null) {
- return ErrorResponse.exists("Flow " + model.getAlias() + " already exists");
+ if (realm.getFlowByAlias(flow.getAlias()) != null) {
+ return ErrorResponse.exists("Flow " + flow.getAlias() + " already exists");
}
- realm.addAuthenticationFlow(model);
+ realm.addAuthenticationFlow(RepresentationToModel.toModel(flow));
return Response.status(201).build();
-
}
/**
@@ -316,14 +221,14 @@ public class AuthenticationManagementResource {
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
- public AuthenticationFlowModel getFlow(@PathParam("id") String id) {
+ public AuthenticationFlowRepresentation getFlow(@PathParam("id") String id) {
this.auth.requireView();
AuthenticationFlowModel flow = realm.getAuthenticationFlowById(id);
if (flow == null) {
throw new NotFoundException("Could not find flow with id");
}
- return flow;
+ return ModelToRepresentation.toRepresentation(realm, flow);
}
/**
@@ -478,8 +383,16 @@ public class AuthenticationManagementResource {
if (parentFlow == null) {
throw new BadRequestException("Parent flow doesn't exists");
}
+ if (parentFlow.isBuiltIn()) {
+ throw new BadRequestException("It is illegal to add execution to a built in flow");
+ }
String provider = data.get("provider");
+ // make sure provider is one of the registered providers
+ ProviderFactory f = session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, provider);
+ if (f == null) {
+ throw new BadRequestException("No authentication provider found for id: " + provider);
+ }
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
execution.setParentFlow(parentFlow.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.DISABLED);
@@ -507,7 +420,7 @@ public class AuthenticationManagementResource {
logger.debug("flow not found: " + flowAlias);
return Response.status(NOT_FOUND).build();
}
- List<AuthenticationExecutionRepresentation> result = new LinkedList<>();
+ List<AuthenticationExecutionInfoRepresentation> result = new LinkedList<>();
int level = 0;
@@ -515,11 +428,11 @@ public class AuthenticationManagementResource {
return Response.ok(result).build();
}
- public void recurseExecutions(AuthenticationFlowModel flow, List<AuthenticationExecutionRepresentation> result, int level) {
+ public void recurseExecutions(AuthenticationFlowModel flow, List<AuthenticationExecutionInfoRepresentation> result, int level) {
int index = 0;
List<AuthenticationExecutionModel> executions = realm.getAuthenticationExecutions(flow.getId());
for (AuthenticationExecutionModel execution : executions) {
- AuthenticationExecutionRepresentation rep = new AuthenticationExecutionRepresentation();
+ AuthenticationExecutionInfoRepresentation rep = new AuthenticationExecutionInfoRepresentation();
rep.setLevel(level);
rep.setIndex(index++);
rep.setRequirementChoices(new LinkedList<String>());
@@ -575,7 +488,7 @@ public class AuthenticationManagementResource {
@PUT
@NoCache
@Consumes(MediaType.APPLICATION_JSON)
- public void updateExecutions(@PathParam("flowAlias") String flowAlias, AuthenticationExecutionRepresentation rep) {
+ public void updateExecutions(@PathParam("flowAlias") String flowAlias, AuthenticationExecutionInfoRepresentation rep) {
this.auth.requireManage();
AuthenticationFlowModel flow = realm.getFlowByAlias(flowAlias);
@@ -599,14 +512,15 @@ public class AuthenticationManagementResource {
/**
* Add new authentication execution
*
- * @param model JSON model describing authentication execution
+ * @param execution JSON model describing authentication execution
*/
@Path("/executions")
@POST
@NoCache
@Consumes(MediaType.APPLICATION_JSON)
- public Response addExecution(AuthenticationExecutionModel model) {
+ public Response addExecution(AuthenticationExecutionRepresentation execution) {
this.auth.requireManage();
+ AuthenticationExecutionModel model = RepresentationToModel.toModel(realm, execution);
AuthenticationFlowModel parentFlow = getParentFlow(model);
if (parentFlow.isBuiltIn()) {
throw new BadRequestException("It is illegal to add execution to a built in flow");
@@ -745,14 +659,14 @@ public class AuthenticationManagementResource {
* Update execution with new configuration
*
* @param execution Execution id
- * @param config JSON with new configuration
+ * @param json JSON with new configuration
* @return
*/
@Path("/executions/{executionId}/config")
@POST
@NoCache
@Consumes(MediaType.APPLICATION_JSON)
- public Response newExecutionConfig(@PathParam("executionId") String execution, AuthenticatorConfigModel config) {
+ public Response newExecutionConfig(@PathParam("executionId") String execution, AuthenticatorConfigRepresentation json) {
this.auth.requireManage();
AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(execution);
@@ -761,6 +675,7 @@ public class AuthenticationManagementResource {
throw new NotFoundException("Illegal execution");
}
+ AuthenticatorConfigModel config = RepresentationToModel.toModel(json);
config = realm.addAuthenticatorConfig(config);
model.setAuthenticatorConfig(config.getId());
realm.updateAuthenticatorExecution(model);
@@ -777,63 +692,14 @@ public class AuthenticationManagementResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
- public AuthenticatorConfigModel getAuthenticatorConfig(@PathParam("executionId") String execution,@PathParam("id") String id) {
+ public AuthenticatorConfigRepresentation getAuthenticatorConfig(@PathParam("executionId") String execution,@PathParam("id") String id) {
this.auth.requireView();
AuthenticatorConfigModel config = realm.getAuthenticatorConfigById(id);
if (config == null) {
throw new NotFoundException("Could not find authenticator config");
}
- return config;
- }
-
-
- public static class RequiredActionProviderRepresentation {
- private String alias;
- private String name;
- private boolean enabled;
- private boolean defaultAction;
- private Map<String, String> config = new HashMap<String, String>();
-
- public String getAlias() {
- return alias;
- }
-
- public void setAlias(String alias) {
- this.alias = alias;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public boolean isEnabled() {
- return enabled;
- }
-
- public void setEnabled(boolean enabled) {
- this.enabled = enabled;
- }
-
- public boolean isDefaultAction() {
- return defaultAction;
- }
-
- public void setDefaultAction(boolean defaultAction) {
- this.defaultAction = defaultAction;
- }
-
- public Map<String, String> getConfig() {
- return config;
- }
-
- public void setConfig(Map<String, String> config) {
- this.config = config;
- }
+ return ModelToRepresentation.toRepresentation(config);
}
/**
@@ -976,47 +842,6 @@ public class AuthenticationManagementResource {
realm.removeRequiredActionProvider(model);
}
- public class AuthenticatorConfigDescription {
- protected String name;
- protected String providerId;
- protected String helpText;
-
- protected List<ConfigPropertyRepresentation> properties;
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public String getHelpText() {
- return helpText;
- }
-
- public String getProviderId() {
- return providerId;
- }
-
- public void setProviderId(String providerId) {
- this.providerId = providerId;
- }
-
- public void setHelpText(String helpText) {
- this.helpText = helpText;
- }
-
- public List<ConfigPropertyRepresentation> getProperties() {
- return properties;
- }
-
- public void setProperties(List<ConfigPropertyRepresentation> properties) {
- this.properties = properties;
- }
- }
-
-
/**
* Get authenticator provider's configuration description
*/
@@ -1024,13 +849,13 @@ public class AuthenticationManagementResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
- public AuthenticatorConfigDescription getAuthenticatorConfigDescription(@PathParam("providerId") String providerId) {
+ public AuthenticatorConfigInfoRepresentation getAuthenticatorConfigDescription(@PathParam("providerId") String providerId) {
this.auth.requireView();
ConfigurableAuthenticatorFactory factory = CredentialHelper.getConfigurableAuthenticatorFactory(session, providerId);
if (factory == null) {
throw new NotFoundException("Could not find authenticator provider");
}
- AuthenticatorConfigDescription rep = new AuthenticatorConfigDescription();
+ AuthenticatorConfigInfoRepresentation rep = new AuthenticatorConfigInfoRepresentation();
rep.setProviderId(providerId);
rep.setName(factory.getDisplayType());
rep.setHelpText(factory.getHelpText());
@@ -1084,14 +909,14 @@ public class AuthenticationManagementResource {
/**
* Create new authenticator configuration
- * @param config JSON describing new authenticator configuration
+ * @param rep JSON describing new authenticator configuration
*/
@Path("config")
@POST
@NoCache
- public Response createAuthenticatorConfig(AuthenticatorConfigModel config) {
+ public Response createAuthenticatorConfig(AuthenticatorConfigRepresentation rep) {
this.auth.requireManage();
- config = realm.addAuthenticatorConfig(config);
+ AuthenticatorConfigModel config = realm.addAuthenticatorConfig(RepresentationToModel.toModel(rep));
return Response.created(uriInfo.getAbsolutePathBuilder().path(config.getId()).build()).build();
}
@@ -1103,14 +928,14 @@ public class AuthenticationManagementResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
@NoCache
- public AuthenticatorConfigModel getAuthenticatorConfig(@PathParam("id") String id) {
+ public AuthenticatorConfigRepresentation getAuthenticatorConfig(@PathParam("id") String id) {
this.auth.requireView();
AuthenticatorConfigModel config = realm.getAuthenticatorConfigById(id);
if (config == null) {
throw new NotFoundException("Could not find authenticator config");
}
- return config;
+ return ModelToRepresentation.toRepresentation(config);
}
/**
@@ -1143,25 +968,21 @@ public class AuthenticationManagementResource {
/**
* Update authenticator configuration
* @param id Configuration id
- * @param config JSON describing new state of authenticator configuration
+ * @param rep JSON describing new state of authenticator configuration
*/
@Path("config/{id}")
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@NoCache
- public void updateAuthenticatorConfig(@PathParam("id") String id, AuthenticatorConfigModel config) {
+ public void updateAuthenticatorConfig(@PathParam("id") String id, AuthenticatorConfigRepresentation rep) {
this.auth.requireManage();
AuthenticatorConfigModel exists = realm.getAuthenticatorConfigById(id);
if (exists == null) {
throw new NotFoundException("Could not find authenticator config");
}
- exists.setAlias(config.getAlias());
- exists.setConfig(config.getConfig());
+ exists.setAlias(rep.getAlias());
+ exists.setConfig(rep.getConfig());
realm.updateAuthenticatorConfig(exists);
}
-
-
-
-
}
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index 6fb06f6..4b49d79 100644
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -91,7 +91,6 @@ public class KeycloakApplication extends Application {
singletons.add(new ObjectMapperResolver(Boolean.parseBoolean(System.getProperty("keycloak.jsonPrettyPrint", "false"))));
migrateModel();
- sessionFactory.publish(new PostMigrationEvent());
boolean bootstrapAdminUser = false;
@@ -138,6 +137,8 @@ public class KeycloakApplication extends Application {
session.close();
}
+ sessionFactory.publish(new PostMigrationEvent());
+
singletons.add(new WelcomeResource(bootstrapAdminUser));
setupScheduledTasks(sessionFactory);
diff --git a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
index 1845501..1d39612 100755
--- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
@@ -84,23 +84,6 @@ public class RealmsResource {
return uriInfo.getBaseUriBuilder().path(RealmsResource.class).path(RealmsResource.class, "getBrokerService");
}
- @Path("{realm}/login-status-iframe.html")
- @Deprecated
- public Object getLoginStatusIframe(final @PathParam("realm") String name,
- @QueryParam("client_id") String client_id,
- @QueryParam("origin") String origin) {
- RealmModel realm = init(name);
-
- EventBuilder event = new EventBuilder(realm, session, clientConnection);
-
- LoginProtocolFactory factory = (LoginProtocolFactory)session.getKeycloakSessionFactory().getProviderFactory(LoginProtocol.class, OIDCLoginProtocol.LOGIN_PROTOCOL);
- OIDCLoginProtocolService endpoint = (OIDCLoginProtocolService)factory.createProtocolEndpoint(realm, event);
-
- ResteasyProviderFactory.getInstance().injectProperties(endpoint);
- return endpoint.getLoginStatusIframe();
-
- }
-
@Path("{realm}/protocol/{protocol}")
public Object getProtocol(final @PathParam("realm") String name,
final @PathParam("protocol") String protocol) {
@@ -115,13 +98,6 @@ public class RealmsResource {
return endpoint;
}
- @Path("{realm}/tokens")
- @Deprecated
- public Object getTokenService(final @PathParam("realm") String name) {
- // for backward compatibility.
- return getProtocol(name, "openid-connect");
- }
-
@Path("{realm}/login-actions")
public LoginActionsService getLoginActionsService(final @PathParam("realm") String name) {
RealmModel realm = init(name);
@@ -131,7 +107,7 @@ public class RealmsResource {
return service;
}
- @Path("{realm}/clients")
+ @Path("{realm}/clients-registrations")
public ClientRegistrationService getClientsService(final @PathParam("realm") String name) {
RealmModel realm = init(name);
EventBuilder event = new EventBuilder(realm, session, clientConnection);
testsuite/integration/pom.xml 2(+1 -1)
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index d9331f9..8dafd44 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -57,7 +57,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.ws.rs</groupId>
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index 4f08b9b..8d9e85d 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -774,4 +774,27 @@ public class AccountTest {
Assert.assertEquals(0, thirdPartyEntry.getProtocolMappersGranted().size());
}
+ @Test
+ public void loginToSpecificPage() {
+ changePasswordPage.open();
+ loginPage.login("test-user@localhost", "password");
+
+ Assert.assertTrue(changePasswordPage.isCurrent());
+
+ events.clear();
+ }
+
+ @Test
+ public void loginToSpecificPageWithReferrer() {
+ driver.navigate().to(changePasswordPage.getPath() + "?referrer=account");
+ System.out.println(driver.getCurrentUrl());
+
+ loginPage.login("test-user@localhost", "password");
+ System.out.println(driver.getCurrentUrl());
+
+ Assert.assertTrue(changePasswordPage.isCurrent());
+
+ events.clear();
+ }
+
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java
index 2f52649..f510f04 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java
@@ -18,6 +18,7 @@
package org.keycloak.testsuite.admin;
import org.jboss.logging.Logger;
+import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.keycloak.admin.client.Keycloak;
@@ -30,6 +31,8 @@ import javax.ws.rs.core.Response;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@@ -39,19 +42,55 @@ import static org.junit.Assert.*;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
-@Ignore
public class ConcurrencyTest extends AbstractClientTest {
private static final Logger log = Logger.getLogger(ConcurrencyTest.class);
- private static final int DEFAULT_THREADS = 3;
- private static final int DEFAULT_ITERATIONS = 10;
+ private static final int DEFAULT_THREADS = 1;
+ private static final int DEFAULT_ITERATIONS = 5;
// If enabled only one request is allowed at the time. Useful for checking that test is working.
private static final boolean SYNCHRONIZED = false;
+ boolean passedCreateClient = false;
+ boolean passedCreateRole = false;
+
+ //@Test
+ public void testAllConcurrently() throws Throwable {
+ Thread client = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ createClient();
+ passedCreateClient = true;
+ } catch (Throwable throwable) {
+ throw new RuntimeException(throwable);
+ }
+ }
+ });
+ Thread role = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ createRole();
+ passedCreateRole = true;
+ } catch (Throwable throwable) {
+ throw new RuntimeException(throwable);
+ }
+ }
+ });
+
+ client.start();
+ role.start();
+ client.join();
+ role.join();
+ Assert.assertTrue(passedCreateClient);
+ Assert.assertTrue(passedCreateRole);
+ }
+
@Test
public void createClient() throws Throwable {
+ long start = System.currentTimeMillis();
run(new KeycloakRunnable() {
@Override
public void run(Keycloak keycloak, RealmResource realm, int threadNum, int iterationNum) {
@@ -75,10 +114,14 @@ public class ConcurrencyTest extends AbstractClientTest {
}
}
});
+ long end = System.currentTimeMillis() - start;
+ System.out.println("createClient took " + end);
+
}
@Test
public void createRole() throws Throwable {
+ long start = System.currentTimeMillis();
run(new KeycloakRunnable() {
@Override
public void run(Keycloak keycloak, RealmResource realm, int threadNum, int iterationNum) {
@@ -88,16 +131,22 @@ public class ConcurrencyTest extends AbstractClientTest {
assertNotNull(realm.roles().get(name).toRepresentation());
}
});
+ long end = System.currentTimeMillis() - start;
+ System.out.println("createRole took " + end);
+
}
@Test
public void createClientRole() throws Throwable {
+ long start = System.currentTimeMillis();
ClientRepresentation c = new ClientRepresentation();
c.setClientId("client");
Response response = realm.clients().create(c);
final String clientId = ApiUtil.getCreatedId(response);
response.close();
+ System.out.println("*********************************************");
+
run(new KeycloakRunnable() {
@Override
public void run(Keycloak keycloak, RealmResource realm, int threadNum, int iterationNum) {
@@ -110,6 +159,10 @@ public class ConcurrencyTest extends AbstractClientTest {
assertNotNull(client.roles().get(name).toRepresentation());
}
});
+ long end = System.currentTimeMillis() - start;
+ System.out.println("createClientRole took " + end);
+ System.out.println("*********************************************");
+
}
private void run(final KeycloakRunnable runnable) throws Throwable {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KeycloakSPNegoSchemeFactory.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KeycloakSPNegoSchemeFactory.java
index 5dbffd1..23fd276 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KeycloakSPNegoSchemeFactory.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KeycloakSPNegoSchemeFactory.java
@@ -48,7 +48,7 @@ public class KeycloakSPNegoSchemeFactory extends SPNegoSchemeFactory {
public KeycloakSPNegoSchemeFactory(CommonKerberosConfig kerberosConfig) {
- super(true);
+ super(true, false);
this.kerberosConfig = kerberosConfig;
}
@@ -61,14 +61,14 @@ public class KeycloakSPNegoSchemeFactory extends SPNegoSchemeFactory {
@Override
public AuthScheme newInstance(HttpParams params) {
- return new KeycloakSPNegoScheme(isStripPort());
+ return new KeycloakSPNegoScheme(isStripPort(), isUseCanonicalHostname());
}
public class KeycloakSPNegoScheme extends SPNegoScheme {
- public KeycloakSPNegoScheme(boolean stripPort) {
- super(stripPort);
+ public KeycloakSPNegoScheme(boolean stripPort, boolean useCanonicalHostname) {
+ super(stripPort, useCanonicalHostname);
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java
index 929de9b..2d763b6 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java
@@ -83,7 +83,7 @@ public class UserSessionInitializerTest {
// Create and persist offline sessions
int started = Time.currentTime();
- int serverStartTime = (int)(session.getKeycloakSessionFactory().getServerStartupTimestamp() / 1000);
+ int serverStartTime = session.sessions().getClusterStartupTime();
for (UserSessionModel origSession : origSessions) {
UserSessionModel userSession = session.sessions().getUserSession(realm, origSession.getId());
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java
index 7231b23..1e1da65 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountPasswordPage.java
@@ -58,7 +58,7 @@ public class AccountPasswordPage extends AbstractAccountPage {
}
public boolean isCurrent() {
- return driver.getTitle().contains("Account Management") && driver.getCurrentUrl().endsWith("/account/password");
+ return driver.getTitle().contains("Account Management") && driver.getCurrentUrl().split("\\?")[0].endsWith("/account/password");
}
public void open() {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/InfinispanCLI.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/InfinispanCLI.java
index 22b1026..b29b610 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/InfinispanCLI.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/InfinispanCLI.java
@@ -100,10 +100,9 @@ public class InfinispanCLI {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String line;
+ System.out.print("$ ");
try {
while ((line = reader.readLine()) != null) {
- log.info("Command: " + line);
-
String[] splits = line.split(" ");
String commandName = splits[0];
Class<? extends AbstractCommand> commandClass = commands.get(commandName);
@@ -128,6 +127,8 @@ public class InfinispanCLI {
log.error(ex);
}
}
+
+ System.out.print("$ ");
}
} finally {
log.info("Exit infinispan CLI");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/UserCommands.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/UserCommands.java
index 950b9a4..9bde852 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/UserCommands.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/UserCommands.java
@@ -18,14 +18,19 @@
package org.keycloak.testsuite.util.cli;
import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Set;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.KeycloakSessionTask;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -34,24 +39,59 @@ public class UserCommands {
public static class Create extends AbstractCommand {
+ private String usernamePrefix;
+ private String password;
+ private String realmName;
+ private String roleNames;
+
@Override
public String getName() {
return "createUsers";
}
+ private class StateHolder {
+ int firstInThisBatch;
+ int countInThisBatch;
+ int remaining;
+ };
+
@Override
protected void doRunCommand(KeycloakSession session) {
- String usernamePrefix = getArg(0);
- String password = getArg(1);
- String realmName = getArg(2);
+ usernamePrefix = getArg(0);
+ password = getArg(1);
+ realmName = getArg(2);
int first = getIntArg(3);
int count = getIntArg(4);
- String roleNames = getArg(5);
+ int batchCount = getIntArg(5);
+ roleNames = getArg(6);
+
+ final StateHolder state = new StateHolder();
+ state.firstInThisBatch = first;
+ state.remaining = count;
+ state.countInThisBatch = Math.min(batchCount, state.remaining);
+ while (state.remaining > 0) {
+ KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), new KeycloakSessionTask() {
+
+ @Override
+ public void run(KeycloakSession session) {
+ createUsersInBatch(session, state.firstInThisBatch, state.countInThisBatch);
+ }
+ });
+
+ // update state
+ state.firstInThisBatch = state.firstInThisBatch + state.countInThisBatch;
+ state.remaining = state.remaining - state.countInThisBatch;
+ state.countInThisBatch = Math.min(batchCount, state.remaining);
+ }
+
+ log.infof("Command finished. All users from %s to %s created", usernamePrefix + first, usernamePrefix + (first + count - 1));
+ }
+ private void createUsersInBatch(KeycloakSession session, int first, int count) {
RealmModel realm = session.realms().getRealmByName(realmName);
if (realm == null) {
log.errorf("Unknown realm: %s", realmName);
- return;
+ throw new HandledException();
}
Set<RoleModel> roles = findRoles(realm, roleNames);
@@ -74,8 +114,11 @@ public class UserCommands {
@Override
public String printUsage() {
- return super.printUsage() + " <username-prefix> <password> <realm-name> <starting-user-offset> <count> <realm-roles-list>. \nRoles list is divided by comma (client roles not yet supported)>\n" +
- "Example usage: " + super.printUsage() + " test test demo 0 20 user,admin";
+ return super.printUsage() + " <username-prefix> <password> <realm-name> <starting-user-offset> <total-count> <batch-size> <realm-roles-list>. " +
+ "\n'total-count' refers to total count of newly created users. 'batch-size' refers to number of created users in each transaction. 'starting-user-offset' refers to starting username offset." +
+ "\nFor example if 'starting-user-offset' is 15 and total-count is 10 and username-prefix is 'test', it will create users test15, test16, test17, ... , test24" +
+ "\nRoles list is divided by comma (client roles are referenced with format <client-id>/<role-name> )>\n" +
+ "Example usage: " + super.printUsage() + " test test demo 0 500 100 user,admin";
}
private Set<RoleModel> findRoles(RealmModel realm, String rolesList) {
@@ -200,10 +243,25 @@ public class UserCommands {
if (user == null) {
log.infof("User '%s' doesn't exist in realm '%s'", username, realmName);
} else {
- log.infof("User: ID: '%s', username: '%s', mail: '%s'", user.getId(), user.getUsername(), user.getEmail());
+ List<String> roleMappings = getRoleMappings(session, realm, user);
+ log.infof("User: ID: '%s', username: '%s', mail: '%s', roles: '%s'", user.getId(), user.getUsername(), user.getEmail(), roleMappings.toString());
}
}
+ private List<String> getRoleMappings(KeycloakSession session, RealmModel realm, UserModel user) {
+ Set<RoleModel> roles = user.getRoleMappings();
+ List<String> result = new LinkedList<>();
+ for (RoleModel role : roles) {
+ if (role.getContainer() instanceof RealmModel) {
+ result.add(role.getName());
+ } else {
+ ClientModel client = (ClientModel) role.getContainer();
+ result.add(client.getClientId() + "/" + role.getName());
+ }
+ }
+ return result;
+ }
+
@Override
public String printUsage() {
return super.printUsage() + " <realm-name> <username>";
diff --git a/testsuite/integration/src/test/resources/log4j.properties b/testsuite/integration/src/test/resources/log4j.properties
index 68b1ba6..91c9646 100755
--- a/testsuite/integration/src/test/resources/log4j.properties
+++ b/testsuite/integration/src/test/resources/log4j.properties
@@ -30,7 +30,9 @@ log4j.logger.org.keycloak=info
# log4j.logger.org.keycloak.services.DefaultKeycloakSessionFactory=debug
# log4j.logger.org.keycloak.provider.ProviderManager=debug
# log4j.logger.org.keycloak.provider.FileSystemProviderLoaderFactory=debug
-
+#log4j.logger.org.infinispan.transaction.impl.TransactionCoordinator=OFF
+#log4j.logger.org.infinispan.transaction.tm.DummyTransaction=OFF
+#log4j.logger.org.infinispan.container.entries.RepeatableReadEntry=OFF
# Broker logging
keycloak.testsuite.logging.level=info
log4j.logger.org.keycloak.testsuite=${keycloak.testsuite.logging.level}
diff --git a/testsuite/integration/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
index 401fc6b..a2f93cd 100755
--- a/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
+++ b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
@@ -32,12 +32,6 @@
}
},
- "realmCache": {
- "infinispan" : {
- "enabled": true
- }
- },
-
"timer": {
"provider": "basic"
},
@@ -84,8 +78,16 @@
}
},
+ "realmCache": {
+ "provider": "infinispan-locking",
+ "infinispan-locking" : {
+ "enabled": true
+ }
+ },
+
"connectionsInfinispan": {
- "default": {
+ "provider": "locking",
+ "locking": {
"clustered": "${keycloak.connectionsInfinispan.clustered:false}",
"async": "${keycloak.connectionsInfinispan.async:true}",
"sessionsOwners": "${keycloak.connectionsInfinispan.sessionsOwners:2}"
testsuite/integration-arquillian/README.md 21(+16 -5)
diff --git a/testsuite/integration-arquillian/README.md b/testsuite/integration-arquillian/README.md
index 5f7f831..39168de 100644
--- a/testsuite/integration-arquillian/README.md
+++ b/testsuite/integration-arquillian/README.md
@@ -13,17 +13,28 @@ other options are: `auth-server-wildfly` and `auth-server-eap7`. The values corr
**Note 1:** For the non-default options it's necessary to build a corresponding server module prior to running any of the test modules.
This can be done by building the server module directly (from `servers/wildfly`/`servers/eap7`),
or by activating `auth-server-wildfly`/`auth-server-eap7` profile when building from the top level module.
-(The profiles will also set the proper value of the `auth.server.container` property.)
**Note 2:** Most server-side configurations are done during the build of the server module
and included in the output artifact - which is then consumed by the test modules( if a corresponding profile is activated).
To reflect a change in server config in the test (e.g. a datasource) it's necessary to rebuild the server module after each change.
-### Migration
+#### Migration
-Migration tests can be enabled by setting `-Dmigrated.auth.server.container` property or activating a corresponding profile.
-When enabled, the `AuthServerTestEnricher` class will start/stop the selected *migrated* instance
-even **before** the *current* auth server instance is started.
+Migration tests can be enabled by setting `-Dmigrated.auth.server.version` property. Supported versions can be found at the bottom of `tests/pom.xml`.
+When enabled, the `AuthServerTestEnricher` class will start and stop the selected migrated instance
+*before* the current auth server instance is started.
+
+#### Cluster Setup
+
+Cluster setup can be enabled with profile `auth-server-wildfly-cluster`.
+(It is also necessary to build the server modules with this profile before running the test. See *Notes 1 and 2* above.)
+
+Clustering tests require MULTICAST to be enabled on machine's `loopback` network interface.
+This can be done by running the following commands under root privileges:
+```
+route add -net 224.0.0.0 netmask 240.0.0.0 dev lo
+ifconfig lo multicast
+```
### App Servers
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authentication/AbstractAuthenticationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authentication/AbstractAuthenticationTest.java
new file mode 100644
index 0000000..4911793
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authentication/AbstractAuthenticationTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.authentication;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.keycloak.admin.client.resource.AuthenticationManagementResource;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
+import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
+import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public abstract class AbstractAuthenticationTest extends AbstractKeycloakTest {
+
+ static final String REALM_NAME = "test";
+
+ static final String REQUIRED = "REQUIRED";
+ static final String OPTIONAL = "OPTIONAL";
+ static final String DISABLED = "DISABLED";
+ static final String ALTERNATIVE = "ALTERNATIVE";
+
+ RealmResource realmResource;
+ AuthenticationManagementResource authMgmtResource;
+
+ @Before
+ public void before() {
+ realmResource = adminClient.realms().realm(REALM_NAME);
+ authMgmtResource = realmResource.flows();
+ }
+
+ @Override
+ public void addTestRealms(List<RealmRepresentation> testRealms) {
+ RealmRepresentation testRealmRep = new RealmRepresentation();
+ testRealmRep.setRealm(REALM_NAME);
+ testRealmRep.setEnabled(true);
+ testRealms.add(testRealmRep);
+ }
+
+
+ AuthenticationExecutionInfoRepresentation findExecutionByProvider(String provider, List<AuthenticationExecutionInfoRepresentation> executions) {
+ for (AuthenticationExecutionInfoRepresentation exec : executions) {
+ if (provider.equals(exec.getProviderId())) {
+ return exec;
+ }
+ }
+ return null;
+ }
+
+
+ AuthenticationFlowRepresentation findFlowByAlias(String alias, List<AuthenticationFlowRepresentation> flows) {
+ for (AuthenticationFlowRepresentation flow : flows) {
+ if (alias.equals(flow.getAlias())) {
+ return flow;
+ }
+ }
+ return null;
+ }
+
+ void compareExecution(AuthenticationExecutionInfoRepresentation expected, AuthenticationExecutionInfoRepresentation actual) {
+ Assert.assertEquals("Execution requirement - " + actual.getProviderId(), expected.getRequirement(), actual.getRequirement());
+ Assert.assertEquals("Execution display name - " + actual.getProviderId(), expected.getDisplayName(), actual.getDisplayName());
+ Assert.assertEquals("Execution configurable - " + actual.getProviderId(), expected.getConfigurable(), actual.getConfigurable());
+ Assert.assertEquals("Execution provider id - " + actual.getProviderId(), expected.getProviderId(), actual.getProviderId());
+ Assert.assertEquals("Execution level - " + actual.getProviderId(), expected.getLevel(), actual.getLevel());
+ Assert.assertEquals("Execution index - " + actual.getProviderId(), expected.getIndex(), actual.getIndex());
+ Assert.assertEquals("Execution authentication flow - " + actual.getProviderId(), expected.getAuthenticationFlow(), actual.getAuthenticationFlow());
+ Assert.assertEquals("Execution requirement choices - " + actual.getProviderId(), expected.getRequirementChoices(), actual.getRequirementChoices());
+ }
+
+ void compareFlows(AuthenticationFlowRepresentation expected, AuthenticationFlowRepresentation actual) {
+ Assert.assertEquals("Flow alias", expected.getAlias(), actual.getAlias());
+ Assert.assertEquals("Flow description", expected.getDescription(), actual.getDescription());
+ Assert.assertEquals("Flow providerId", expected.getProviderId(), actual.getProviderId());
+ Assert.assertEquals("Flow top level", expected.isTopLevel(), actual.isTopLevel());
+ Assert.assertEquals("Flow built-in", expected.isBuiltIn(), actual.isBuiltIn());
+ }
+
+
+ AuthenticationFlowRepresentation newFlow(String alias, String description,
+ String providerId, boolean topLevel, boolean builtIn) {
+ AuthenticationFlowRepresentation flow = new AuthenticationFlowRepresentation();
+ flow.setAlias(alias);
+ flow.setDescription(description);
+ flow.setProviderId(providerId);
+ flow.setTopLevel(topLevel);
+ flow.setBuiltIn(builtIn);
+ return flow;
+ }
+
+ AuthenticationExecutionInfoRepresentation newExecution(String displayName, String providerId, Boolean configurable,
+ int level, int index, String requirement, Boolean authFlow, String[] choices) {
+
+ AuthenticationExecutionInfoRepresentation execution = new AuthenticationExecutionInfoRepresentation();
+ execution.setRequirement(requirement);
+ execution.setDisplayName(displayName);
+ execution.setConfigurable(configurable);
+ execution.setProviderId(providerId);
+ execution.setLevel(level);
+ execution.setIndex(index);
+ execution.setAuthenticationFlow(authFlow);
+ if (choices != null) {
+ execution.setRequirementChoices(Arrays.asList(choices));
+ }
+ return execution;
+ }
+
+
+ AuthenticatorConfigRepresentation newConfig(String alias, String[] keyvalues) {
+ AuthenticatorConfigRepresentation config = new AuthenticatorConfigRepresentation();
+ config.setAlias(alias);
+
+ if (keyvalues == null) {
+ throw new IllegalArgumentException("keyvalues == null");
+ }
+ if (keyvalues.length % 2 != 0) {
+ throw new IllegalArgumentException("keyvalues should have even number of elements");
+ }
+
+ LinkedHashMap<String, String> params = new LinkedHashMap<>();
+ for (int i = 0; i < keyvalues.length; i += 2) {
+ params.put(keyvalues[i], keyvalues[i + 1]);
+ }
+ config.setConfig(params);
+ return config;
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authentication/ExecutionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authentication/ExecutionTest.java
new file mode 100644
index 0000000..5c76c58
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authentication/ExecutionTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.authentication;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
+import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
+import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
+
+import javax.ws.rs.BadRequestException;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.Response;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class ExecutionTest extends AbstractAuthenticationTest {
+
+ @Test
+ public void testAddRemoveExecution() {
+
+ // try add execution to built-in flow
+ HashMap<String, String> params = new HashMap<>();
+ params.put("provider", "idp-review-profile");
+ try {
+ authMgmtResource.addExecution("browser", params);
+ Assert.fail("add execution to built-in flow should fail");
+ } catch (BadRequestException expected) {
+ }
+
+ // copy built-in flow so we get a new editable flow
+ params.put("newName", "Copy of browser");
+ Response response = authMgmtResource.copy("browser", params);
+ try {
+ Assert.assertEquals("Copy flow", 201, response.getStatus());
+ } finally {
+ response.close();
+ }
+
+ // add execution using inexistent provider
+ params.put("provider", "test-execution");
+ try {
+ authMgmtResource.addExecution("Copy of browser", params);
+ Assert.fail("add execution with inexistent provider should fail");
+ } catch(BadRequestException expected) {
+ }
+
+ // add execution - should succeed
+ params.put("provider", "idp-review-profile");
+ authMgmtResource.addExecution("Copy of browser", params);
+
+ // check execution was added
+ response = authMgmtResource.getExecutions("Copy of browser");
+ AuthenticationExecutionInfoRepresentation exec;
+ AuthenticationExecutionInfoRepresentation authCookieExec;
+ try {
+ List<AuthenticationExecutionInfoRepresentation> executionReps = response.readEntity(new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {
+ });
+ exec = findExecutionByProvider("idp-review-profile", executionReps);
+ Assert.assertNotNull("idp-review-profile added", exec);
+
+ // we'll need auth-cookie later
+ authCookieExec = findExecutionByProvider("auth-cookie", executionReps);
+ } finally {
+ response.close();
+ }
+
+ compareExecution(newExecution("Review Profile", "idp-review-profile", true, 0, 3, DISABLED, null, new String[]{REQUIRED, DISABLED}), exec);
+
+ // remove execution
+ authMgmtResource.removeExecution(exec.getId());
+
+ // check execution was removed
+ response = authMgmtResource.getExecutions("Copy of browser");
+ try {
+ List<AuthenticationExecutionInfoRepresentation> executionReps = response.readEntity(new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {
+ });
+ exec = findExecutionByProvider("idp-review-profile", executionReps);
+ Assert.assertNull("idp-review-profile removed", exec);
+ } finally {
+ response.close();
+ }
+
+ // now add the execution again using a different method and representation
+
+ // delete auth-cookie
+ authMgmtResource.removeExecution(authCookieExec.getId());
+
+ AuthenticationExecutionRepresentation rep = new AuthenticationExecutionRepresentation();
+ rep.setPriority(10);
+ rep.setAuthenticator("auth-cookie");
+ rep.setRequirement(OPTIONAL);
+
+ response = authMgmtResource.addExecution(rep);
+ try {
+ Assert.assertEquals("added execution missing parent flow", 400, response.getStatus());
+ } finally {
+ response.close();
+ }
+
+ // get Copy of browser flow id, and set it on execution
+ List<AuthenticationFlowRepresentation> flows = authMgmtResource.getFlows();
+ AuthenticationFlowRepresentation flow = findFlowByAlias("Copy of browser", flows);
+ rep.setParentFlow(flow.getId());
+
+ // add execution - should succeed
+ response = authMgmtResource.addExecution(rep);
+ try {
+ Assert.assertEquals("added execution", 201, response.getStatus());
+ } finally {
+ response.close();
+ }
+
+ // check execution was added
+ List<AuthenticationExecutionInfoRepresentation> executions;
+ response = authMgmtResource.getExecutions("Copy of browser");
+ try {
+ executions = response.readEntity(new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {
+ });
+ exec = findExecutionByProvider("auth-cookie", executions);
+ Assert.assertNotNull("auth-cookie added", exec);
+ } finally {
+ response.close();
+ }
+
+ // Note: there is no checking in addExecution if requirement is one of requirementChoices
+ // Thus we can have OPTIONAL which is neither ALTERNATIVE, nor DISABLED
+ compareExecution(newExecution("Cookie", "auth-cookie", false, 0, 2, OPTIONAL, null, new String[]{ALTERNATIVE, DISABLED}), exec);
+ }
+
+ @Test
+ public void testUpdateExecution() {
+
+ // get current auth-cookie execution
+ Response response = authMgmtResource.getExecutions("browser");
+ List<AuthenticationExecutionInfoRepresentation> executionReps = response.readEntity(
+ new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {
+ });
+ AuthenticationExecutionInfoRepresentation exec = findExecutionByProvider("auth-cookie", executionReps);
+
+ Assert.assertEquals("auth-cookie set to ALTERNATIVE", ALTERNATIVE, exec.getRequirement());
+
+ // switch from DISABLED to ALTERNATIVE
+ exec.setRequirement(DISABLED);
+ authMgmtResource.updateExecutions("browser", exec);
+
+ // make sure the change is visible
+ response = authMgmtResource.getExecutions("browser");
+ executionReps = response.readEntity(new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {});
+
+ // get current auth-cookie execution
+ AuthenticationExecutionInfoRepresentation exec2 = findExecutionByProvider("auth-cookie", executionReps);
+ compareExecution(exec, exec2);
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authentication/FlowTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authentication/FlowTest.java
new file mode 100644
index 0000000..f0dc62b
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authentication/FlowTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.authentication;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
+
+import javax.ws.rs.BadRequestException;
+import javax.ws.rs.core.Response;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class FlowTest extends AbstractAuthenticationTest {
+
+ @Test
+ public void testAddRemoveFlow() {
+
+ // test that built-in flow cannot be deleted
+ List<AuthenticationFlowRepresentation> flows = authMgmtResource.getFlows();
+ for (AuthenticationFlowRepresentation flow : flows) {
+ try {
+ authMgmtResource.deleteFlow(flow.getId());
+ Assert.fail("deleteFlow should fail for built in flow");
+ } catch (BadRequestException e) {
+ break;
+ }
+ }
+
+ // try create new flow using alias of already existing flow
+ Response response = authMgmtResource.createFlow(newFlow("browser", "Browser flow", "basic-flow", true, false));
+ try {
+ Assert.assertEquals("createFlow using the alias of existing flow should fail", 409, response.getStatus());
+ } finally {
+ response.close();
+ }
+
+ // create new flow that should succeed
+ AuthenticationFlowRepresentation newFlow = newFlow("browser-2", "Browser flow", "basic-flow", true, false);
+ response = authMgmtResource.createFlow(newFlow);
+ try {
+ Assert.assertEquals("createFlow success", 201, response.getStatus());
+ } finally {
+ response.close();
+ }
+
+ // check that new flow is returned
+ flows = authMgmtResource.getFlows();
+ AuthenticationFlowRepresentation found = findFlowByAlias("browser-2", flows);
+
+ Assert.assertNotNull("created flow visible", found);
+ compareFlows(newFlow, found);
+
+ // delete non-built-in flow
+ authMgmtResource.deleteFlow(found.getId());
+
+ // check the deleted flow is no longer returned
+ flows = authMgmtResource.getFlows();
+ found = findFlowByAlias("browser-2", flows);
+ Assert.assertNull("flow deleted", found);
+ }
+
+
+ @Test
+ public void testCopyFlow() {
+
+ HashMap<String, String> params = new HashMap<>();
+ params.put("newName", "clients");
+
+ // copy using existing alias as new name
+ Response response = authMgmtResource.copy("browser", params);
+ try {
+ Assert.assertEquals("Copy flow using the new alias of existing flow should fail", 409, response.getStatus());
+ } finally {
+ response.close();
+ }
+
+ // copy non-existing flow
+ params.clear();
+ response = authMgmtResource.copy("non-existent", params);
+ try {
+ Assert.assertEquals("Copy non-existing flow", 404, response.getStatus());
+ } finally {
+ response.close();
+ }
+
+ // copy that should succeed
+ params.put("newName", "Copy of browser");
+ response = authMgmtResource.copy("browser", params);
+ try {
+ Assert.assertEquals("Copy flow", 201, response.getStatus());
+ } finally {
+ response.close();
+ }
+
+ // compare original flow with a copy - fields should be the same except id, alias, and builtIn
+ List<AuthenticationFlowRepresentation> flows = authMgmtResource.getFlows();
+ AuthenticationFlowRepresentation browser = findFlowByAlias("browser", flows);
+ AuthenticationFlowRepresentation copyOfBrowser = findFlowByAlias("Copy of browser", flows);
+
+ Assert.assertNotNull(browser);
+ Assert.assertNotNull(copyOfBrowser);
+
+ // adjust expected values before comparing
+ browser.setAlias("Copy of browser");
+ browser.setBuiltIn(false);
+ compareFlows(browser, copyOfBrowser);
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authentication/InitialFlowsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authentication/InitialFlowsTest.java
new file mode 100644
index 0000000..f1f341e
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authentication/InitialFlowsTest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.authentication;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
+import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
+import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
+
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.Response;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class InitialFlowsTest extends AbstractAuthenticationTest {
+
+ private HashMap<String, AuthenticatorConfigRepresentation> configs = new HashMap<>();
+ private HashMap<String, AuthenticatorConfigRepresentation> expectedConfigs = new HashMap<>();
+
+ {
+ expectedConfigs.put("idp-review-profile", newConfig("review profile config", new String[]{"update.profile.on.first.login", "missing"}));
+ expectedConfigs.put("idp-create-user-if-unique", newConfig("create unique user config", new String[]{"require.password.update.after.registration", "false"}));
+ }
+
+ @Test
+ public void testInitialFlows() {
+
+ List<FlowExecutions> result = new LinkedList<>();
+
+ // get all flows
+ List<AuthenticationFlowRepresentation> flows = authMgmtResource.getFlows();
+ for (AuthenticationFlowRepresentation flow : flows) {
+ // get all executions for flow
+ Response executions = authMgmtResource.getExecutions(flow.getAlias());
+ List<AuthenticationExecutionInfoRepresentation> executionReps = executions.readEntity(new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {
+ });
+
+ for (AuthenticationExecutionInfoRepresentation exec : executionReps) {
+ // separately load referenced configurations
+ String configId = exec.getAuthenticationConfig();
+ if (configId != null && !configs.containsKey(configId)) {
+ configs.put(configId, authMgmtResource.getAuthenticatorConfig(exec.getId(), configId));
+ }
+ }
+ result.add(new FlowExecutions(flow, executionReps));
+ }
+
+ // make sure received flows and their details are as expected
+ compare(expectedFlows(), orderAlphabetically(result));
+ }
+
+ private void compare(List<FlowExecutions> expected, List<FlowExecutions> actual) {
+ Assert.assertEquals("Flow count", expected.size(), actual.size());
+ Iterator<FlowExecutions> it1 = expected.iterator();
+ Iterator<FlowExecutions> it2 = actual.iterator();
+ while (it1.hasNext()) {
+ FlowExecutions fe1 = it1.next();
+ FlowExecutions fe2 = it2.next();
+
+ compareFlows(fe1.flow, fe2.flow);
+ compareExecutions(fe1.executions, fe2.executions);
+ }
+ }
+
+
+ private void compareExecutions(List<AuthenticationExecutionInfoRepresentation> expected, List<AuthenticationExecutionInfoRepresentation> actual) {
+ Assert.assertEquals("Executions count", expected.size(), actual.size());
+ Iterator<AuthenticationExecutionInfoRepresentation> it1 = expected.iterator();
+ Iterator<AuthenticationExecutionInfoRepresentation> it2 = actual.iterator();
+ while (it1.hasNext()) {
+ AuthenticationExecutionInfoRepresentation exe1 = it1.next();
+ AuthenticationExecutionInfoRepresentation exe2 = it2.next();
+
+ compareExecutionWithConfig(exe1, exe2);
+ }
+ }
+
+ private void compareExecutionWithConfig(AuthenticationExecutionInfoRepresentation expected, AuthenticationExecutionInfoRepresentation actual) {
+ super.compareExecution(expected, actual);
+ compareAuthConfig(expected, actual);
+ }
+
+ private void compareAuthConfig(AuthenticationExecutionInfoRepresentation expected, AuthenticationExecutionInfoRepresentation actual) {
+ AuthenticatorConfigRepresentation cfg1 = expectedConfigs.get(expected.getProviderId());
+ AuthenticatorConfigRepresentation cfg2 = configs.get(actual.getAuthenticationConfig());
+
+ if (cfg1 == null && cfg2 == null) {
+ return;
+ }
+ Assert.assertEquals("Execution configuration alias", cfg1.getAlias(), cfg2.getAlias());
+ Assert.assertEquals("Execution configuration params", cfg1.getConfig(), cfg2.getConfig());
+ }
+
+ private List<FlowExecutions> orderAlphabetically(List<FlowExecutions> result) {
+ List<FlowExecutions> sorted = new ArrayList<>(result);
+ Collections.sort(sorted);
+ return sorted;
+ }
+
+ private LinkedList<FlowExecutions> expectedFlows() {
+ LinkedList<FlowExecutions> expected = new LinkedList<>();
+
+ AuthenticationFlowRepresentation flow = newFlow("browser", "browser based authentication", "basic-flow", true, true);
+ List<AuthenticationExecutionInfoRepresentation> executions = new LinkedList<>();
+ executions.add(newExecution("Cookie", "auth-cookie", false, 0, 0, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED}));
+ executions.add(newExecution("Kerberos", "auth-spnego", false, 0, 1, DISABLED, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED}));
+ executions.add(newExecution("forms", null, false, 0, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED}));
+ executions.add(newExecution("Username Password Form", "auth-username-password-form", false, 1, 0, REQUIRED, null, new String[]{REQUIRED}));
+ executions.add(newExecution("OTP Form", "auth-otp-form", false, 1, 1, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED}));
+ expected.add(new FlowExecutions(flow, executions));
+
+ flow = newFlow("clients", "Base authentication for clients", "client-flow", true, true);
+ executions = new LinkedList<>();
+ executions.add(newExecution("Client Id and Secret", "client-secret", false, 0, 0, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED}));
+ executions.add(newExecution("Signed Jwt", "client-jwt", false, 0, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED}));
+ expected.add(new FlowExecutions(flow, executions));
+
+ flow = newFlow("direct grant", "OpenID Connect Resource Owner Grant", "basic-flow", true, true);
+ executions = new LinkedList<>();
+ executions.add(newExecution("Username Validation", "direct-grant-validate-username", false, 0, 0, REQUIRED, null, new String[]{REQUIRED}));
+ executions.add(newExecution("Password", "direct-grant-validate-password", false, 0, 1, REQUIRED, null, new String[]{REQUIRED, DISABLED}));
+ executions.add(newExecution("OTP", "direct-grant-validate-otp", false, 0, 2, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED}));
+ expected.add(new FlowExecutions(flow, executions));
+
+ flow = newFlow("first broker login", "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
+ "basic-flow", true, true);
+ executions = new LinkedList<>();
+ executions.add(newExecution("Review Profile", "idp-review-profile", true, 0, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED}));
+ executions.add(newExecution("Create User If Unique", "idp-create-user-if-unique", true, 0, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED}));
+ executions.add(newExecution("Handle Existing Account", null, false, 0, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED}));
+ executions.add(newExecution("Confirm link existing account", "idp-confirm-link", false, 1, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED}));
+ executions.add(newExecution("Verify existing account by Email", "idp-email-verification", false, 1, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, REQUIRED, DISABLED}));
+ executions.add(newExecution("Verify Existing Account by Re-authentication", null, false, 1, 2, ALTERNATIVE, true, new String[]{ALTERNATIVE, REQUIRED, DISABLED}));
+ executions.add(newExecution("Username Password Form for identity provider reauthentication", "idp-username-password-form", false, 2, 0, REQUIRED, null, new String[]{REQUIRED}));
+ executions.add(newExecution("OTP Form", "auth-otp-form", false, 2, 1, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED}));
+ expected.add(new FlowExecutions(flow, executions));
+
+ flow = newFlow("registration", "registration flow", "basic-flow", true, true);
+ executions = new LinkedList<>();
+ executions.add(newExecution("registration form", "registration-page-form", false, 0, 0, REQUIRED, true, new String[]{REQUIRED, DISABLED}));
+ executions.add(newExecution("Registration User Creation", "registration-user-creation", false, 1, 0, REQUIRED, null, new String[]{REQUIRED, DISABLED}));
+ executions.add(newExecution("Profile Validation", "registration-profile-action", false, 1, 1, REQUIRED, null, new String[]{REQUIRED, DISABLED}));
+ executions.add(newExecution("Password Validation", "registration-password-action", false, 1, 2, REQUIRED, null, new String[]{REQUIRED, DISABLED}));
+ executions.add(newExecution("Recaptcha", "registration-recaptcha-action", true, 1, 3, DISABLED, null, new String[]{REQUIRED, DISABLED}));
+ expected.add(new FlowExecutions(flow, executions));
+
+ flow = newFlow("reset credentials", "Reset credentials for a user if they forgot their password or something", "basic-flow", true, true);
+ executions = new LinkedList<>();
+ executions.add(newExecution("Choose User", "reset-credentials-choose-user", false, 0, 0, REQUIRED, null, new String[]{REQUIRED}));
+ executions.add(newExecution("Send Reset Email", "reset-credential-email", false, 0, 1, REQUIRED, null, new String[]{REQUIRED}));
+ executions.add(newExecution("Reset Password", "reset-password", false, 0, 2, REQUIRED, null, new String[]{REQUIRED, OPTIONAL, DISABLED}));
+ executions.add(newExecution("Reset OTP", "reset-otp", false, 0, 3, OPTIONAL, null, new String[]{REQUIRED, OPTIONAL, DISABLED}));
+ expected.add(new FlowExecutions(flow, executions));
+
+ flow = newFlow("saml ecp", "SAML ECP Profile Authentication Flow", "basic-flow", true, true);
+ executions = new LinkedList<>();
+ executions.add(newExecution(null, "http-basic-authenticator", false, 0, 0, REQUIRED, null, new String[]{}));
+ expected.add(new FlowExecutions(flow, executions));
+
+ return expected;
+ }
+
+ static class FlowExecutions implements Comparable<FlowExecutions> {
+ AuthenticationFlowRepresentation flow;
+ List<AuthenticationExecutionInfoRepresentation> executions;
+
+ FlowExecutions(AuthenticationFlowRepresentation flow, List<AuthenticationExecutionInfoRepresentation> executions) {
+ this.flow = flow;
+ this.executions = executions;
+ }
+
+ @Override
+ public int compareTo(FlowExecutions o) {
+ return flow.getAlias().compareTo(o.flow.getAlias());
+ }
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authentication/InitialProvidersTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authentication/InitialProvidersTest.java
new file mode 100644
index 0000000..d8d7f42
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authentication/InitialProvidersTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.authentication;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class InitialProvidersTest extends AbstractAuthenticationTest {
+
+ @Test
+ public void testAuthenticationProvidersList() {
+
+ List<Map<String, Object>> providers = authMgmtResource.getAuthenticatorProviders();
+ providers = sortProviders(providers);
+
+ compareProviders(expectedAuthProviders(), providers);
+ }
+
+ private void compareProviders(List<Map<String, Object>> expected, List<Map<String, Object>> actual) {
+
+ Assert.assertEquals("Providers count", expected.size(), actual.size());
+
+ Iterator<Map<String, Object>> it1 = expected.iterator();
+ Iterator<Map<String, Object>> it2 = actual.iterator();
+
+ while (it1.hasNext()) {
+ Assert.assertEquals("Provider", it1.next(), it2.next());
+ }
+ }
+
+ private List<Map<String, Object>> expectedAuthProviders() {
+ ArrayList<Map<String, Object>> result = new ArrayList<>();
+ result.add(newClientProvider("auth-conditional-otp-form", "Conditional OTP Form", "Validates a OTP on a separate OTP form. Only shown if required based on the configured conditions."));
+ result.add(newClientProvider("auth-cookie", "Cookie", "Validates the SSO cookie set by the auth server."));
+ result.add(newClientProvider("auth-otp-form", "OTP Form", "Validates a OTP on a separate OTP form."));
+ result.add(newClientProvider("auth-spnego", "Kerberos", "Initiates the SPNEGO protocol. Most often used with Kerberos."));
+ result.add(newClientProvider("auth-username-password-form", "Username Password Form", "Validates a username and password from login form."));
+ result.add(newClientProvider("direct-grant-validate-otp", "OTP", "Validates the one time password supplied as a 'totp' form parameter in direct grant request"));
+ result.add(newClientProvider("direct-grant-validate-password", "Password", "Validates the password supplied as a 'password' form parameter in direct grant request"));
+ result.add(newClientProvider("direct-grant-validate-username", "Username Validation", "Validates the username supplied as a 'username' form parameter in direct grant request"));
+ result.add(newClientProvider("http-basic-authenticator", null, null));
+ result.add(newClientProvider("idp-confirm-link", "Confirm link existing account", "Show the form where user confirms if he wants to link identity provider with existing account or rather edit user profile data retrieved from identity provider to avoid conflict"));
+ result.add(newClientProvider("idp-create-user-if-unique", "Create User If Unique", "Detect if there is existing Keycloak account with same email like identity provider. If no, create new user"));
+ result.add(newClientProvider("idp-email-verification", "Verify existing account by Email", "Email verification of existing Keycloak user, that wants to link his user account with identity provider"));
+ result.add(newClientProvider("idp-review-profile", "Review Profile", "User reviews and updates profile data retrieved from Identity Provider in the displayed form"));
+ result.add(newClientProvider("idp-username-password-form", "Username Password Form for identity provider reauthentication", "Validates a password from login form. Username is already known from identity provider authentication"));
+ result.add(newClientProvider("reset-credential-email", "Send Reset Email", "Send email to user and wait for response."));
+ result.add(newClientProvider("reset-credentials-choose-user", "Choose User", "Choose a user to reset credentials for"));
+ result.add(newClientProvider("reset-otp", "Reset OTP", "Sets the Configure OTP required action if execution is REQUIRED. Will also set it if execution is OPTIONAL and the OTP is currently configured for it."));
+ result.add(newClientProvider("reset-password", "Reset Password", "Sets the Update Password required action if execution is REQUIRED. Will also set it if execution is OPTIONAL and the password is currently configured for it."));
+ return result;
+ }
+
+ private Map<String, Object> newClientProvider(String id, String displayName, String description) {
+ Map<String, Object> obj = new HashMap<>();
+ obj.put("id", id);
+ obj.put("displayName", displayName);
+ obj.put("description", description);
+ return obj;
+ }
+
+ private List<Map<String, Object>> sortProviders(List<Map<String, Object>> providers) {
+ ArrayList<Map<String, Object>> sorted = new ArrayList<>(providers);
+ Collections.sort(sorted, new ProviderComparator());
+ return sorted;
+ }
+
+ private static class ProviderComparator implements Comparator<Map<String, Object>> {
+ @Override
+ public int compare(Map<String, Object> o1, Map<String, Object> o2) {
+ return String.valueOf(o1.get("id")).compareTo(String.valueOf(o2.get("id")));
+ }
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authentication/ShiftExecutionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authentication/ShiftExecutionTest.java
new file mode 100644
index 0000000..3124537
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authentication/ShiftExecutionTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.authentication;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
+
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.Response;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class ShiftExecutionTest extends AbstractAuthenticationTest {
+
+ @Test
+ public void testShiftExecution() {
+
+ // copy built-in flow so we get a new editable flow
+ HashMap<String, String> params = new HashMap<>();
+ params.put("newName", "Copy of browser");
+ Response response = authMgmtResource.copy("browser", params);
+ try {
+ Assert.assertEquals("Copy flow", 201, response.getStatus());
+ } finally {
+ response.close();
+ }
+
+ // get executions
+ response = authMgmtResource.getExecutions("Copy of browser");
+ List<AuthenticationExecutionInfoRepresentation> executions = response.readEntity(new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {
+ });
+
+ AuthenticationExecutionInfoRepresentation last = executions.get(executions.size() - 1);
+ AuthenticationExecutionInfoRepresentation oneButLast = executions.get(executions.size() - 2);
+
+ // shift last execution up
+ authMgmtResource.raisePriority(last.getId());
+
+ response = authMgmtResource.getExecutions("Copy of browser");
+ List<AuthenticationExecutionInfoRepresentation> executions2 = response.readEntity(new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {
+ });
+
+ AuthenticationExecutionInfoRepresentation last2 = executions2.get(executions.size() - 1);
+ AuthenticationExecutionInfoRepresentation oneButLast2 = executions2.get(executions.size() - 2);
+
+ Assert.assertEquals("Execution shifted up - N", last.getId(), oneButLast2.getId());
+ Assert.assertEquals("Execution shifted up - N-1", oneButLast.getId(), last2.getId());
+
+ // shift one before last down
+ authMgmtResource.lowerPriority(oneButLast2.getId());
+
+ response = authMgmtResource.getExecutions("Copy of browser");
+ executions2 = response.readEntity(new GenericType<List<AuthenticationExecutionInfoRepresentation>>() {
+ });
+
+ last2 = executions2.get(executions.size() - 1);
+ oneButLast2 = executions2.get(executions.size() - 2);
+
+ Assert.assertEquals("Execution shifted down - N", last.getId(), last2.getId());
+ Assert.assertEquals("Execution shifted down - N-1", oneButLast.getId(), oneButLast2.getId());
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/AbstractClusterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/AbstractClusterTest.java
index 9dd6089..fc523b7 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/AbstractClusterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/AbstractClusterTest.java
@@ -3,12 +3,15 @@ package org.keycloak.testsuite.cluster;
import java.util.ArrayList;
import java.util.List;
import org.jboss.arquillian.container.test.api.ContainerController;
+import org.jboss.arquillian.graphene.page.Page;
import org.jboss.arquillian.test.api.ArquillianResource;
import static org.junit.Assert.assertTrue;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.models.Constants;
+import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.arquillian.ContainerInfo;
+import org.keycloak.testsuite.auth.page.AuthRealm;
import static org.keycloak.testsuite.auth.page.AuthRealm.ADMIN;
import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER;
@@ -35,25 +38,38 @@ public abstract class AbstractClusterTest extends AbstractKeycloakTest {
controller.start(backendNode.getQualifier());
assertTrue(controller.isStarted(backendNode.getQualifier()));
- log.info("Initializing admin client for: '" + backendNode.getContextRoot() + "/auth'");
- backendAdminClients.add(Keycloak.getInstance(backendNode.getContextRoot() + "/auth",
- MASTER, ADMIN, ADMIN, Constants.ADMIN_CLI_CLIENT_ID));
+ backendAdminClients.add(createAdminClientFor(backendNode));
}
}
- protected ContainerInfo backendInfo(int i) {
+ protected Keycloak createAdminClientFor(ContainerInfo backendNode) {
+ log.info("Initializing admin client for " + backendNode.getContextRoot() + "/auth");
+ return Keycloak.getInstance(backendNode.getContextRoot() + "/auth",
+ MASTER, ADMIN, ADMIN, Constants.ADMIN_CLI_CLIENT_ID);
+ }
+
+ protected ContainerInfo backendNode(int i) {
return suiteContext.getAuthServerBackendsInfo().get(i);
}
protected void startBackendNode(int i) {
- String container = backendInfo(i).getQualifier();
+ String container = backendNode(i).getQualifier();
if (!controller.isStarted(container)) {
controller.start(container);
+ backendAdminClients.set(i, createAdminClientFor(backendNode(i)));
}
}
- protected void stopBackendNode(int i) {
- controller.kill(backendInfo(i).getQualifier());
+ protected void killBackendNode(int i) {
+ backendAdminClients.get(i).close();
+ controller.kill(backendNode(i).getQualifier());
}
+ protected void listRealms(int i) {
+ log.info(String.format("Node %s: AccessTokenString: %s", i + 1, backendAdminClients.get(i).tokenManager().getAccessTokenString()));
+ for (RealmRepresentation r : backendAdminClients.get(i).realms().findAll()) {
+ log.info(String.format("Node %s: Realm: %s, Id: %s", i + 1, r.getRealm(), r.getId()));
+ }
+ }
+
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/AbstractTwoNodeClusterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/AbstractTwoNodeClusterTest.java
new file mode 100644
index 0000000..d88e616
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/AbstractTwoNodeClusterTest.java
@@ -0,0 +1,57 @@
+package org.keycloak.testsuite.cluster;
+
+import org.junit.Before;
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.testsuite.arquillian.ContainerInfo;
+import static org.keycloak.testsuite.util.WaitUtils.pause;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+public abstract class AbstractTwoNodeClusterTest extends AbstractClusterTest {
+
+ @Before
+ public void beforeTwoNodeClusterTest() {
+ startBackendNodes(2);
+ pause(3000);
+ }
+
+ protected ContainerInfo backend1Info() {
+ return backendNode(0);
+ }
+
+ protected ContainerInfo backend2Info() {
+ return backendNode(1);
+ }
+
+ protected Keycloak backend1AdminClient() {
+ return backendAdminClients.get(0);
+ }
+
+ protected Keycloak backend2AdminClient() {
+ return backendAdminClients.get(1);
+ }
+
+ protected void startBackend1() {
+ startBackendNode(0);
+ }
+
+ protected void startBackend2() {
+ startBackendNode(1);
+ }
+
+ protected void failback() {
+ startBackend1();
+ startBackend2();
+ }
+
+ protected void killBackend1() {
+ killBackendNode(0);
+ }
+
+ protected void killBackend2() {
+ killBackendNode(1);
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/SessionFailoverClusterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/SessionFailoverClusterTest.java
new file mode 100644
index 0000000..591e2e8
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cluster/SessionFailoverClusterTest.java
@@ -0,0 +1,95 @@
+package org.keycloak.testsuite.cluster;
+
+import java.util.List;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.keycloak.representations.idm.RealmRepresentation;
+import static org.keycloak.testsuite.auth.page.AuthRealm.ADMIN;
+import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlDoesntStartWith;
+import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
+import static org.keycloak.testsuite.util.WaitUtils.pause;
+import org.openqa.selenium.Cookie;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+public class SessionFailoverClusterTest extends AbstractTwoNodeClusterTest {
+
+ public static final String KEYCLOAK_SESSION_COOKIE = "KEYCLOAK_SESSION";
+ public static final String KEYCLOAK_IDENTITY_COOKIE = "KEYCLOAK_IDENTITY";
+
+ @Override
+ public void addTestRealms(List<RealmRepresentation> testRealms) {
+ }
+
+ @Test
+ @Ignore("work in progress") // only works with owners="2" at the moment
+ public void sessionFailover() {
+
+ // LOGOUT
+ accountPage.navigateTo();
+ driver.navigate().refresh();
+ pause(3000);
+ loginPage.form().login(ADMIN, ADMIN);
+ assertCurrentUrlStartsWith(accountPage);
+
+ Cookie sessionCookie = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
+ assertNotNull(sessionCookie);
+
+ killBackend1();
+
+ // check if session survived backend failure
+
+ driver.navigate().refresh();
+ pause(3000);
+
+ assertCurrentUrlStartsWith(accountPage);
+ Cookie sessionCookieAfterFailover = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
+ assertNotNull(sessionCookieAfterFailover);
+ assertEquals(sessionCookieAfterFailover.getValue(), sessionCookie.getValue());
+
+ failback();
+
+ // check if session survived backend failback
+ driver.navigate().refresh();
+ pause(3000);
+ assertCurrentUrlStartsWith(accountPage);
+ Cookie sessionCookieAfterFailback = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
+ assertNotNull(sessionCookieAfterFailback);
+ assertEquals(sessionCookieAfterFailover.getValue(), sessionCookie.getValue());
+
+ // LOGOUT
+ accountPage.navigateTo();
+ accountPage.signOut();
+
+ assertCurrentUrlDoesntStartWith(accountPage);
+ masterRealmPage.navigateTo();
+ sessionCookie = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
+ assertNull(sessionCookie);
+
+ killBackend1();
+
+ // check if session survived backend failure
+ driver.navigate().refresh();
+ pause(3000);
+ assertCurrentUrlDoesntStartWith(accountPage);
+ masterRealmPage.navigateTo();
+ sessionCookieAfterFailover = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
+ assertNull(sessionCookieAfterFailover);
+
+ failback();
+
+ // check if session survived backend failback
+ driver.navigate().refresh();
+ pause(3000);
+ assertCurrentUrlDoesntStartWith(accountPage);
+ masterRealmPage.navigateTo();
+ sessionCookieAfterFailback = driver.manage().getCookieNamed(KEYCLOAK_SESSION_COOKIE);
+ assertNull(sessionCookieAfterFailback);
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
index 404f5b5..fc91116 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
@@ -72,10 +72,12 @@
<property name="enabled">${auth.server.wildfly.cluster}</property>
<property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
<property name="jbossHome">${keycloak.balancer.home}</property>
- <property name="javaVmArguments">
+ <property name="jbossArguments">
-Djboss.socket.binding.port-offset=${auth.server.port.offset}
- -Xms64m -Xmx512m -XX:MaxPermSize=256m
- ${adapter.test.props}
+ </property>
+ <property name="javaVmArguments">
+ -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m
+ -Djava.net.preferIPv4Stack=true
</property>
<property name="managementPort">${auth.server.management.port}</property>
<property name="startupTimeoutInSeconds">${startup.timeout.sec}</property>
@@ -87,11 +89,14 @@
<property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
<property name="jbossHome">${keycloak.backend1.home}</property>
<property name="serverConfig">standalone-ha.xml</property>
- <property name="javaVmArguments">
+ <property name="jbossArguments">
-Djboss.socket.binding.port-offset=${auth.server.backend1.port.offset}
- -Xms64m -Xmx512m -XX:MaxPermSize=256m
- ${adapter.test.props}
-Djboss.node.name=node1
+ ${adapter.test.props}
+ </property>
+ <property name="javaVmArguments">
+ -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m
+ -Djava.net.preferIPv4Stack=true
</property>
<!--<property name="outputToConsole">false</property>-->
<property name="managementPort">${auth.server.backend1.management.port}</property>
@@ -104,11 +109,14 @@
<property name="adapterImplClass">org.jboss.as.arquillian.container.managed.ManagedDeployableContainer</property>
<property name="jbossHome">${keycloak.backend2.home}</property>
<property name="serverConfig">standalone-ha.xml</property>
- <property name="javaVmArguments">
+ <property name="jbossArguments">
-Djboss.socket.binding.port-offset=${auth.server.backend2.port.offset}
- -Xms64m -Xmx512m -XX:MaxPermSize=256m
- ${adapter.test.props}
-Djboss.node.name=node2
+ ${adapter.test.props}
+ </property>
+ <property name="javaVmArguments">
+ -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m
+ -Djava.net.preferIPv4Stack=true
</property>
<!--<property name="outputToConsole">false</property>-->
<property name="managementPort">${auth.server.backend2.management.port}</property>
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json
old mode 100644
new mode 100755
index 0978231..5cef402
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json
@@ -26,14 +26,6 @@
"provider": "${keycloak.userSessionPersister.provider:jpa}"
},
- "userSessions": {
- "provider" : "${keycloak.userSessions.provider:infinispan}"
- },
-
- "realmCache": {
- "provider": "${keycloak.realm.cache.provider:infinispan}"
- },
-
"userCache": {
"provider": "${keycloak.user.cache.provider:infinispan}",
"mem": {
@@ -41,6 +33,10 @@
}
},
+ "userSessions": {
+ "provider" : "${keycloak.userSessions.provider:infinispan}"
+ },
+
"timer": {
"provider": "basic"
},
@@ -99,6 +95,23 @@
}
},
+ "realmCache": {
+ "provider": "infinispan-locking",
+ "infinispan-locking" : {
+ "enabled": true
+ }
+ },
+
+ "connectionsInfinispan": {
+ "provider": "locking",
+ "locking": {
+ "clustered": "${keycloak.connectionsInfinispan.clustered:false}",
+ "async": "${keycloak.connectionsInfinispan.async:true}",
+ "sessionsOwners": "${keycloak.connectionsInfinispan.sessionsOwners:2}"
+ }
+ },
+
+
"truststore": {
"file": {
"file": "${keycloak.truststore.file:src/main/keystore/keycloak.truststore}",
testsuite/jetty/jetty81/pom.xml 2(+1 -1)
diff --git a/testsuite/jetty/jetty81/pom.xml b/testsuite/jetty/jetty81/pom.xml
index 73dd109..8700028 100755
--- a/testsuite/jetty/jetty81/pom.xml
+++ b/testsuite/jetty/jetty81/pom.xml
@@ -59,7 +59,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.ws.rs</groupId>
testsuite/jetty/jetty91/pom.xml 2(+1 -1)
diff --git a/testsuite/jetty/jetty91/pom.xml b/testsuite/jetty/jetty91/pom.xml
index 1bb34e9..181e575 100755
--- a/testsuite/jetty/jetty91/pom.xml
+++ b/testsuite/jetty/jetty91/pom.xml
@@ -59,7 +59,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.ws.rs</groupId>
testsuite/jetty/jetty92/pom.xml 2(+1 -1)
diff --git a/testsuite/jetty/jetty92/pom.xml b/testsuite/jetty/jetty92/pom.xml
index 76fb153..0a24ecd 100755
--- a/testsuite/jetty/jetty92/pom.xml
+++ b/testsuite/jetty/jetty92/pom.xml
@@ -63,7 +63,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.ws.rs</groupId>
testsuite/proxy/pom.xml 2(+1 -1)
diff --git a/testsuite/proxy/pom.xml b/testsuite/proxy/pom.xml
index 233f4d8..52c83ef 100755
--- a/testsuite/proxy/pom.xml
+++ b/testsuite/proxy/pom.xml
@@ -54,7 +54,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.ws.rs</groupId>
testsuite/tomcat6/pom.xml 2(+1 -1)
diff --git a/testsuite/tomcat6/pom.xml b/testsuite/tomcat6/pom.xml
index 42aeab4..9584809 100755
--- a/testsuite/tomcat6/pom.xml
+++ b/testsuite/tomcat6/pom.xml
@@ -53,7 +53,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.ws.rs</groupId>
testsuite/tomcat7/pom.xml 2(+1 -1)
diff --git a/testsuite/tomcat7/pom.xml b/testsuite/tomcat7/pom.xml
index 8ca978b..4e17e56 100755
--- a/testsuite/tomcat7/pom.xml
+++ b/testsuite/tomcat7/pom.xml
@@ -77,7 +77,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.ws.rs</groupId>
testsuite/tomcat8/pom.xml 2(+1 -1)
diff --git a/testsuite/tomcat8/pom.xml b/testsuite/tomcat8/pom.xml
index 1f9cbb9..8c3248f 100755
--- a/testsuite/tomcat8/pom.xml
+++ b/testsuite/tomcat8/pom.xml
@@ -49,7 +49,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.ws.rs</groupId>
testsuite/wildfly/pom.xml 2(+1 -1)
diff --git a/testsuite/wildfly/pom.xml b/testsuite/wildfly/pom.xml
index e907b70..f4af438 100644
--- a/testsuite/wildfly/pom.xml
+++ b/testsuite/wildfly/pom.xml
@@ -56,7 +56,7 @@
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
- <artifactId>jboss-servlet-api_3.1_spec</artifactId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.ws.rs</groupId>
diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakServerDeploymentProcessor.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakServerDeploymentProcessor.java
old mode 100644
new mode 100755
index 8f86b52..1d2230c
--- a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakServerDeploymentProcessor.java
+++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakServerDeploymentProcessor.java
@@ -62,6 +62,7 @@ public class KeycloakServerDeploymentProcessor implements DeploymentUnitProcesso
st.addDependency(cacheContainerService.append("sessions"));
st.addDependency(cacheContainerService.append("offlineSessions"));
st.addDependency(cacheContainerService.append("loginFailures"));
+ st.addDependency(cacheContainerService.append("realmVersions"));
}
}
diff --git a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml
old mode 100644
new mode 100755
index 57cd94b..74fbae3
--- a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml
+++ b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml
@@ -30,6 +30,9 @@
<local-cache name="sessions"/>
<local-cache name="offlineSessions"/>
<local-cache name="loginFailures"/>
+ <local-cache name="realmVersions">
+ <transaction mode="BATCH" locking="PESSIMISTIC"/>
+ </local-cache>
</cache-container>
<cache-container name="server" default-cache="default" module="org.wildfly.clustering.server">
<local-cache name="default">
@@ -87,6 +90,9 @@
<distributed-cache name="sessions" mode="SYNC" owners="1"/>
<distributed-cache name="offlineSessions" mode="SYNC" owners="1"/>
<distributed-cache name="loginFailures" mode="SYNC" owners="1"/>
+ <local-cache name="realmVersions">
+ <transaction mode="BATCH" locking="PESSIMISTIC"/>
+ </local-cache>
</cache-container>
<cache-container name="server" aliases="singleton cluster" default-cache="default" module="org.wildfly.clustering.server">
<transport lock-timeout="60000"/>