diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml new file mode 100644 index 000000000000..2bf51caca9e8 --- /dev/null +++ b/.github/workflows/ci-docker.yml @@ -0,0 +1,34 @@ +name: Docker CI + +on: + push: + branches: + - master + +jobs: + build: + name: Build and push docker image + runs-on: ubuntu-latest + steps: + - name: Check out keycloak/keycloak-containers + uses: actions/checkout@v2 + with: + repository: keycloak/keycloak-containers + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: primesign-services + password: ${{ secrets.CR_TOKEN }} + - name: Build docker image + uses: docker/build-push-action@v2 + with: + context: server + file: server/Dockerfile + push: true + build-args: GIT_REPO=primesign/keycloak + cache-from: type=registry,ref=primesign/keycloak:latest + cache-to: type=inline + tags: ghcr.io/primesign/keycloak:latest \ No newline at end of file diff --git a/adapters/oidc/adapter-core/pom.xml b/adapters/oidc/adapter-core/pom.xml index 65e4313b9d9b..7b7b98ac61e8 100755 --- a/adapters/oidc/adapter-core/pom.xml +++ b/adapters/oidc/adapter-core/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AuthenticatedActionsHandler.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AuthenticatedActionsHandler.java index 5840543d6627..057da2c69413 100755 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AuthenticatedActionsHandler.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AuthenticatedActionsHandler.java @@ -89,7 +89,8 @@ protected boolean abortTokenResponse() { return true; } // Don't allow a CORS request if we're not validating CORS requests. - if (!deployment.isCors() && facade.getRequest().getHeader(CorsHeaders.ORIGIN) != null) { + String origin = facade.getRequest().getHeader(CorsHeaders.ORIGIN); + if (!deployment.isCors() && origin != null && !origin.equals("null")) { facade.getResponse().setStatus(200); facade.getResponse().end(); return true; @@ -101,6 +102,7 @@ protected boolean corsRequest() { if (!deployment.isCors()) return false; KeycloakSecurityContext securityContext = facade.getSecurityContext(); String origin = facade.getRequest().getHeader(CorsHeaders.ORIGIN); + origin = "null".equals(origin) ? null : origin; String exposeHeaders = deployment.getCorsExposedHeaders(); if (deployment.getPolicyEnforcer() != null) { diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java index f3c3c6abec97..42fdd90905bc 100755 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java @@ -103,13 +103,13 @@ public boolean preflightCors() { if (!facade.getRequest().getMethod().equalsIgnoreCase("OPTIONS")) { return false; } - if (facade.getRequest().getHeader(CorsHeaders.ORIGIN) == null) { + String origin = facade.getRequest().getHeader(CorsHeaders.ORIGIN); + if (origin == null || origin.equals("null")) { log.debug("checkCorsPreflight: no origin header"); return false; } log.debug("Preflight request returning"); facade.getResponse().setStatus(200); - String origin = facade.getRequest().getHeader(CorsHeaders.ORIGIN); facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin); facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); String requestMethods = facade.getRequest().getHeader(CorsHeaders.ACCESS_CONTROL_REQUEST_METHOD); diff --git a/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/RefreshableKeycloakSecurityContextTest.java b/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/RefreshableKeycloakSecurityContextTest.java index 08fd3b34075c..1a3b0101e9a7 100644 --- a/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/RefreshableKeycloakSecurityContextTest.java +++ b/adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/RefreshableKeycloakSecurityContextTest.java @@ -1,8 +1,21 @@ package org.keycloak.adapters; +import org.junit.Assert; import org.junit.Test; +import org.keycloak.KeycloakPrincipal; +import org.keycloak.KeycloakSecurityContext; +import org.keycloak.jose.jws.JWSBuilder; +import org.keycloak.representations.AccessToken; +import org.keycloak.representations.IDToken; import org.keycloak.representations.oidc.TokenMetadataRepresentation; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.security.KeyPair; +import java.security.KeyPairGenerator; + import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -39,4 +52,63 @@ public void sameIssuedAtAsNotBeforeIsActiveKEYCLOAK10013() { token.issuedAt(5000); assertTrue(sut.isActive()); } + + private AccessToken createSimpleToken() { + AccessToken token = new AccessToken(); + token.id("111"); + token.issuer("http://localhost:8080/auth/acme"); + token.addAccess("foo").addRole("admin"); + token.addAccess("bar").addRole("user"); + return token; + } + + @Test + public void testSerialization() throws Exception { + AccessToken token = createSimpleToken(); + IDToken idToken = new IDToken(); + + idToken.setEmail("joe@email.cz"); + + KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); + + String encoded = new JWSBuilder() + .jsonContent(token) + .rsa256(keyPair.getPrivate()); + String encodedIdToken = new JWSBuilder() + .jsonContent(idToken) + .rsa256(keyPair.getPrivate()); + + KeycloakDeployment keycloakDeployment = new KeycloakDeployment(); + keycloakDeployment.setNotBefore(5000); + + KeycloakSecurityContext ctx = new RefreshableKeycloakSecurityContext(keycloakDeployment,null, encoded, token,encodedIdToken, null, null); + KeycloakPrincipal principal = new KeycloakPrincipal("joe", ctx); + + // Serialize + ByteArrayOutputStream bso = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bso); + oos.writeObject(principal); + oos.close(); + + // Deserialize + byte[] bytes = bso.toByteArray(); + ByteArrayInputStream bis = new ByteArrayInputStream(bytes); + ObjectInputStream ois = new ObjectInputStream(bis); + principal = (KeycloakPrincipal)ois.readObject(); + ctx = principal.getKeycloakSecurityContext(); + token = ctx.getToken(); + idToken = ctx.getIdToken(); + + System.out.println("Size of serialized principal: " + bytes.length); + + Assert.assertEquals(encoded, ctx.getTokenString()); + Assert.assertEquals(encodedIdToken, ctx.getIdTokenString()); + Assert.assertEquals("111", token.getId()); + Assert.assertEquals("111", token.getId()); + Assert.assertTrue(token.getResourceAccess("foo").isUserInRole("admin")); + Assert.assertTrue(token.getResourceAccess("bar").isUserInRole("user")); + Assert.assertEquals("joe@email.cz", idToken.getEmail()); + Assert.assertEquals("acme", ctx.getRealm()); + ois.close(); + } } diff --git a/adapters/oidc/as7-eap6/as7-adapter-spi/pom.xml b/adapters/oidc/as7-eap6/as7-adapter-spi/pom.xml index 7fc86b865a6a..7c862f887c39 100755 --- a/adapters/oidc/as7-eap6/as7-adapter-spi/pom.xml +++ b/adapters/oidc/as7-eap6/as7-adapter-spi/pom.xml @@ -21,7 +21,7 @@ keycloak-as7-integration-pom org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml 4.0.0 diff --git a/adapters/oidc/as7-eap6/as7-adapter/pom.xml b/adapters/oidc/as7-eap6/as7-adapter/pom.xml index e8c6c803f59b..4ce28810eef7 100755 --- a/adapters/oidc/as7-eap6/as7-adapter/pom.xml +++ b/adapters/oidc/as7-eap6/as7-adapter/pom.xml @@ -21,7 +21,7 @@ keycloak-as7-integration-pom org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml 4.0.0 diff --git a/adapters/oidc/as7-eap6/as7-subsystem/pom.xml b/adapters/oidc/as7-eap6/as7-subsystem/pom.xml index 72260d2a7b17..59b86f66c3ba 100755 --- a/adapters/oidc/as7-eap6/as7-subsystem/pom.xml +++ b/adapters/oidc/as7-eap6/as7-subsystem/pom.xml @@ -21,7 +21,7 @@ org.keycloak keycloak-as7-integration-pom - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml diff --git a/adapters/oidc/as7-eap6/pom.xml b/adapters/oidc/as7-eap6/pom.xml index 7822ebac35e5..bbde3902f008 100755 --- a/adapters/oidc/as7-eap6/pom.xml +++ b/adapters/oidc/as7-eap6/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml Keycloak AS7 / JBoss EAP 6 Integration diff --git a/adapters/oidc/fuse7/camel-undertow/pom.xml b/adapters/oidc/fuse7/camel-undertow/pom.xml index e86e192529af..57bb68f71600 100644 --- a/adapters/oidc/fuse7/camel-undertow/pom.xml +++ b/adapters/oidc/fuse7/camel-undertow/pom.xml @@ -21,7 +21,7 @@ keycloak-fuse7-integration-pom org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml 4.0.0 diff --git a/adapters/oidc/fuse7/jetty94/pom.xml b/adapters/oidc/fuse7/jetty94/pom.xml index 1e1b2ff12bed..f6e0309af97f 100644 --- a/adapters/oidc/fuse7/jetty94/pom.xml +++ b/adapters/oidc/fuse7/jetty94/pom.xml @@ -21,7 +21,7 @@ keycloak-fuse7-integration-pom org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml 4.0.0 diff --git a/adapters/oidc/fuse7/pom.xml b/adapters/oidc/fuse7/pom.xml index 56a011ef935a..6184b6d539d6 100644 --- a/adapters/oidc/fuse7/pom.xml +++ b/adapters/oidc/fuse7/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/oidc/fuse7/tomcat8/pom.xml b/adapters/oidc/fuse7/tomcat8/pom.xml index 36c21c6da9e4..2cc3e18a2a53 100644 --- a/adapters/oidc/fuse7/tomcat8/pom.xml +++ b/adapters/oidc/fuse7/tomcat8/pom.xml @@ -21,7 +21,7 @@ keycloak-fuse7-integration-pom org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml 4.0.0 diff --git a/adapters/oidc/fuse7/undertow/pom.xml b/adapters/oidc/fuse7/undertow/pom.xml index 74e5208b2564..252559262521 100644 --- a/adapters/oidc/fuse7/undertow/pom.xml +++ b/adapters/oidc/fuse7/undertow/pom.xml @@ -21,7 +21,7 @@ keycloak-fuse7-integration-pom org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml 4.0.0 diff --git a/adapters/oidc/installed/pom.xml b/adapters/oidc/installed/pom.xml index c7dafd3c30b6..a4191cac925c 100755 --- a/adapters/oidc/installed/pom.xml +++ b/adapters/oidc/installed/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/oidc/jaxrs-oauth-client/pom.xml b/adapters/oidc/jaxrs-oauth-client/pom.xml index 03a2408c1016..1b4be4369b3c 100755 --- a/adapters/oidc/jaxrs-oauth-client/pom.xml +++ b/adapters/oidc/jaxrs-oauth-client/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/oidc/jetty/jetty-core/pom.xml b/adapters/oidc/jetty/jetty-core/pom.xml index 3a114076fc0a..11d57c3d22a1 100755 --- a/adapters/oidc/jetty/jetty-core/pom.xml +++ b/adapters/oidc/jetty/jetty-core/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../../pom.xml 4.0.0 diff --git a/adapters/oidc/jetty/jetty9.2/pom.xml b/adapters/oidc/jetty/jetty9.2/pom.xml index 6ebe0c29d29a..88c9af3f082d 100755 --- a/adapters/oidc/jetty/jetty9.2/pom.xml +++ b/adapters/oidc/jetty/jetty9.2/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../../pom.xml 4.0.0 diff --git a/adapters/oidc/jetty/jetty9.3/pom.xml b/adapters/oidc/jetty/jetty9.3/pom.xml index 8f058d973160..993408301732 100644 --- a/adapters/oidc/jetty/jetty9.3/pom.xml +++ b/adapters/oidc/jetty/jetty9.3/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../../pom.xml 4.0.0 diff --git a/adapters/oidc/jetty/jetty9.4/pom.xml b/adapters/oidc/jetty/jetty9.4/pom.xml index 732f0c42f444..8b0f5b1594eb 100644 --- a/adapters/oidc/jetty/jetty9.4/pom.xml +++ b/adapters/oidc/jetty/jetty9.4/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../../pom.xml 4.0.0 diff --git a/adapters/oidc/jetty/pom.xml b/adapters/oidc/jetty/pom.xml index b57b03da6caa..33a963f8c6c8 100755 --- a/adapters/oidc/jetty/pom.xml +++ b/adapters/oidc/jetty/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml Keycloak Jetty Integration diff --git a/adapters/oidc/js/pom.xml b/adapters/oidc/js/pom.xml index 2ccb207228df..8c91d1edc076 100755 --- a/adapters/oidc/js/pom.xml +++ b/adapters/oidc/js/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/oidc/js/src/main/resources/keycloak.js b/adapters/oidc/js/src/main/resources/keycloak.js index 5ba4b6378d37..75fa20fc84f7 100755 --- a/adapters/oidc/js/src/main/resources/keycloak.js +++ b/adapters/oidc/js/src/main/resources/keycloak.js @@ -74,38 +74,6 @@ } } - function toKeycloakPromise(promise) { - promise.__proto__ = KeycloakPromise.prototype; - return promise; - } - - function KeycloakPromise(executor) { - return toKeycloakPromise(new Promise(executor)); - } - - KeycloakPromise.prototype = Object.create(Promise.prototype); - KeycloakPromise.prototype.constructor = KeycloakPromise; - - KeycloakPromise.prototype.success = function(callback) { - logPromiseDeprecation(); - - var promise = this.then(function handleSuccess(value) { - callback(value); - }); - - return toKeycloakPromise(promise); - }; - - KeycloakPromise.prototype.error = function(callback) { - logPromiseDeprecation(); - - var promise = this.catch(function handleError(error) { - callback(error); - }); - - return toKeycloakPromise(promise); - }; - function Keycloak (config) { if (!(this instanceof Keycloak)) { return new Keycloak(config); @@ -362,8 +330,24 @@ } } + function domReady() { + var promise = createPromise(); + + var checkReadyState = function () { + if (document.readyState === 'interactive' || document.readyState === 'complete') { + document.removeEventListener('readystatechange', checkReadyState); + promise.setSuccess(); + } + } + document.addEventListener('readystatechange', checkReadyState); + + checkReadyState(); // just in case the event was already fired and we missed it (in case the init is done later than at the load time, i.e. it's done from code) + + return promise.promise; + } + configPromise.then(function () { - check3pCookiesSupported().then(processInit) + domReady().then(check3pCookiesSupported).then(processInit) .catch(function() { promise.setError(); }); @@ -1053,8 +1037,8 @@ function decodeToken(str) { str = str.split('.')[1]; - str = str.replace('/-/g', '+'); - str = str.replace('/_/g', '/'); + str = str.replace(/-/g, '+'); + str = str.replace(/_/g, '/'); switch (str.length % 4) { case 0: break; @@ -1192,10 +1176,31 @@ p.reject(result); } }; - p.promise = new KeycloakPromise(function(resolve, reject) { + p.promise = new Promise(function(resolve, reject) { p.resolve = resolve; p.reject = reject; }); + + p.promise.success = function(callback) { + logPromiseDeprecation(); + + this.then(function handleSuccess(value) { + callback(value); + }); + + return this; + } + + p.promise.error = function(callback) { + logPromiseDeprecation(); + + this.catch(function handleError(error) { + callback(error); + }); + + return this; + } + return p; } diff --git a/adapters/oidc/kcinit/pom.xml b/adapters/oidc/kcinit/pom.xml index cae2f5151771..f538eeff9dd2 100755 --- a/adapters/oidc/kcinit/pom.xml +++ b/adapters/oidc/kcinit/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/oidc/osgi-adapter/pom.xml b/adapters/oidc/osgi-adapter/pom.xml index b686fba3144f..5990e1ab38cc 100755 --- a/adapters/oidc/osgi-adapter/pom.xml +++ b/adapters/oidc/osgi-adapter/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/oidc/pom.xml b/adapters/oidc/pom.xml index 0d14e170c54b..7c2597913ae2 100755 --- a/adapters/oidc/pom.xml +++ b/adapters/oidc/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../pom.xml Keycloak OIDC Client Adapter Modules diff --git a/adapters/oidc/servlet-filter/pom.xml b/adapters/oidc/servlet-filter/pom.xml index fbb25d65e226..3f168dcb7edc 100755 --- a/adapters/oidc/servlet-filter/pom.xml +++ b/adapters/oidc/servlet-filter/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/oidc/spring-boot-adapter-core/pom.xml b/adapters/oidc/spring-boot-adapter-core/pom.xml index 3b3036e4ace4..fefa0e6e2927 100755 --- a/adapters/oidc/spring-boot-adapter-core/pom.xml +++ b/adapters/oidc/spring-boot-adapter-core/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/oidc/spring-boot-container-bundle/pom.xml b/adapters/oidc/spring-boot-container-bundle/pom.xml index c9ffb1e8e143..9c16c2f2096f 100644 --- a/adapters/oidc/spring-boot-container-bundle/pom.xml +++ b/adapters/oidc/spring-boot-container-bundle/pom.xml @@ -4,7 +4,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml spring-boot-container-bundle diff --git a/adapters/oidc/spring-boot-legacy-container-bundle/pom.xml b/adapters/oidc/spring-boot-legacy-container-bundle/pom.xml index 89b9b8bd74de..5d3761713317 100644 --- a/adapters/oidc/spring-boot-legacy-container-bundle/pom.xml +++ b/adapters/oidc/spring-boot-legacy-container-bundle/pom.xml @@ -4,7 +4,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml spring-boot-legacy-container-bundle diff --git a/adapters/oidc/spring-boot/pom.xml b/adapters/oidc/spring-boot/pom.xml index a64ed0df2b91..1a5941844451 100755 --- a/adapters/oidc/spring-boot/pom.xml +++ b/adapters/oidc/spring-boot/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/oidc/spring-boot2/pom.xml b/adapters/oidc/spring-boot2/pom.xml index 8b794055145d..30b51729ecef 100755 --- a/adapters/oidc/spring-boot2/pom.xml +++ b/adapters/oidc/spring-boot2/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/oidc/spring-security/pom.xml b/adapters/oidc/spring-security/pom.xml index 8d8c8121e74c..662eec9212fe 100755 --- a/adapters/oidc/spring-security/pom.xml +++ b/adapters/oidc/spring-security/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/oidc/tomcat/pom.xml b/adapters/oidc/tomcat/pom.xml index 186ec9fced12..135cf37e37a3 100755 --- a/adapters/oidc/tomcat/pom.xml +++ b/adapters/oidc/tomcat/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml Keycloak Tomcat Integration diff --git a/adapters/oidc/tomcat/tomcat-core/pom.xml b/adapters/oidc/tomcat/tomcat-core/pom.xml index 60aed87fc9cb..88c03baae07a 100755 --- a/adapters/oidc/tomcat/tomcat-core/pom.xml +++ b/adapters/oidc/tomcat/tomcat-core/pom.xml @@ -21,7 +21,7 @@ keycloak-tomcat-integration-pom org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml 4.0.0 diff --git a/adapters/oidc/tomcat/tomcat/pom.xml b/adapters/oidc/tomcat/tomcat/pom.xml index 19b9ce6f8e3b..4dcc03bbcf96 100755 --- a/adapters/oidc/tomcat/tomcat/pom.xml +++ b/adapters/oidc/tomcat/tomcat/pom.xml @@ -21,7 +21,7 @@ keycloak-tomcat-integration-pom org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml 4.0.0 diff --git a/adapters/oidc/tomcat/tomcat7/pom.xml b/adapters/oidc/tomcat/tomcat7/pom.xml index 9965dddd2116..cce5633568fd 100755 --- a/adapters/oidc/tomcat/tomcat7/pom.xml +++ b/adapters/oidc/tomcat/tomcat7/pom.xml @@ -21,7 +21,7 @@ keycloak-tomcat-integration-pom org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml 4.0.0 diff --git a/adapters/oidc/undertow/pom.xml b/adapters/oidc/undertow/pom.xml index 68402e2a6c95..6d417428ced7 100755 --- a/adapters/oidc/undertow/pom.xml +++ b/adapters/oidc/undertow/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/oidc/wildfly-elytron/pom.xml b/adapters/oidc/wildfly-elytron/pom.xml index b55a13c531ca..6b6bc000abad 100755 --- a/adapters/oidc/wildfly-elytron/pom.xml +++ b/adapters/oidc/wildfly-elytron/pom.xml @@ -22,7 +22,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/oidc/wildfly/pom.xml b/adapters/oidc/wildfly/pom.xml index cedacf5c578c..fd7a164f9566 100755 --- a/adapters/oidc/wildfly/pom.xml +++ b/adapters/oidc/wildfly/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml Keycloak WildFly Integration diff --git a/adapters/oidc/wildfly/wildfly-adapter/pom.xml b/adapters/oidc/wildfly/wildfly-adapter/pom.xml index a75694bb85bd..a60b4ace9523 100644 --- a/adapters/oidc/wildfly/wildfly-adapter/pom.xml +++ b/adapters/oidc/wildfly/wildfly-adapter/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../../pom.xml 4.0.0 diff --git a/adapters/oidc/wildfly/wildfly-subsystem/pom.xml b/adapters/oidc/wildfly/wildfly-subsystem/pom.xml index 1b655f638cf2..59a0a1f16cc8 100755 --- a/adapters/oidc/wildfly/wildfly-subsystem/pom.xml +++ b/adapters/oidc/wildfly/wildfly-subsystem/pom.xml @@ -21,7 +21,7 @@ org.keycloak keycloak-parent - 11.0.0-SNAPSHOT + 11.0.3 ../../../../pom.xml diff --git a/adapters/pom.xml b/adapters/pom.xml index c90260e02dde..30a45394bcb0 100755 --- a/adapters/pom.xml +++ b/adapters/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml Keycloak Adapters diff --git a/adapters/saml/as7-eap6/adapter/pom.xml b/adapters/saml/as7-eap6/adapter/pom.xml index d056ca2b2578..708c2054b516 100755 --- a/adapters/saml/as7-eap6/adapter/pom.xml +++ b/adapters/saml/as7-eap6/adapter/pom.xml @@ -21,7 +21,7 @@ keycloak-saml-eap-integration-pom org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml 4.0.0 diff --git a/adapters/saml/as7-eap6/pom.xml b/adapters/saml/as7-eap6/pom.xml index ce0e0cfc0524..9383cfc1e0ed 100755 --- a/adapters/saml/as7-eap6/pom.xml +++ b/adapters/saml/as7-eap6/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml Keycloak SAML EAP Integration diff --git a/adapters/saml/as7-eap6/subsystem/pom.xml b/adapters/saml/as7-eap6/subsystem/pom.xml index b3932dcbb9fa..4acaf798fe99 100755 --- a/adapters/saml/as7-eap6/subsystem/pom.xml +++ b/adapters/saml/as7-eap6/subsystem/pom.xml @@ -21,7 +21,7 @@ org.keycloak keycloak-saml-eap-integration-pom - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml diff --git a/adapters/saml/core-public/pom.xml b/adapters/saml/core-public/pom.xml index 4f103b786796..3263325a9c3f 100755 --- a/adapters/saml/core-public/pom.xml +++ b/adapters/saml/core-public/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/saml/core/pom.xml b/adapters/saml/core/pom.xml index 70c1ef2f7e7b..d4f12ad42fa8 100755 --- a/adapters/saml/core/pom.xml +++ b/adapters/saml/core/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/saml/jetty/jetty-core/pom.xml b/adapters/saml/jetty/jetty-core/pom.xml index 94965b1afa5c..70f5f3882033 100755 --- a/adapters/saml/jetty/jetty-core/pom.xml +++ b/adapters/saml/jetty/jetty-core/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../../pom.xml 4.0.0 diff --git a/adapters/saml/jetty/jetty9.2/pom.xml b/adapters/saml/jetty/jetty9.2/pom.xml index 0602804bfd1b..22a3adbfc62e 100755 --- a/adapters/saml/jetty/jetty9.2/pom.xml +++ b/adapters/saml/jetty/jetty9.2/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../../pom.xml 4.0.0 diff --git a/adapters/saml/jetty/jetty9.3/pom.xml b/adapters/saml/jetty/jetty9.3/pom.xml index 46c16a01e7cb..5c3258effd56 100644 --- a/adapters/saml/jetty/jetty9.3/pom.xml +++ b/adapters/saml/jetty/jetty9.3/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../../pom.xml 4.0.0 diff --git a/adapters/saml/jetty/jetty9.4/pom.xml b/adapters/saml/jetty/jetty9.4/pom.xml index 11807f0b0a06..2379d00b5b7b 100644 --- a/adapters/saml/jetty/jetty9.4/pom.xml +++ b/adapters/saml/jetty/jetty9.4/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../../pom.xml 4.0.0 diff --git a/adapters/saml/jetty/pom.xml b/adapters/saml/jetty/pom.xml index 8c7225b07731..ba26f19fb109 100755 --- a/adapters/saml/jetty/pom.xml +++ b/adapters/saml/jetty/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml Keycloak SAML Jetty Integration diff --git a/adapters/saml/pom.xml b/adapters/saml/pom.xml index 3ef7d9d6f044..be5b84833c03 100755 --- a/adapters/saml/pom.xml +++ b/adapters/saml/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../pom.xml Keycloak SAML Client Adapter Modules diff --git a/adapters/saml/servlet-filter/pom.xml b/adapters/saml/servlet-filter/pom.xml index c7126393a613..d11ba7805184 100755 --- a/adapters/saml/servlet-filter/pom.xml +++ b/adapters/saml/servlet-filter/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/saml/tomcat/pom.xml b/adapters/saml/tomcat/pom.xml index 75c5b9b451cb..9bef0f59b941 100755 --- a/adapters/saml/tomcat/pom.xml +++ b/adapters/saml/tomcat/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml Keycloak SAML Tomcat Integration diff --git a/adapters/saml/tomcat/tomcat-core/pom.xml b/adapters/saml/tomcat/tomcat-core/pom.xml index d2d6e7e324d8..9404141a9ab5 100755 --- a/adapters/saml/tomcat/tomcat-core/pom.xml +++ b/adapters/saml/tomcat/tomcat-core/pom.xml @@ -21,7 +21,7 @@ keycloak-saml-tomcat-integration-pom org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml 4.0.0 diff --git a/adapters/saml/tomcat/tomcat/pom.xml b/adapters/saml/tomcat/tomcat/pom.xml index c089919c722b..93abfc28f339 100755 --- a/adapters/saml/tomcat/tomcat/pom.xml +++ b/adapters/saml/tomcat/tomcat/pom.xml @@ -21,7 +21,7 @@ keycloak-saml-tomcat-integration-pom org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml 4.0.0 diff --git a/adapters/saml/tomcat/tomcat7/pom.xml b/adapters/saml/tomcat/tomcat7/pom.xml index b4a985c55e36..c44263838114 100755 --- a/adapters/saml/tomcat/tomcat7/pom.xml +++ b/adapters/saml/tomcat/tomcat7/pom.xml @@ -21,7 +21,7 @@ keycloak-saml-tomcat-integration-pom org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml 4.0.0 diff --git a/adapters/saml/undertow/pom.xml b/adapters/saml/undertow/pom.xml index 8f4ad60405b7..74f6a49c2845 100755 --- a/adapters/saml/undertow/pom.xml +++ b/adapters/saml/undertow/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/saml/wildfly-elytron/pom.xml b/adapters/saml/wildfly-elytron/pom.xml index b602e9e44948..0804d69f50b2 100755 --- a/adapters/saml/wildfly-elytron/pom.xml +++ b/adapters/saml/wildfly-elytron/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/saml/wildfly/pom.xml b/adapters/saml/wildfly/pom.xml index 0b91605de6bc..af6f5d1d8c24 100755 --- a/adapters/saml/wildfly/pom.xml +++ b/adapters/saml/wildfly/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml Keycloak SAML Wildfly Integration diff --git a/adapters/saml/wildfly/wildfly-adapter/pom.xml b/adapters/saml/wildfly/wildfly-adapter/pom.xml index d5a64670d491..8c4398fa706a 100755 --- a/adapters/saml/wildfly/wildfly-adapter/pom.xml +++ b/adapters/saml/wildfly/wildfly-adapter/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../../pom.xml 4.0.0 diff --git a/adapters/saml/wildfly/wildfly-subsystem/pom.xml b/adapters/saml/wildfly/wildfly-subsystem/pom.xml index 1e7e233eede9..0b9a4e5330f2 100755 --- a/adapters/saml/wildfly/wildfly-subsystem/pom.xml +++ b/adapters/saml/wildfly/wildfly-subsystem/pom.xml @@ -21,7 +21,7 @@ org.keycloak keycloak-parent - 11.0.0-SNAPSHOT + 11.0.3 ../../../../pom.xml diff --git a/adapters/spi/adapter-spi/pom.xml b/adapters/spi/adapter-spi/pom.xml index a6b21f4dddbc..0f5fd6609b93 100755 --- a/adapters/spi/adapter-spi/pom.xml +++ b/adapters/spi/adapter-spi/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/spi/jboss-adapter-core/pom.xml b/adapters/spi/jboss-adapter-core/pom.xml index 58669a9c46c4..d20250514b1f 100755 --- a/adapters/spi/jboss-adapter-core/pom.xml +++ b/adapters/spi/jboss-adapter-core/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/spi/jetty-adapter-spi/pom.xml b/adapters/spi/jetty-adapter-spi/pom.xml index fe842b7699fe..b1c8e410f099 100755 --- a/adapters/spi/jetty-adapter-spi/pom.xml +++ b/adapters/spi/jetty-adapter-spi/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/spi/pom.xml b/adapters/spi/pom.xml index 59da2346d935..422d508d6eb3 100755 --- a/adapters/spi/pom.xml +++ b/adapters/spi/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../pom.xml Keycloak Client Adapter SPI Modules diff --git a/adapters/spi/servlet-adapter-spi/pom.xml b/adapters/spi/servlet-adapter-spi/pom.xml index 2ca2c43b0179..108184be3d35 100755 --- a/adapters/spi/servlet-adapter-spi/pom.xml +++ b/adapters/spi/servlet-adapter-spi/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/spi/tomcat-adapter-spi/pom.xml b/adapters/spi/tomcat-adapter-spi/pom.xml index a6de5b49c106..fdced8d4ca7d 100755 --- a/adapters/spi/tomcat-adapter-spi/pom.xml +++ b/adapters/spi/tomcat-adapter-spi/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/spi/undertow-adapter-spi/pom.xml b/adapters/spi/undertow-adapter-spi/pom.xml index ec61898cd842..159d072b5711 100755 --- a/adapters/spi/undertow-adapter-spi/pom.xml +++ b/adapters/spi/undertow-adapter-spi/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml 4.0.0 diff --git a/authz/client/pom.xml b/authz/client/pom.xml index eb58e7ded191..528776902144 100644 --- a/authz/client/pom.xml +++ b/authz/client/pom.xml @@ -7,7 +7,7 @@ org.keycloak keycloak-authz-parent - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml diff --git a/authz/policy/common/pom.xml b/authz/policy/common/pom.xml index 32ca573ccc3d..070f368274e7 100644 --- a/authz/policy/common/pom.xml +++ b/authz/policy/common/pom.xml @@ -25,7 +25,7 @@ org.keycloak keycloak-authz-provider-parent - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml diff --git a/authz/policy/pom.xml b/authz/policy/pom.xml index 1ecefff1cc83..4d92a95ded82 100644 --- a/authz/policy/pom.xml +++ b/authz/policy/pom.xml @@ -7,7 +7,7 @@ org.keycloak keycloak-authz-parent - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml diff --git a/authz/pom.xml b/authz/pom.xml index 144c2770534f..9201bba36a02 100644 --- a/authz/pom.xml +++ b/authz/pom.xml @@ -7,7 +7,7 @@ org.keycloak keycloak-parent - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml diff --git a/boms/adapter/pom.xml b/boms/adapter/pom.xml index 2d24c4cac4d7..53ac15814d8a 100644 --- a/boms/adapter/pom.xml +++ b/boms/adapter/pom.xml @@ -22,7 +22,7 @@ org.keycloak.bom keycloak-bom-parent - 11.0.0-SNAPSHOT + 11.0.3 org.keycloak.bom diff --git a/boms/misc/pom.xml b/boms/misc/pom.xml index 4e72b3f958d5..3086bc0f9fd2 100644 --- a/boms/misc/pom.xml +++ b/boms/misc/pom.xml @@ -22,7 +22,7 @@ org.keycloak.bom keycloak-bom-parent - 11.0.0-SNAPSHOT + 11.0.3 org.keycloak.bom diff --git a/boms/pom.xml b/boms/pom.xml index 248d2935e534..1ae109d27cc8 100644 --- a/boms/pom.xml +++ b/boms/pom.xml @@ -26,7 +26,7 @@ org.keycloak.bom keycloak-bom-parent - 11.0.0-SNAPSHOT + 11.0.3 pom diff --git a/boms/spi/pom.xml b/boms/spi/pom.xml index 684fa7d6d40f..3c0f2a7abbbe 100644 --- a/boms/spi/pom.xml +++ b/boms/spi/pom.xml @@ -23,7 +23,7 @@ org.keycloak.bom keycloak-bom-parent - 11.0.0-SNAPSHOT + 11.0.3 org.keycloak.bom diff --git a/common/pom.xml b/common/pom.xml index 93cca86645c6..60c0405de5ed 100755 --- a/common/pom.xml +++ b/common/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml 4.0.0 diff --git a/core/pom.xml b/core/pom.xml index 137839db64df..31bfbc3f09e4 100755 --- a/core/pom.xml +++ b/core/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml 4.0.0 diff --git a/core/src/main/java/org/keycloak/KeycloakPrincipal.java b/core/src/main/java/org/keycloak/KeycloakPrincipal.java index f7b1b3007ee8..beb7448599cf 100755 --- a/core/src/main/java/org/keycloak/KeycloakPrincipal.java +++ b/core/src/main/java/org/keycloak/KeycloakPrincipal.java @@ -72,6 +72,7 @@ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundE DelegatingSerializationFilter.builder() .addAllowedClass(KeycloakPrincipal.class) .addAllowedClass(KeycloakSecurityContext.class) + .addAllowedPattern("org.keycloak.adapters.RefreshableKeycloakSecurityContext") .setFilter(in); in.defaultReadObject(); diff --git a/core/src/main/java/org/keycloak/representations/ClaimsParameter.java b/core/src/main/java/org/keycloak/representations/ClaimsParameter.java new file mode 100644 index 000000000000..0d304f811870 --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/ClaimsParameter.java @@ -0,0 +1,68 @@ +package org.keycloak.representations; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.node.ValueNode; +import java.util.List; +import java.util.Map; + +public class ClaimsParameter { + + @JsonProperty("userinfo") + protected Map userinfo; + + @JsonProperty("id_token") + protected Map idToken; + + public Map getUserinfo() { + return userinfo; + } + + public void setUserinfo(Map userinfo) { + this.userinfo = userinfo; + } + + public Map getIdToken() { + return idToken; + } + + public void setIdToken(Map idToken) { + this.idToken = idToken; + } + + public static class ClaimRequest { + + @JsonProperty("essential") + protected boolean essential; + + @JsonProperty("value") + protected ValueNode value; + + @JsonProperty("values") + protected List values; + + public boolean isEssential() { + return essential; + } + + public void setEssential(boolean essential) { + this.essential = essential; + } + + public ValueNode getValue() { + return value; + } + + public void setValue(ValueNode value) { + this.value = value; + } + + public List getValues() { + return values; + } + + public void setValues(List values) { + this.values = values; + } + } + +} diff --git a/dependencies/pom.xml b/dependencies/pom.xml index 5233e5512522..33fb18d239e6 100755 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/dependencies/server-all/pom.xml b/dependencies/server-all/pom.xml index f216f51ce056..cbc061466a94 100755 --- a/dependencies/server-all/pom.xml +++ b/dependencies/server-all/pom.xml @@ -21,7 +21,7 @@ keycloak-dependencies-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/dependencies/server-min/pom.xml b/dependencies/server-min/pom.xml index 3444b79baa95..7c53125eae05 100755 --- a/dependencies/server-min/pom.xml +++ b/dependencies/server-min/pom.xml @@ -21,7 +21,7 @@ keycloak-dependencies-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/distribution/adapters/as7-eap6-adapter/as7-adapter-zip/pom.xml b/distribution/adapters/as7-eap6-adapter/as7-adapter-zip/pom.xml index f45b299ceada..da06d1cd70de 100755 --- a/distribution/adapters/as7-eap6-adapter/as7-adapter-zip/pom.xml +++ b/distribution/adapters/as7-eap6-adapter/as7-adapter-zip/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../../pom.xml diff --git a/distribution/adapters/as7-eap6-adapter/as7-modules/pom.xml b/distribution/adapters/as7-eap6-adapter/as7-modules/pom.xml index 989e4f21c906..216869f2e74d 100755 --- a/distribution/adapters/as7-eap6-adapter/as7-modules/pom.xml +++ b/distribution/adapters/as7-eap6-adapter/as7-modules/pom.xml @@ -25,7 +25,7 @@ keycloak-as7-eap6-adapter-dist-pom org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml diff --git a/distribution/adapters/as7-eap6-adapter/eap6-adapter-zip/pom.xml b/distribution/adapters/as7-eap6-adapter/eap6-adapter-zip/pom.xml index eab0d6cce818..f8d58ade509f 100755 --- a/distribution/adapters/as7-eap6-adapter/eap6-adapter-zip/pom.xml +++ b/distribution/adapters/as7-eap6-adapter/eap6-adapter-zip/pom.xml @@ -21,7 +21,7 @@ keycloak-as7-eap6-adapter-dist-pom org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml diff --git a/distribution/adapters/as7-eap6-adapter/pom.xml b/distribution/adapters/as7-eap6-adapter/pom.xml index 431ff223832b..09803a494237 100644 --- a/distribution/adapters/as7-eap6-adapter/pom.xml +++ b/distribution/adapters/as7-eap6-adapter/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml Keycloak AS7 / JBoss EAP 6 Adapter Distros diff --git a/distribution/adapters/fuse-adapter-zip/pom.xml b/distribution/adapters/fuse-adapter-zip/pom.xml index 6e9da9c517f8..b4787cb47457 100644 --- a/distribution/adapters/fuse-adapter-zip/pom.xml +++ b/distribution/adapters/fuse-adapter-zip/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml diff --git a/distribution/adapters/jetty92-adapter-zip/pom.xml b/distribution/adapters/jetty92-adapter-zip/pom.xml index 6e7e24f7fcfa..8cb8dc3a991f 100755 --- a/distribution/adapters/jetty92-adapter-zip/pom.xml +++ b/distribution/adapters/jetty92-adapter-zip/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml diff --git a/distribution/adapters/jetty93-adapter-zip/pom.xml b/distribution/adapters/jetty93-adapter-zip/pom.xml index 8f97413c414e..832368850522 100644 --- a/distribution/adapters/jetty93-adapter-zip/pom.xml +++ b/distribution/adapters/jetty93-adapter-zip/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml diff --git a/distribution/adapters/jetty94-adapter-zip/pom.xml b/distribution/adapters/jetty94-adapter-zip/pom.xml index 15c9984d91d5..b63131b3269b 100644 --- a/distribution/adapters/jetty94-adapter-zip/pom.xml +++ b/distribution/adapters/jetty94-adapter-zip/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml diff --git a/distribution/adapters/js-adapter-npm-zip/pom.xml b/distribution/adapters/js-adapter-npm-zip/pom.xml index 35a1b0354fa4..f074cb4f2b0d 100755 --- a/distribution/adapters/js-adapter-npm-zip/pom.xml +++ b/distribution/adapters/js-adapter-npm-zip/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml diff --git a/distribution/adapters/js-adapter-zip/pom.xml b/distribution/adapters/js-adapter-zip/pom.xml index 191a38fbd303..ccdef6df0b43 100755 --- a/distribution/adapters/js-adapter-zip/pom.xml +++ b/distribution/adapters/js-adapter-zip/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml diff --git a/distribution/adapters/osgi/features/pom.xml b/distribution/adapters/osgi/features/pom.xml index 70797030186d..add9dc8644b9 100755 --- a/distribution/adapters/osgi/features/pom.xml +++ b/distribution/adapters/osgi/features/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../../pom.xml Keycloak OSGI Features diff --git a/distribution/adapters/osgi/jaas/pom.xml b/distribution/adapters/osgi/jaas/pom.xml index efa6b1987009..8aca2832404a 100755 --- a/distribution/adapters/osgi/jaas/pom.xml +++ b/distribution/adapters/osgi/jaas/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../../pom.xml Keycloak OSGI JAAS Realm Configuration diff --git a/distribution/adapters/osgi/pom.xml b/distribution/adapters/osgi/pom.xml index 3fe988536bf7..e411307592fd 100755 --- a/distribution/adapters/osgi/pom.xml +++ b/distribution/adapters/osgi/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml Keycloak OSGI Integration diff --git a/distribution/adapters/pom.xml b/distribution/adapters/pom.xml index f29b5064f1d3..0530c54ee4a2 100755 --- a/distribution/adapters/pom.xml +++ b/distribution/adapters/pom.xml @@ -20,7 +20,7 @@ keycloak-distribution-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 Adapters Distribution Parent diff --git a/distribution/adapters/tomcat-adapter-zip/pom.xml b/distribution/adapters/tomcat-adapter-zip/pom.xml index d2e34e2ab93b..902e18ffc5f3 100755 --- a/distribution/adapters/tomcat-adapter-zip/pom.xml +++ b/distribution/adapters/tomcat-adapter-zip/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml diff --git a/distribution/adapters/tomcat7-adapter-zip/pom.xml b/distribution/adapters/tomcat7-adapter-zip/pom.xml index 6fe03f1b5759..21b68a37c793 100755 --- a/distribution/adapters/tomcat7-adapter-zip/pom.xml +++ b/distribution/adapters/tomcat7-adapter-zip/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml diff --git a/distribution/adapters/wildfly-adapter/pom.xml b/distribution/adapters/wildfly-adapter/pom.xml index f9d9d02ba3a6..24c67b40c72b 100644 --- a/distribution/adapters/wildfly-adapter/pom.xml +++ b/distribution/adapters/wildfly-adapter/pom.xml @@ -21,7 +21,7 @@ keycloak-adapters-distribution-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 keycloak-wildfly-adapter-dist diff --git a/distribution/api-docs-dist/pom.xml b/distribution/api-docs-dist/pom.xml index 068ccc3bdf8f..d791735b1ef3 100755 --- a/distribution/api-docs-dist/pom.xml +++ b/distribution/api-docs-dist/pom.xml @@ -21,7 +21,7 @@ keycloak-distribution-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 keycloak-api-docs-dist diff --git a/distribution/downloads/pom.xml b/distribution/downloads/pom.xml index 846dc5fea255..8b7aeea8e097 100755 --- a/distribution/downloads/pom.xml +++ b/distribution/downloads/pom.xml @@ -21,7 +21,7 @@ keycloak-distribution-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 keycloak-dist-downloads diff --git a/distribution/examples-dist/pom.xml b/distribution/examples-dist/pom.xml index c0f65b98c4ca..cc533532393a 100755 --- a/distribution/examples-dist/pom.xml +++ b/distribution/examples-dist/pom.xml @@ -21,7 +21,7 @@ keycloak-distribution-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 keycloak-examples-dist diff --git a/distribution/feature-packs/adapter-feature-pack/pom.xml b/distribution/feature-packs/adapter-feature-pack/pom.xml index e802831971ab..89e6cc9f7a39 100755 --- a/distribution/feature-packs/adapter-feature-pack/pom.xml +++ b/distribution/feature-packs/adapter-feature-pack/pom.xml @@ -19,7 +19,7 @@ org.keycloak feature-packs-parent - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/distribution/feature-packs/pom.xml b/distribution/feature-packs/pom.xml index 5e97a954613c..b2b46d2295dd 100644 --- a/distribution/feature-packs/pom.xml +++ b/distribution/feature-packs/pom.xml @@ -20,7 +20,7 @@ keycloak-distribution-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 Feature Pack Builds diff --git a/distribution/feature-packs/server-feature-pack/pom.xml b/distribution/feature-packs/server-feature-pack/pom.xml index fec2d22cd369..0d1394d0be73 100644 --- a/distribution/feature-packs/server-feature-pack/pom.xml +++ b/distribution/feature-packs/server-feature-pack/pom.xml @@ -19,7 +19,7 @@ org.keycloak feature-packs-parent - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml 4.0.0 diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-domain-clustered.cli b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-domain-clustered.cli index e5c4f2b8df48..6765dfa987ab 100644 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-domain-clustered.cli +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-domain-clustered.cli @@ -695,6 +695,26 @@ if (result != fixed) of /profile=$clusteredProfile/subsystem=keycloak-server/spi end-try end-if +# Migrate from 8.0.0 to 9.0.5 + +if (result == 120000 || result == undefined) of /profile=$clusteredProfile/subsystem=undertow/server=default-server/http-listener=default/:read-attribute(name=read-timeout) + echo Updating value of http listener read-timeout. + /profile=$clusteredProfile/subsystem=undertow/server=default-server/http-listener=default/:write-attribute(name=read-timeout,value=30000) + echo +end-if + +if (result == 120000 || result == undefined) of /profile=$clusteredProfile/subsystem=undertow/server=default-server/https-listener=https/:read-attribute(name=read-timeout) + echo Updating value of https listener read-timeout. + /profile=$clusteredProfile/subsystem=undertow/server=default-server/https-listener=https/:write-attribute(name=read-timeout,value=30000) + echo +end-if + +if (result == 20 || result == undefined) of /profile=$clusteredProfile/subsystem=datasources/data-source=KeycloakDS/:read-attribute(name=max-pool-size) + echo Updating value of datasource max-pool-size. + /profile=$clusteredProfile/subsystem=datasources/data-source=KeycloakDS/:write-attribute(name=max-pool-size,value=100) + echo +end-if + # Migrate from 10.0.2 to 11.0.0 (migration changes for infinispan update from 9.4.18.Final to 10.1.8.Final) if (result != org.keycloak.keycloak-model-infinispan) of /profile=$clusteredProfile/subsystem=infinispan/cache-container=keycloak:read-attribute(name=module) diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-domain-standalone.cli b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-domain-standalone.cli index 652bbb81804c..f5287998203a 100644 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-domain-standalone.cli +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-domain-standalone.cli @@ -596,6 +596,26 @@ if (result != fixed) of /profile=$standaloneProfile/subsystem=keycloak-server/sp end-try end-if +# Migrate from 8.0.0 to 9.0.5 + +if (result == 120000 || result == undefined) of /profile=$standaloneProfile/subsystem=undertow/server=default-server/http-listener=default/:read-attribute(name=read-timeout) + echo Updating value of http listener read-timeout. + /profile=$standaloneProfile/subsystem=undertow/server=default-server/http-listener=default/:write-attribute(name=read-timeout,value=30000) + echo +end-if + +if (result == 120000 || result == undefined) of /profile=$standaloneProfile/subsystem=undertow/server=default-server/https-listener=https/:read-attribute(name=read-timeout) + echo Updating value of https listener read-timeout. + /profile=$standaloneProfile/subsystem=undertow/server=default-server/https-listener=https/:write-attribute(name=read-timeout,value=30000) + echo +end-if + +if (result == 20 || result == undefined) of /profile=$standaloneProfile/subsystem=datasources/data-source=KeycloakDS/:read-attribute(name=max-pool-size) + echo Updating value of datasource max-pool-size. + /profile=$standaloneProfile/subsystem=datasources/data-source=KeycloakDS/:write-attribute(name=max-pool-size,value=100) + echo +end-if + # Migrate from 10.0.2 to 11.0.0 (migration changes for infinispan update from 9.4.18.Final to 10.1.8.Final) if (result != org.keycloak.keycloak-model-infinispan) of /profile=$standaloneProfile/subsystem=infinispan/cache-container=keycloak:read-attribute(name=module) diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-standalone-ha.cli b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-standalone-ha.cli index 92bcdb612d8a..8baf80bfa03c 100644 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-standalone-ha.cli +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-standalone-ha.cli @@ -774,6 +774,26 @@ if (result != fixed) of /subsystem=keycloak-server/spi=hostname/:read-attribute( end-try end-if +# Migrate from 8.0.0 to 9.0.5 + +if (result == 120000 || result == undefined) of /subsystem=undertow/server=default-server/http-listener=default/:read-attribute(name=read-timeout) + echo Updating value of http listener read-timeout. + /subsystem=undertow/server=default-server/http-listener=default/:write-attribute(name=read-timeout,value=30000) + echo +end-if + +if (result == 120000 || result == undefined) of /subsystem=undertow/server=default-server/https-listener=https/:read-attribute(name=read-timeout) + echo Updating value of https listener read-timeout. + /subsystem=undertow/server=default-server/https-listener=https/:write-attribute(name=read-timeout,value=30000) + echo +end-if + +if (result == 20 || result == undefined) of /subsystem=datasources/data-source=KeycloakDS/:read-attribute(name=max-pool-size) + echo Updating value of datasource max-pool-size. + /subsystem=datasources/data-source=KeycloakDS/:write-attribute(name=max-pool-size,value=100) + echo +end-if + # Migrate from 10.0.2 to 11.0.0 (migration changes for infinispan update from 9.4.18.Final to 10.1.8.Final) if (result != org.keycloak.keycloak-model-infinispan) of /subsystem=infinispan/cache-container=keycloak:read-attribute(name=module) diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-standalone.cli b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-standalone.cli index e82e198c4536..5ff3b048ab3c 100644 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-standalone.cli +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/bin/migrate-standalone.cli @@ -639,6 +639,26 @@ if (result != fixed) of /subsystem=keycloak-server/spi=hostname/:read-attribute( end-try end-if +# Migrate from 8.0.0 to 9.0.5 + +if (result == 120000 || result == undefined) of /subsystem=undertow/server=default-server/http-listener=default/:read-attribute(name=read-timeout) + echo Updating value of http listener read-timeout. + /subsystem=undertow/server=default-server/http-listener=default/:write-attribute(name=read-timeout,value=30000) + echo +end-if + +if (result == 120000 || result == undefined) of /subsystem=undertow/server=default-server/https-listener=https/:read-attribute(name=read-timeout) + echo Updating value of https listener read-timeout. + /subsystem=undertow/server=default-server/https-listener=https/:write-attribute(name=read-timeout,value=30000) + echo +end-if + +if (result == 20 || result == undefined) of /subsystem=datasources/data-source=KeycloakDS/:read-attribute(name=max-pool-size) + echo Updating value of datasource max-pool-size. + /subsystem=datasources/data-source=KeycloakDS/:write-attribute(name=max-pool-size,value=100) + echo +end-if + # Migrate from 10.0.2 to 11.0.0 (migration changes for infinispan update from 9.4.18.Final to 10.1.8.Final) if (result != org.keycloak.keycloak-model-infinispan) of /subsystem=infinispan/cache-container=keycloak:read-attribute(name=module) diff --git a/distribution/licenses-common/pom.xml b/distribution/licenses-common/pom.xml index 53051fd16ed3..155aa7cc1ba4 100644 --- a/distribution/licenses-common/pom.xml +++ b/distribution/licenses-common/pom.xml @@ -20,7 +20,7 @@ keycloak-distribution-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 keycloak-distribution-licenses-common diff --git a/distribution/maven-plugins/licenses-processor/pom.xml b/distribution/maven-plugins/licenses-processor/pom.xml index ece332300523..08071680d6f1 100644 --- a/distribution/maven-plugins/licenses-processor/pom.xml +++ b/distribution/maven-plugins/licenses-processor/pom.xml @@ -20,7 +20,7 @@ keycloak-distribution-maven-plugins-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 keycloak-distribution-licenses-maven-plugin diff --git a/distribution/maven-plugins/pom.xml b/distribution/maven-plugins/pom.xml index 2fab36397ca4..8cbea173c758 100644 --- a/distribution/maven-plugins/pom.xml +++ b/distribution/maven-plugins/pom.xml @@ -20,7 +20,7 @@ keycloak-distribution-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 keycloak-distribution-maven-plugins-parent diff --git a/distribution/pom.xml b/distribution/pom.xml index e7c81e41a251..3881b3aff08a 100755 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml diff --git a/distribution/saml-adapters/as7-eap6-adapter/as7-adapter-zip/pom.xml b/distribution/saml-adapters/as7-eap6-adapter/as7-adapter-zip/pom.xml index 97bd912dad0a..c06a8e2a20e6 100755 --- a/distribution/saml-adapters/as7-eap6-adapter/as7-adapter-zip/pom.xml +++ b/distribution/saml-adapters/as7-eap6-adapter/as7-adapter-zip/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../../pom.xml diff --git a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/pom.xml b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/pom.xml index 5953471014c1..5ac229825c3c 100755 --- a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/pom.xml +++ b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/pom.xml @@ -25,7 +25,7 @@ keycloak-saml-as7-eap6-adapter-dist-pom org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml diff --git a/distribution/saml-adapters/as7-eap6-adapter/eap6-adapter-zip/pom.xml b/distribution/saml-adapters/as7-eap6-adapter/eap6-adapter-zip/pom.xml index 6da525fa219a..fb0bf98a5e70 100755 --- a/distribution/saml-adapters/as7-eap6-adapter/eap6-adapter-zip/pom.xml +++ b/distribution/saml-adapters/as7-eap6-adapter/eap6-adapter-zip/pom.xml @@ -21,7 +21,7 @@ keycloak-saml-as7-eap6-adapter-dist-pom org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml diff --git a/distribution/saml-adapters/as7-eap6-adapter/pom.xml b/distribution/saml-adapters/as7-eap6-adapter/pom.xml index 0bce976e477f..76a05af07cd8 100755 --- a/distribution/saml-adapters/as7-eap6-adapter/pom.xml +++ b/distribution/saml-adapters/as7-eap6-adapter/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml Keycloak SAML AS7 / JBoss EAP 6 Adapter Distros diff --git a/distribution/saml-adapters/jetty92-adapter-zip/pom.xml b/distribution/saml-adapters/jetty92-adapter-zip/pom.xml index 9d66489d8220..bc6d9275465d 100755 --- a/distribution/saml-adapters/jetty92-adapter-zip/pom.xml +++ b/distribution/saml-adapters/jetty92-adapter-zip/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml diff --git a/distribution/saml-adapters/jetty93-adapter-zip/pom.xml b/distribution/saml-adapters/jetty93-adapter-zip/pom.xml index bf6d08035992..b8f018161fac 100644 --- a/distribution/saml-adapters/jetty93-adapter-zip/pom.xml +++ b/distribution/saml-adapters/jetty93-adapter-zip/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml diff --git a/distribution/saml-adapters/jetty94-adapter-zip/pom.xml b/distribution/saml-adapters/jetty94-adapter-zip/pom.xml index b20e2671c4ba..b550d9ea7c71 100644 --- a/distribution/saml-adapters/jetty94-adapter-zip/pom.xml +++ b/distribution/saml-adapters/jetty94-adapter-zip/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml diff --git a/distribution/saml-adapters/pom.xml b/distribution/saml-adapters/pom.xml index dbdacfe8982b..06e204709710 100755 --- a/distribution/saml-adapters/pom.xml +++ b/distribution/saml-adapters/pom.xml @@ -20,7 +20,7 @@ keycloak-distribution-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 SAML Adapters Distribution Parent diff --git a/distribution/saml-adapters/tomcat-adapter-zip/pom.xml b/distribution/saml-adapters/tomcat-adapter-zip/pom.xml index 74e2aaac1359..912435e122f1 100755 --- a/distribution/saml-adapters/tomcat-adapter-zip/pom.xml +++ b/distribution/saml-adapters/tomcat-adapter-zip/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml diff --git a/distribution/saml-adapters/tomcat7-adapter-zip/pom.xml b/distribution/saml-adapters/tomcat7-adapter-zip/pom.xml index 5360a27b01aa..f75cc3bbc8ef 100755 --- a/distribution/saml-adapters/tomcat7-adapter-zip/pom.xml +++ b/distribution/saml-adapters/tomcat7-adapter-zip/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml diff --git a/distribution/saml-adapters/wildfly-adapter/pom.xml b/distribution/saml-adapters/wildfly-adapter/pom.xml index 71cfecf6a8a2..060a51cdb9c1 100755 --- a/distribution/saml-adapters/wildfly-adapter/pom.xml +++ b/distribution/saml-adapters/wildfly-adapter/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../pom.xml Keycloak Wildfly SAML Adapter diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-adapter-zip/pom.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-adapter-zip/pom.xml index a9e47aabda51..7521477d6234 100755 --- a/distribution/saml-adapters/wildfly-adapter/wildfly-adapter-zip/pom.xml +++ b/distribution/saml-adapters/wildfly-adapter/wildfly-adapter-zip/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../../pom.xml diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/pom.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/pom.xml index 73b307063795..afeafb2b5d0e 100755 --- a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/pom.xml +++ b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/pom.xml @@ -25,7 +25,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../../../pom.xml diff --git a/distribution/server-dist/pom.xml b/distribution/server-dist/pom.xml index 2be9ada67746..6a3366dbd16e 100755 --- a/distribution/server-dist/pom.xml +++ b/distribution/server-dist/pom.xml @@ -21,7 +21,7 @@ keycloak-distribution-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 keycloak-server-dist diff --git a/distribution/server-overlay/pom.xml b/distribution/server-overlay/pom.xml index 7dcfd15ff1f7..1de9df3891bf 100755 --- a/distribution/server-overlay/pom.xml +++ b/distribution/server-overlay/pom.xml @@ -21,7 +21,7 @@ keycloak-distribution-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 keycloak-server-overlay diff --git a/distribution/server-x/pom.xml b/distribution/server-x/pom.xml index 64da567ef002..243f8b4d98dc 100755 --- a/distribution/server-x/pom.xml +++ b/distribution/server-x/pom.xml @@ -21,7 +21,7 @@ keycloak-distribution-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 keycloak-server-x diff --git a/examples/admin-client/pom.xml b/examples/admin-client/pom.xml index c56276412d6b..68e012ebd06b 100755 --- a/examples/admin-client/pom.xml +++ b/examples/admin-client/pom.xml @@ -22,7 +22,7 @@ keycloak-examples-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 Keycloak Examples - Admin Client diff --git a/examples/basic-auth/pom.xml b/examples/basic-auth/pom.xml index 024912db1a33..0f5baa25eaed 100755 --- a/examples/basic-auth/pom.xml +++ b/examples/basic-auth/pom.xml @@ -23,7 +23,7 @@ keycloak-examples-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 Keycloak Examples - Basic Auth diff --git a/examples/broker/facebook-authentication/pom.xml b/examples/broker/facebook-authentication/pom.xml index b71f1ff7906a..4bc957c78eed 100755 --- a/examples/broker/facebook-authentication/pom.xml +++ b/examples/broker/facebook-authentication/pom.xml @@ -23,7 +23,7 @@ keycloak-examples-broker-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 Keycloak Broker Examples - Facebook Authentication diff --git a/examples/broker/google-authentication/pom.xml b/examples/broker/google-authentication/pom.xml index 94f290a8bde9..56d65e80b55f 100755 --- a/examples/broker/google-authentication/pom.xml +++ b/examples/broker/google-authentication/pom.xml @@ -23,7 +23,7 @@ keycloak-examples-broker-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 Keycloak Broker Examples - Google Authentication diff --git a/examples/broker/pom.xml b/examples/broker/pom.xml index 04252a9bbe57..ffbb0ba93405 100755 --- a/examples/broker/pom.xml +++ b/examples/broker/pom.xml @@ -20,7 +20,7 @@ keycloak-examples-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 Broker Examples diff --git a/examples/broker/saml-broker-authentication/pom.xml b/examples/broker/saml-broker-authentication/pom.xml index 69c95f462494..f0a82e54d8a4 100755 --- a/examples/broker/saml-broker-authentication/pom.xml +++ b/examples/broker/saml-broker-authentication/pom.xml @@ -23,7 +23,7 @@ keycloak-examples-broker-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 Keycloak Broker Examples - SAML Identity Provider Brokering diff --git a/examples/broker/twitter-authentication/pom.xml b/examples/broker/twitter-authentication/pom.xml index 80ed4f1bfbdf..e27e70145313 100755 --- a/examples/broker/twitter-authentication/pom.xml +++ b/examples/broker/twitter-authentication/pom.xml @@ -23,7 +23,7 @@ keycloak-examples-broker-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 Keycloak Broker Examples - Twitter Authentication diff --git a/examples/cors/angular-product-app/pom.xml b/examples/cors/angular-product-app/pom.xml index 7763d94f85b0..8e59b6e3cc1b 100755 --- a/examples/cors/angular-product-app/pom.xml +++ b/examples/cors/angular-product-app/pom.xml @@ -21,7 +21,7 @@ keycloak-examples-cors-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/examples/cors/database-service/pom.xml b/examples/cors/database-service/pom.xml index 27710c3d1cb6..5059dfcdac59 100755 --- a/examples/cors/database-service/pom.xml +++ b/examples/cors/database-service/pom.xml @@ -21,7 +21,7 @@ keycloak-examples-cors-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/examples/cors/pom.xml b/examples/cors/pom.xml index c65178c87700..676961a11a4b 100755 --- a/examples/cors/pom.xml +++ b/examples/cors/pom.xml @@ -21,7 +21,7 @@ keycloak-examples-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 Keycloak Examples - CORS diff --git a/examples/demo-template/admin-access-app/pom.xml b/examples/demo-template/admin-access-app/pom.xml index f9d19b0ba6f0..b43107014209 100755 --- a/examples/demo-template/admin-access-app/pom.xml +++ b/examples/demo-template/admin-access-app/pom.xml @@ -21,7 +21,7 @@ keycloak-examples-demo-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/examples/demo-template/angular-product-app/pom.xml b/examples/demo-template/angular-product-app/pom.xml index de50baa52c79..ce18a04bc8f8 100755 --- a/examples/demo-template/angular-product-app/pom.xml +++ b/examples/demo-template/angular-product-app/pom.xml @@ -21,7 +21,7 @@ keycloak-examples-demo-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/examples/demo-template/customer-app-cli/pom.xml b/examples/demo-template/customer-app-cli/pom.xml index 7391bb2915c3..563e5e9ffff9 100755 --- a/examples/demo-template/customer-app-cli/pom.xml +++ b/examples/demo-template/customer-app-cli/pom.xml @@ -21,7 +21,7 @@ keycloak-examples-demo-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/examples/demo-template/customer-app-filter/pom.xml b/examples/demo-template/customer-app-filter/pom.xml index 6a8312259a88..dfa9106d0581 100755 --- a/examples/demo-template/customer-app-filter/pom.xml +++ b/examples/demo-template/customer-app-filter/pom.xml @@ -21,7 +21,7 @@ keycloak-examples-demo-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/examples/demo-template/customer-app-js/pom.xml b/examples/demo-template/customer-app-js/pom.xml index 4e64f584d30d..b9b6805b2c53 100755 --- a/examples/demo-template/customer-app-js/pom.xml +++ b/examples/demo-template/customer-app-js/pom.xml @@ -21,7 +21,7 @@ keycloak-examples-demo-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/examples/demo-template/customer-app/pom.xml b/examples/demo-template/customer-app/pom.xml index 248424af5a84..734552e07d1e 100755 --- a/examples/demo-template/customer-app/pom.xml +++ b/examples/demo-template/customer-app/pom.xml @@ -21,7 +21,7 @@ keycloak-examples-demo-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/examples/demo-template/database-service/pom.xml b/examples/demo-template/database-service/pom.xml index 4a49de4f5234..b888426b6c66 100755 --- a/examples/demo-template/database-service/pom.xml +++ b/examples/demo-template/database-service/pom.xml @@ -21,7 +21,7 @@ keycloak-examples-demo-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/examples/demo-template/example-ear/pom.xml b/examples/demo-template/example-ear/pom.xml index bd45c574fcee..b56fa199725c 100755 --- a/examples/demo-template/example-ear/pom.xml +++ b/examples/demo-template/example-ear/pom.xml @@ -21,7 +21,7 @@ keycloak-examples-demo-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/examples/demo-template/offline-access-app/pom.xml b/examples/demo-template/offline-access-app/pom.xml index 6434fd848ed1..72bdaad08ee8 100755 --- a/examples/demo-template/offline-access-app/pom.xml +++ b/examples/demo-template/offline-access-app/pom.xml @@ -21,7 +21,7 @@ keycloak-examples-demo-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/examples/demo-template/pom.xml b/examples/demo-template/pom.xml index 91d61da08bda..6ce4b764cc0b 100755 --- a/examples/demo-template/pom.xml +++ b/examples/demo-template/pom.xml @@ -20,7 +20,7 @@ keycloak-examples-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 Demo Examples diff --git a/examples/demo-template/product-app/pom.xml b/examples/demo-template/product-app/pom.xml index 3d6149e5f930..97f8f2f5bba8 100755 --- a/examples/demo-template/product-app/pom.xml +++ b/examples/demo-template/product-app/pom.xml @@ -21,7 +21,7 @@ keycloak-examples-demo-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/examples/demo-template/service-account/pom.xml b/examples/demo-template/service-account/pom.xml index 0c050ed31178..6ebb71032954 100755 --- a/examples/demo-template/service-account/pom.xml +++ b/examples/demo-template/service-account/pom.xml @@ -21,7 +21,7 @@ keycloak-examples-demo-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/examples/js-console/pom.xml b/examples/js-console/pom.xml index 27a936f4128e..9f12dad3ee0e 100755 --- a/examples/js-console/pom.xml +++ b/examples/js-console/pom.xml @@ -21,7 +21,7 @@ keycloak-examples-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/examples/kerberos/pom.xml b/examples/kerberos/pom.xml index 645586541ef3..35babbe252ad 100755 --- a/examples/kerberos/pom.xml +++ b/examples/kerberos/pom.xml @@ -22,7 +22,7 @@ keycloak-examples-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 Keycloak Examples - Kerberos Credential Delegation diff --git a/examples/ldap/pom.xml b/examples/ldap/pom.xml index eb368894afb0..0c1c17cd9f94 100644 --- a/examples/ldap/pom.xml +++ b/examples/ldap/pom.xml @@ -22,7 +22,7 @@ keycloak-examples-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/examples/multi-tenant/pom.xml b/examples/multi-tenant/pom.xml index 0860000cd0f0..f3c1fad3ff37 100755 --- a/examples/multi-tenant/pom.xml +++ b/examples/multi-tenant/pom.xml @@ -21,7 +21,7 @@ keycloak-examples-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 Keycloak Examples - Multi Tenant diff --git a/examples/pom.xml b/examples/pom.xml index 171e9cf520e6..a54c10dd82e2 100755 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 Keycloak Examples diff --git a/examples/providers/authenticator/pom.xml b/examples/providers/authenticator/pom.xml index e3c26cb74cc4..eb9911d1034f 100755 --- a/examples/providers/authenticator/pom.xml +++ b/examples/providers/authenticator/pom.xml @@ -20,7 +20,7 @@ keycloak-examples-providers-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 Authenticator Example diff --git a/examples/providers/domain-extension/pom.xml b/examples/providers/domain-extension/pom.xml index 4fc4172d1a0f..da234c4b505a 100755 --- a/examples/providers/domain-extension/pom.xml +++ b/examples/providers/domain-extension/pom.xml @@ -20,7 +20,7 @@ keycloak-examples-providers-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 Domain Extension Example diff --git a/examples/providers/pom.xml b/examples/providers/pom.xml index ad77623aeca8..9c841b4410e3 100755 --- a/examples/providers/pom.xml +++ b/examples/providers/pom.xml @@ -20,7 +20,7 @@ keycloak-examples-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 Provider Examples diff --git a/examples/providers/rest/pom.xml b/examples/providers/rest/pom.xml index acb859fc1cad..18bfe176097e 100755 --- a/examples/providers/rest/pom.xml +++ b/examples/providers/rest/pom.xml @@ -20,7 +20,7 @@ keycloak-examples-providers-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 REST Example diff --git a/examples/saml/pom.xml b/examples/saml/pom.xml index e021f553254d..1cd829b2fd1a 100755 --- a/examples/saml/pom.xml +++ b/examples/saml/pom.xml @@ -20,7 +20,7 @@ keycloak-examples-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 SAML Examples diff --git a/examples/saml/post-with-encryption/pom.xml b/examples/saml/post-with-encryption/pom.xml index 019a5bf0e31d..c4406a5d423d 100755 --- a/examples/saml/post-with-encryption/pom.xml +++ b/examples/saml/post-with-encryption/pom.xml @@ -22,7 +22,7 @@ keycloak-examples-saml-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 saml-post-encryption diff --git a/examples/saml/post-with-signature/pom.xml b/examples/saml/post-with-signature/pom.xml index ab32a0b270e1..480c89782294 100755 --- a/examples/saml/post-with-signature/pom.xml +++ b/examples/saml/post-with-signature/pom.xml @@ -22,7 +22,7 @@ keycloak-examples-saml-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 sales-post-sig diff --git a/examples/saml/redirect-with-signature/pom.xml b/examples/saml/redirect-with-signature/pom.xml index c42244ead82b..119bf731b000 100755 --- a/examples/saml/redirect-with-signature/pom.xml +++ b/examples/saml/redirect-with-signature/pom.xml @@ -22,7 +22,7 @@ keycloak-examples-saml-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 saml-redirect-signatures diff --git a/examples/saml/servlet-filter/pom.xml b/examples/saml/servlet-filter/pom.xml index 1329859169de..3e963e168e0b 100755 --- a/examples/saml/servlet-filter/pom.xml +++ b/examples/saml/servlet-filter/pom.xml @@ -22,7 +22,7 @@ keycloak-examples-saml-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 saml-servlet-filter diff --git a/examples/themes/pom.xml b/examples/themes/pom.xml index 556d9c7a2e41..b51f1e1d0657 100755 --- a/examples/themes/pom.xml +++ b/examples/themes/pom.xml @@ -20,7 +20,7 @@ keycloak-examples-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 Themes Examples diff --git a/federation/kerberos/pom.xml b/federation/kerberos/pom.xml index f6cc7a1be374..9dafb9e46ff7 100755 --- a/federation/kerberos/pom.xml +++ b/federation/kerberos/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../pom.xml 4.0.0 diff --git a/federation/ldap/pom.xml b/federation/ldap/pom.xml index 4f6b3cc285d5..68978ea5b6a0 100755 --- a/federation/ldap/pom.xml +++ b/federation/ldap/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../pom.xml 4.0.0 diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java index 59b995c6cd49..ce84fb23b96b 100755 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java @@ -175,7 +175,11 @@ protected UserModel proxy(RealmModel realm, UserModel local, LDAPObject ldapObje switch (editMode) { case READ_ONLY: + if (model.isImportEnabled()) { + proxied = new ReadonlyLDAPUserModelDelegate(local); + } else { proxied = new ReadOnlyUserModelDelegate(local); + } break; case WRITABLE: case UNSYNCED: diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/ReadonlyLDAPUserModelDelegate.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/ReadonlyLDAPUserModelDelegate.java new file mode 100644 index 000000000000..ba1b94a83342 --- /dev/null +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/ReadonlyLDAPUserModelDelegate.java @@ -0,0 +1,73 @@ +/* + * 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.storage.ldap; + +import java.util.List; + +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.UserModelDelegate; +import org.keycloak.storage.ReadOnlyException; + +/** + * Will be good to get rid of this class and use ReadOnlyUserModelDelegate, but it can't be done now due the backwards compatibility. + * See KEYCLOAK-15139 as an example + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class ReadonlyLDAPUserModelDelegate extends UserModelDelegate implements UserModel { + + public ReadonlyLDAPUserModelDelegate(UserModel delegate) { + super(delegate); + } + + @Override + public void setUsername(String username) { + throw new ReadOnlyException("Federated storage is not writable"); + } + + @Override + public void setLastName(String lastName) { + throw new ReadOnlyException("Federated storage is not writable"); + } + + @Override + public void setFirstName(String first) { + throw new ReadOnlyException("Federated storage is not writable"); + } + + @Override + public void setEmail(String email) { + throw new ReadOnlyException("Federated storage is not writable"); + } + + @Override + public void setSingleAttribute(String name, String value) { + throw new ReadOnlyException("Federated storage is not writable"); + } + + @Override + public void setAttribute(String name, List values) { + throw new ReadOnlyException("Federated storage is not writable"); + } + + @Override + public void removeAttribute(String name) { + throw new ReadOnlyException("Federated storage is not writable"); + } +} diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/FullNameLDAPStorageMapper.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/FullNameLDAPStorageMapper.java index d893a8584d50..4fa689503d2e 100644 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/FullNameLDAPStorageMapper.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/FullNameLDAPStorageMapper.java @@ -141,14 +141,15 @@ public void setSingleAttribute(String name, String value) { @Override public void setAttribute(String name, List values) { + String valueToSet = (values != null && values.size() > 0) ? values.get(0) : null; if (UserModel.FIRST_NAME.equals(name)) { - this.firstName = values.get(0); + this.firstName = valueToSet; setFullNameToLDAPObject(); } else if (UserModel.LAST_NAME.equals(name)) { - this.lastName = values.get(0); + this.lastName = valueToSet; setFullNameToLDAPObject(); } - super.setSingleAttribute(name, values.get(0)); + super.setSingleAttribute(name, valueToSet); } @Override diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/UserAttributeLDAPStorageMapper.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/UserAttributeLDAPStorageMapper.java index 00546afd81ef..475429389108 100644 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/UserAttributeLDAPStorageMapper.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/UserAttributeLDAPStorageMapper.java @@ -22,6 +22,7 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.LDAPConstants; import org.keycloak.models.ModelDuplicateException; +import org.keycloak.models.ModelException; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.models.utils.KeycloakModelUtils; @@ -43,8 +44,6 @@ import java.util.Map; import java.util.Set; -import org.keycloak.models.ModelException; - /** * @author Marek Posolda */ @@ -192,11 +191,10 @@ public UserModel proxy(final LDAPObject ldapUser, UserModel delegate, RealmModel @Override public void setSingleAttribute(String name, String value) { if (UserModel.USERNAME.equals(name)) { - checkDuplicateUsername(userModelAttrName, value, realm, ldapProvider.getSession(), this); + setUsername(value); } else if (UserModel.EMAIL.equals(name)) { - checkDuplicateEmail(userModelAttrName, value, realm, ldapProvider.getSession(), this); - } - if (setLDAPAttribute(name, value)) { + setEmail(value); + } else if (setLDAPAttribute(name, value)) { super.setSingleAttribute(name, value); } } @@ -204,11 +202,10 @@ public void setSingleAttribute(String name, String value) { @Override public void setAttribute(String name, List values) { if (UserModel.USERNAME.equals(name)) { - checkDuplicateUsername(userModelAttrName, values.get(0), realm, ldapProvider.getSession(), this); + setUsername((values != null && values.size() > 0) ? values.get(0) : null); } else if (UserModel.EMAIL.equals(name)) { - checkDuplicateEmail(userModelAttrName, values.get(0), realm, ldapProvider.getSession(), this); - } - if (setLDAPAttribute(name, values)) { + setEmail((values != null && values.size() > 0) ? values.get(0) : null); + } else if (setLDAPAttribute(name, values)) { super.setAttribute(name, values); } } @@ -222,17 +219,19 @@ public void removeAttribute(String name) { @Override public void setUsername(String username) { - checkDuplicateUsername(userModelAttrName, username, realm, ldapProvider.getSession(), this); - setLDAPAttribute(UserModel.USERNAME, username); - super.setUsername(username); + String lowercaseUsername = KeycloakModelUtils.toLowerCaseSafe(username); + checkDuplicateUsername(userModelAttrName, lowercaseUsername, realm, ldapProvider.getSession(), this); + setLDAPAttribute(UserModel.USERNAME, lowercaseUsername); + super.setUsername(lowercaseUsername); } @Override public void setEmail(String email) { + String lowercaseEmail = KeycloakModelUtils.toLowerCaseSafe(email); checkDuplicateEmail(userModelAttrName, email, realm, ldapProvider.getSession(), this); setLDAPAttribute(UserModel.EMAIL, email); - super.setEmail(email); + super.setEmail(lowercaseEmail); } @Override diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapper.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapper.java index ea5a6cc1656a..9ac5f1cbcd9a 100644 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapper.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapper.java @@ -18,6 +18,7 @@ package org.keycloak.storage.ldap.mappers.membership.group; import org.jboss.logging.Logger; +import org.keycloak.common.util.Time; import org.keycloak.component.ComponentModel; import org.keycloak.models.GroupModel; import org.keycloak.models.ModelException; @@ -835,20 +836,46 @@ protected Collection getKcSubGroups(RealmModel realm, GroupModel par } /** - * Provides a list of all KC groups (with their sub groups) from groups path. + * Provides a list of all KC groups (with their sub groups) from groups path configured by the "Groups Path" configuration property. */ protected List getAllKcGroups(RealmModel realm) { - List allGroups = new ArrayList<>(); - for (GroupModel group : getKcSubGroups(realm, null)) { - addGroupAndSubGroups(group, allGroups); + Long start = null; + if (logger.isTraceEnabled()) { + logger.trace("Calling getAllKcGroups started"); + start = Time.currentTimeMillis(); + } + + GroupModel topParentGroup = getKcGroupsPathGroup(realm); + + List allGroups = realm.getGroups(); + List filtered = new ArrayList<>(); + for (GroupModel group : allGroups) { + if (topParentGroup == null) { + filtered.add(group); + } else { + // Check if group is descendant of the topParentGroup (which is group configured by "Groups Path") + boolean finished = false; + GroupModel parent = group.getParent(); + while (!finished) { + if (parent == null) { + finished = true; + } else { + if (parent.getId().equals(topParentGroup.getId())) { + filtered.add(group); + finished = true; + } else { + parent = parent.getParent(); + } + } + } + } } - return allGroups; - } - private void addGroupAndSubGroups(GroupModel group, List allGroups) { - allGroups.add(group); - for (GroupModel subGroup : group.getSubGroups()) { - addGroupAndSubGroups(subGroup, allGroups); + if (logger.isTraceEnabled()) { + long took = Time.currentTimeMillis() - start; + logger.tracef("Calling getAllKcGroups took %d ms. Count of groups %d", took, filtered.size()); } + + return filtered; } } diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapperFactory.java index 0e502d0da62d..dfa6251ca7ae 100644 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapperFactory.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapperFactory.java @@ -17,6 +17,7 @@ package org.keycloak.storage.ldap.mappers.membership.group; +import org.keycloak.common.util.ObjectUtil; import org.keycloak.component.ComponentModel; import org.keycloak.component.ComponentValidationException; import org.keycloak.models.KeycloakSession; @@ -264,6 +265,7 @@ public void onCreate(KeycloakSession session, RealmModel realm, ComponentModel m ComponentModel parentModel = realm.getComponent(model.getParentId()); UserStorageProviderModel parent = new UserStorageProviderModel(parentModel); onParentUpdate(realm, parent, parent, model); + setDefaultGroupsPath(realm, model); } @@ -272,6 +274,14 @@ public void onUpdate(KeycloakSession session, RealmModel realm, ComponentModel o ComponentModel parentModel = realm.getComponent(newModel.getParentId()); UserStorageProviderModel parent = new UserStorageProviderModel(parentModel); onParentUpdate(realm, parent, parent, newModel); + setDefaultGroupsPath(realm, newModel); + } + + private void setDefaultGroupsPath(RealmModel realm, ComponentModel mapperModel) { + if (ObjectUtil.isBlank(mapperModel.getConfig().getFirst(GroupMapperConfig.LDAP_GROUPS_PATH))) { + mapperModel.getConfig().putSingle(GroupMapperConfig.LDAP_GROUPS_PATH, GroupMapperConfig.DEFAULT_LDAP_GROUPS_PATH); + realm.updateComponent(mapperModel); + } } @Override @@ -293,9 +303,8 @@ public void validateConfiguration(KeycloakSession session, RealmModel realm, Com LDAPUtils.validateCustomLdapFilter(config.getConfig().getFirst(GroupMapperConfig.GROUPS_LDAP_FILTER)); - checkMandatoryConfigAttribute(GroupMapperConfig.LDAP_GROUPS_PATH, "Groups Path", config); - String group = config.getConfig().getFirst(GroupMapperConfig.LDAP_GROUPS_PATH).trim(); - if (!"/".equals(group) && KeycloakModelUtils.findGroupByPath(realm, group) == null) { + String group = new GroupMapperConfig(config).getGroupsPath(); + if (!GroupMapperConfig.DEFAULT_LDAP_GROUPS_PATH.equals(group) && KeycloakModelUtils.findGroupByPath(realm, group) == null) { throw new ComponentValidationException("ldapErrorMissingGroupsPathGroup"); } } diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupMapperConfig.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupMapperConfig.java index 399651a050eb..f5067a53604d 100644 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupMapperConfig.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupMapperConfig.java @@ -17,6 +17,7 @@ package org.keycloak.storage.ldap.mappers.membership.group; +import org.keycloak.common.util.ObjectUtil; import org.keycloak.component.ComponentModel; import org.keycloak.models.LDAPConstants; import org.keycloak.models.ModelException; @@ -63,6 +64,7 @@ public class GroupMapperConfig extends CommonLDAPGroupMapperConfig { // Keycloak group path the LDAP groups are added to (default: top level "/") public static final String LDAP_GROUPS_PATH = "groups.path"; + public static final String DEFAULT_LDAP_GROUPS_PATH = "/"; public GroupMapperConfig(ComponentModel mapperModel) { super(mapperModel); @@ -129,7 +131,8 @@ public String getUserGroupsRetrieveStrategy() { } public String getGroupsPath() { - return mapperModel.getConfig().getFirst(LDAP_GROUPS_PATH); + String groupsPath = mapperModel.getConfig().getFirst(LDAP_GROUPS_PATH); + return ObjectUtil.isBlank(groupsPath) ? DEFAULT_LDAP_GROUPS_PATH : groupsPath.trim(); } public String getGroupsPathWithTrailingSlash() { diff --git a/federation/pom.xml b/federation/pom.xml index c9946096b6e4..556f80661138 100755 --- a/federation/pom.xml +++ b/federation/pom.xml @@ -22,7 +22,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml 4.0.0 diff --git a/federation/sssd/pom.xml b/federation/sssd/pom.xml index 70150dc7ceb2..d28f1078f13e 100644 --- a/federation/sssd/pom.xml +++ b/federation/sssd/pom.xml @@ -4,7 +4,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../pom.xml 4.0.0 diff --git a/integration/admin-client/pom.xml b/integration/admin-client/pom.xml index 88312b3bcc4e..26788a6adf33 100755 --- a/integration/admin-client/pom.xml +++ b/integration/admin-client/pom.xml @@ -22,7 +22,7 @@ keycloak-integration-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/integration/client-cli/admin-cli/pom.xml b/integration/client-cli/admin-cli/pom.xml index 88b9f0818439..b5d4f3b56892 100755 --- a/integration/client-cli/admin-cli/pom.xml +++ b/integration/client-cli/admin-cli/pom.xml @@ -21,7 +21,7 @@ keycloak-client-cli-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/integration/client-cli/client-cli-dist/pom.xml b/integration/client-cli/client-cli-dist/pom.xml index 7cd6935f5ec2..af772bba32e6 100755 --- a/integration/client-cli/client-cli-dist/pom.xml +++ b/integration/client-cli/client-cli-dist/pom.xml @@ -21,7 +21,7 @@ keycloak-client-cli-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 keycloak-client-cli-dist diff --git a/integration/client-cli/client-registration-cli/pom.xml b/integration/client-cli/client-registration-cli/pom.xml index d99a7bbd0040..ed6dcb129114 100755 --- a/integration/client-cli/client-registration-cli/pom.xml +++ b/integration/client-cli/client-registration-cli/pom.xml @@ -21,7 +21,7 @@ keycloak-client-cli-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/integration/client-cli/pom.xml b/integration/client-cli/pom.xml index e74debb4e7d8..c97853c08023 100644 --- a/integration/client-cli/pom.xml +++ b/integration/client-cli/pom.xml @@ -20,7 +20,7 @@ keycloak-integration-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 Keycloak Client CLI diff --git a/integration/client-registration/pom.xml b/integration/client-registration/pom.xml index 862d82a9cd8c..86fbe53aabdb 100755 --- a/integration/client-registration/pom.xml +++ b/integration/client-registration/pom.xml @@ -21,7 +21,7 @@ keycloak-integration-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/integration/pom.xml b/integration/pom.xml index 74585e70c6ce..1aed86439cd9 100755 --- a/integration/pom.xml +++ b/integration/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml Keycloak Integration diff --git a/misc/keycloak-test-helper/pom.xml b/misc/keycloak-test-helper/pom.xml index f004903a0c58..8a6a447ade8e 100644 --- a/misc/keycloak-test-helper/pom.xml +++ b/misc/keycloak-test-helper/pom.xml @@ -6,7 +6,7 @@ keycloak-misc-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 org.keycloak keycloak-test-helper diff --git a/misc/pom.xml b/misc/pom.xml index 7ba8410bb643..dc43f6b846ba 100644 --- a/misc/pom.xml +++ b/misc/pom.xml @@ -3,7 +3,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 Keycloak Misc diff --git a/misc/spring-boot-starter/keycloak-spring-boot-starter/pom.xml b/misc/spring-boot-starter/keycloak-spring-boot-starter/pom.xml index 5352612c0e5c..b94b8c15d2b4 100644 --- a/misc/spring-boot-starter/keycloak-spring-boot-starter/pom.xml +++ b/misc/spring-boot-starter/keycloak-spring-boot-starter/pom.xml @@ -4,7 +4,7 @@ org.keycloak keycloak-spring-boot-starter-parent - 11.0.0-SNAPSHOT + 11.0.3 keycloak-spring-boot-starter Keycloak :: Spring :: Boot :: Default :: Starter diff --git a/misc/spring-boot-starter/pom.xml b/misc/spring-boot-starter/pom.xml index edb923206d2e..8c875f9e5666 100644 --- a/misc/spring-boot-starter/pom.xml +++ b/misc/spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ keycloak-misc-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 org.keycloak keycloak-spring-boot-starter-parent diff --git a/misc/spring-legacy-boot-starter/keycloak-legacy-spring-boot-starter/pom.xml b/misc/spring-legacy-boot-starter/keycloak-legacy-spring-boot-starter/pom.xml index 02fdb3ea2e1c..8cc816bd8096 100644 --- a/misc/spring-legacy-boot-starter/keycloak-legacy-spring-boot-starter/pom.xml +++ b/misc/spring-legacy-boot-starter/keycloak-legacy-spring-boot-starter/pom.xml @@ -4,7 +4,7 @@ org.keycloak keycloak-legacy-spring-boot-starter-parent - 11.0.0-SNAPSHOT + 11.0.3 keycloak-legacy-spring-boot-starter Keycloak :: Legacy :: Spring :: Boot :: Default :: Starter diff --git a/misc/spring-legacy-boot-starter/pom.xml b/misc/spring-legacy-boot-starter/pom.xml index 82e18e752a4f..5536c857b55a 100644 --- a/misc/spring-legacy-boot-starter/pom.xml +++ b/misc/spring-legacy-boot-starter/pom.xml @@ -5,7 +5,7 @@ keycloak-misc-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 org.keycloak keycloak-legacy-spring-boot-starter-parent diff --git a/model/infinispan/pom.xml b/model/infinispan/pom.xml index ed58f20047ef..5cc5ba5b0537 100755 --- a/model/infinispan/pom.xml +++ b/model/infinispan/pom.xml @@ -21,7 +21,7 @@ keycloak-model-pom org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java index 938adec47c95..74043f582906 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java @@ -167,12 +167,19 @@ public void setEnabled(boolean enabled) { @Override public void setSingleAttribute(String name, String value) { getDelegateForUpdate(); + if (UserModel.USERNAME.equals(name) || UserModel.EMAIL.equals(name)) { + value = KeycloakModelUtils.toLowerCaseSafe(value); + } updated.setSingleAttribute(name, value); } @Override public void setAttribute(String name, List values) { getDelegateForUpdate(); + if (UserModel.USERNAME.equals(name) || UserModel.EMAIL.equals(name)) { + String lowerCasedFirstValue = KeycloakModelUtils.toLowerCaseSafe((values != null && values.size() > 0) ? values.get(0) : null); + if (lowerCasedFirstValue != null) values.set(0, lowerCasedFirstValue); + } updated.setAttribute(name, values); } diff --git a/model/jpa/pom.xml b/model/jpa/pom.xml index 872d67aad6f3..1c2e113aff7e 100755 --- a/model/jpa/pom.xml +++ b/model/jpa/pom.xml @@ -21,7 +21,7 @@ keycloak-model-pom org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java index b62cc68c4442..ad80953ebcdb 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java @@ -164,17 +164,18 @@ public void setSingleAttribute(String name, String value) { @Override public void setAttribute(String name, List values) { + String valueToSet = (values != null && values.size() > 0) ? values.get(0) : null; if (UserModel.FIRST_NAME.equals(name)) { - user.setFirstName(values.get(0)); + user.setFirstName(valueToSet); return; } else if (UserModel.LAST_NAME.equals(name)) { - user.setLastName(values.get(0)); + user.setLastName(valueToSet); return; } else if (UserModel.EMAIL.equals(name)) { - setEmail(values.get(0)); + setEmail(valueToSet); return; } else if (UserModel.USERNAME.equals(name)) { - setUsername(values.get(0)); + setUsername(valueToSet); return; } // Remove all existing diff --git a/model/map/pom.xml b/model/map/pom.xml index d5b8ed8902a6..57431dfd41db 100644 --- a/model/map/pom.xml +++ b/model/map/pom.xml @@ -3,7 +3,7 @@ keycloak-model-pom org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/model/pom.xml b/model/pom.xml index 0721a5a4f06f..9117f76ea4e9 100755 --- a/model/pom.xml +++ b/model/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml Keycloak Model Parent diff --git a/pom.xml b/pom.xml index 48647ea2c420..81c0164f2296 100755 --- a/pom.xml +++ b/pom.xml @@ -31,7 +31,7 @@ org.keycloak keycloak-parent - 11.0.0-SNAPSHOT + 11.0.3 pom diff --git a/quarkus/deployment/pom.xml b/quarkus/deployment/pom.xml index 83a7b710968b..d1983ee2cff2 100644 --- a/quarkus/deployment/pom.xml +++ b/quarkus/deployment/pom.xml @@ -5,7 +5,7 @@ keycloak-quarkus-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml 4.0.0 diff --git a/quarkus/pom.xml b/quarkus/pom.xml index 2bf32805ff3a..9ddd830cc6ac 100755 --- a/quarkus/pom.xml +++ b/quarkus/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml Keycloak Quarkus Parent diff --git a/quarkus/runtime/pom.xml b/quarkus/runtime/pom.xml index de3f8d55a8cc..931b9f88dcca 100644 --- a/quarkus/runtime/pom.xml +++ b/quarkus/runtime/pom.xml @@ -5,7 +5,7 @@ keycloak-quarkus-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml 4.0.0 diff --git a/quarkus/server/pom.xml b/quarkus/server/pom.xml index 5cc5acfe55ce..f60fde715cb6 100644 --- a/quarkus/server/pom.xml +++ b/quarkus/server/pom.xml @@ -7,7 +7,7 @@ keycloak-quarkus-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml diff --git a/release-details b/release-details new file mode 100644 index 000000000000..77602d0b1b64 --- /dev/null +++ b/release-details @@ -0,0 +1,3 @@ +VERSION=11.0.3 +SHORT_VERSION=11.0 +NPM_VERSION=11.0.3 diff --git a/saml-core-api/pom.xml b/saml-core-api/pom.xml index d7e2396e0005..e83dd76d9109 100755 --- a/saml-core-api/pom.xml +++ b/saml-core-api/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml 4.0.0 diff --git a/saml-core/pom.xml b/saml-core/pom.xml index 9ef609f4fd3a..bdb37e3c0283 100755 --- a/saml-core/pom.xml +++ b/saml-core/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml 4.0.0 diff --git a/saml-core/src/main/java/org/keycloak/saml/common/util/StaxParserUtil.java b/saml-core/src/main/java/org/keycloak/saml/common/util/StaxParserUtil.java index e4bd59d63284..10622bb012b6 100755 --- a/saml-core/src/main/java/org/keycloak/saml/common/util/StaxParserUtil.java +++ b/saml-core/src/main/java/org/keycloak/saml/common/util/StaxParserUtil.java @@ -895,6 +895,7 @@ private static XMLInputFactory getXMLInputFactory() { xmlInputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE); xmlInputFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.TRUE); xmlInputFactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE); + xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE); return xmlInputFactory; } finally { diff --git a/saml-core/src/test/java/org/keycloak/saml/common/util/StaxParserUtilTest.java b/saml-core/src/test/java/org/keycloak/saml/common/util/StaxParserUtilTest.java index 14438c4eeb70..7a4c277704bb 100644 --- a/saml-core/src/test/java/org/keycloak/saml/common/util/StaxParserUtilTest.java +++ b/saml-core/src/test/java/org/keycloak/saml/common/util/StaxParserUtilTest.java @@ -214,4 +214,34 @@ public void testGetDOMElementSameElements() throws XMLStreamException, ParsingEx Assert.fail(String.valueOf(reader.nextEvent())); } + @Test + public void testXMLBombAttack() throws XMLStreamException { + String xml = "" + + "" + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + "]>" + + "&lol9;"; + + XMLEventReader reader = StaxParserUtil.getXMLEventReader(IOUtils.toInputStream(xml, Charset.defaultCharset())); + + reader.nextEvent(); // + reader.nextEvent(); // + try { + StaxParserUtil.getDOMElement(reader); + } catch (ParsingException exception) { + // DTD should be disabled for SAML Parser, therefore this should fail with following error message + assertThat("", exception.getMessage(), containsString("The entity \"lol9\" was referenced, but not declared")); + } + } + } diff --git a/server-spi-private/pom.xml b/server-spi-private/pom.xml index 47b32c7f75a3..e8d6ba8894b5 100755 --- a/server-spi-private/pom.xml +++ b/server-spi-private/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml 4.0.0 diff --git a/server-spi-private/src/main/java/org/keycloak/models/BrowserSecurityHeaders.java b/server-spi-private/src/main/java/org/keycloak/models/BrowserSecurityHeaders.java old mode 100755 new mode 100644 index 53c1f12a27ca..5e584c274443 --- a/server-spi-private/src/main/java/org/keycloak/models/BrowserSecurityHeaders.java +++ b/server-spi-private/src/main/java/org/keycloak/models/BrowserSecurityHeaders.java @@ -21,125 +21,54 @@ import java.util.HashMap; import java.util.Map; -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class BrowserSecurityHeaders { - - public static final String X_FRAME_OPTIONS = "X-Frame-Options"; - - public static final String X_FRAME_OPTIONS_DEFAULT = "SAMEORIGIN"; - - public static final String X_FRAME_OPTIONS_KEY = "xFrameOptions"; - - public static final String CONTENT_SECURITY_POLICY = "Content-Security-Policy"; - - public static final String CONTENT_SECURITY_POLICY_DEFAULT = ContentSecurityPolicyBuilder.create().build(); - - public static final String CONTENT_SECURITY_POLICY_KEY = "contentSecurityPolicy"; - - public static final String CONTENT_SECURITY_POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only"; - - public static final String CONTENT_SECURITY_POLICY_REPORT_ONLY_DEFAULT = ""; - - public static final String CONTENT_SECURITY_POLICY_REPORT_ONLY_KEY = "contentSecurityPolicyReportOnly"; - - public static final String X_CONTENT_TYPE_OPTIONS = "X-Content-Type-Options"; - - public static final String X_CONTENT_TYPE_OPTIONS_DEFAULT = "nosniff"; - - public static final String X_CONTENT_TYPE_OPTIONS_KEY = "xContentTypeOptions"; - - public static final String X_ROBOTS_TAG = "X-Robots-Tag"; - - public static final String X_ROBOTS_TAG_KEY = "xRobotsTag"; - - public static final String X_ROBOTS_TAG_DEFAULT = "none"; - - public static final String X_XSS_PROTECTION = "X-XSS-Protection"; - - public static final String X_XSS_PROTECTION_DEFAULT = "1; mode=block"; - - public static final String X_XSS_PROTECTION_KEY = "xXSSProtection"; +public enum BrowserSecurityHeaders { + + X_FRAME_OPTIONS("xFrameOptions", "X-Frame-Options", "SAMEORIGIN"), + CONTENT_SECURITY_POLICY("contentSecurityPolicy", "Content-Security-Policy", ContentSecurityPolicyBuilder.create().build()), + CONTENT_SECURITY_POLICY_REPORT_ONLY("contentSecurityPolicyReportOnly", "Content-Security-Policy-Report-Only", ""), + X_CONTENT_TYPE_OPTIONS("xContentTypeOptions", "X-Content-Type-Options", "nosniff"), + X_ROBOTS_TAG("xRobotsTag", "X-Robots-Tag", "none"), + X_XSS_PROTECTION("xXSSProtection", "X-XSS-Protection", "1; mode=block"), + STRICT_TRANSPORT_SECURITY("strictTransportSecurity", "Strict-Transport-Security", "max-age=31536000; includeSubDomains"), + REFERRER_POLICY("referrerPolicy", "Referrer-Policy", "no-referrer"), + ; + + private final String key; + private final String headerName; + private final String defaultValue; + + BrowserSecurityHeaders(String key, String headerName, String defaultValue) { + this.key = key; + this.headerName = headerName; + this.defaultValue = defaultValue; + } - public static final String STRICT_TRANSPORT_SECURITY = "Strict-Transport-Security"; + public String getKey() { + return key; + } - public static final String STRICT_TRANSPORT_SECURITY_DEFAULT = "max-age=31536000; includeSubDomains"; + public String getHeaderName() { + return headerName; + } - public static final String STRICT_TRANSPORT_SECURITY_KEY = "strictTransportSecurity"; + public String getDefaultValue() { + return defaultValue; + } - public static final Map headerAttributeMap; - public static final Map defaultHeaders; + @Deprecated // should be removed eventually + public static final Map realmDefaultHeaders; static { - Map headerMap = new HashMap<>(); - headerMap.put(X_FRAME_OPTIONS_KEY, X_FRAME_OPTIONS); - headerMap.put(CONTENT_SECURITY_POLICY_KEY, CONTENT_SECURITY_POLICY); - headerMap.put(CONTENT_SECURITY_POLICY_REPORT_ONLY_KEY, CONTENT_SECURITY_POLICY_REPORT_ONLY); - headerMap.put(X_CONTENT_TYPE_OPTIONS_KEY, X_CONTENT_TYPE_OPTIONS); - headerMap.put(X_ROBOTS_TAG_KEY, X_ROBOTS_TAG); - headerMap.put(X_XSS_PROTECTION_KEY, X_XSS_PROTECTION); - headerMap.put(STRICT_TRANSPORT_SECURITY_KEY, STRICT_TRANSPORT_SECURITY); Map dh = new HashMap<>(); - dh.put(X_FRAME_OPTIONS_KEY, X_FRAME_OPTIONS_DEFAULT); - dh.put(CONTENT_SECURITY_POLICY_KEY, CONTENT_SECURITY_POLICY_DEFAULT); - dh.put(CONTENT_SECURITY_POLICY_REPORT_ONLY_KEY, CONTENT_SECURITY_POLICY_REPORT_ONLY_DEFAULT); - dh.put(X_CONTENT_TYPE_OPTIONS_KEY, X_CONTENT_TYPE_OPTIONS_DEFAULT); - dh.put(X_ROBOTS_TAG_KEY, X_ROBOTS_TAG_DEFAULT); - dh.put(X_XSS_PROTECTION_KEY, X_XSS_PROTECTION_DEFAULT); - dh.put(STRICT_TRANSPORT_SECURITY_KEY, STRICT_TRANSPORT_SECURITY_DEFAULT); - - defaultHeaders = Collections.unmodifiableMap(dh); - headerAttributeMap = Collections.unmodifiableMap(headerMap); + dh.put(X_FRAME_OPTIONS.getKey(), X_FRAME_OPTIONS.getDefaultValue()); + dh.put(CONTENT_SECURITY_POLICY.getKey(), CONTENT_SECURITY_POLICY.getDefaultValue()); + dh.put(CONTENT_SECURITY_POLICY_REPORT_ONLY.getKey(), CONTENT_SECURITY_POLICY_REPORT_ONLY.getDefaultValue()); + dh.put(X_CONTENT_TYPE_OPTIONS.getKey(), X_CONTENT_TYPE_OPTIONS.getDefaultValue()); + dh.put(X_ROBOTS_TAG.getKey(), X_ROBOTS_TAG.getDefaultValue()); + dh.put(X_XSS_PROTECTION.getKey(), X_XSS_PROTECTION.getDefaultValue()); + dh.put(STRICT_TRANSPORT_SECURITY.getKey(), STRICT_TRANSPORT_SECURITY.getDefaultValue()); + + realmDefaultHeaders = Collections.unmodifiableMap(dh); } - - public static class ContentSecurityPolicyBuilder { - - private String frameSrc = "'self'"; - private String frameAncestors = "'self'"; - private String objectSrc = "'none'"; - - private boolean first; - private StringBuilder sb; - - public static ContentSecurityPolicyBuilder create() { - return new ContentSecurityPolicyBuilder(); - } - - public ContentSecurityPolicyBuilder frameSrc(String frameSrc) { - this.frameSrc = frameSrc; - return this; - } - - public ContentSecurityPolicyBuilder frameAncestors(String frameancestors) { - this.frameAncestors = frameancestors; - return this; - } - - public String build() { - sb = new StringBuilder(); - first = true; - - build("frame-src", frameSrc); - build("frame-ancestors", frameAncestors); - build("object-src", objectSrc); - - return sb.toString(); - } - - private void build(String k, String v) { - if (v != null) { - if (!first) { - sb.append(" "); - } - first = false; - - sb.append(k).append(" ").append(v).append(";"); - } - } - - } - } diff --git a/server-spi-private/src/main/java/org/keycloak/models/Constants.java b/server-spi-private/src/main/java/org/keycloak/models/Constants.java index 617acf30fc05..ed63173086db 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/Constants.java +++ b/server-spi-private/src/main/java/org/keycloak/models/Constants.java @@ -105,4 +105,9 @@ public final class Constants { public static final Pattern CFG_DELIMITER_PATTERN = Pattern.compile("\\s*" + CFG_DELIMITER + "\\s*"); public static final String OFFLINE_ACCESS_SCOPE_CONSENT_TEXT = "${offlineAccessScopeConsentText}"; + + public static final String LEVEL_OF_AUTHENTICATION = "level-of-authentication"; + public static final String REQUESTED_LEVEL_OF_AUTHENTICATION = "requested-level-of-authentication"; + public static final String FORCE_LEVEL_OF_AUTHENTICATION = "force-level-of-authentication"; + public static final String ACR_LOA_MAP = "acr.loa.map"; } diff --git a/server-spi-private/src/main/java/org/keycloak/models/ContentSecurityPolicyBuilder.java b/server-spi-private/src/main/java/org/keycloak/models/ContentSecurityPolicyBuilder.java new file mode 100644 index 000000000000..b220c1f50f0f --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/models/ContentSecurityPolicyBuilder.java @@ -0,0 +1,48 @@ +package org.keycloak.models; + +public class ContentSecurityPolicyBuilder { + + private String frameSrc = "'self'"; + private String frameAncestors = "'self'"; + private String objectSrc = "'none'"; + + private boolean first; + private StringBuilder sb; + + public static ContentSecurityPolicyBuilder create() { + return new ContentSecurityPolicyBuilder(); + } + + public ContentSecurityPolicyBuilder frameSrc(String frameSrc) { + this.frameSrc = frameSrc; + return this; + } + + public ContentSecurityPolicyBuilder frameAncestors(String frameancestors) { + this.frameAncestors = frameancestors; + return this; + } + + public String build() { + sb = new StringBuilder(); + first = true; + + build("frame-src", frameSrc); + build("frame-ancestors", frameAncestors); + build("object-src", objectSrc); + + return sb.toString(); + } + + private void build(String k, String v) { + if (v != null) { + if (!first) { + sb.append(" "); + } + first = false; + + sb.append(k).append(" ").append(v).append(";"); + } + } + +} diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index 52ff18538dd5..117064e1b0db 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -29,7 +29,6 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import java.util.function.Function; import java.util.stream.Collectors; import org.jboss.logging.Logger; @@ -136,7 +135,7 @@ import org.keycloak.storage.UserStorageProviderModel; import org.keycloak.storage.federated.UserFederatedStorageProvider; import org.keycloak.util.JsonSerialization; -import org.keycloak.validation.ClientValidationUtil; +import org.keycloak.validation.ValidationUtil; public class RepresentationToModel { @@ -385,7 +384,7 @@ public static void importRealm(KeycloakSession session, RealmRepresentation rep, if (rep.getBrowserSecurityHeaders() != null) { newRealm.setBrowserSecurityHeaders(rep.getBrowserSecurityHeaders()); } else { - newRealm.setBrowserSecurityHeaders(BrowserSecurityHeaders.defaultHeaders); + newRealm.setBrowserSecurityHeaders(BrowserSecurityHeaders.realmDefaultHeaders); } if (rep.getComponents() != null) { @@ -1265,8 +1264,8 @@ private static Map createClients(KeycloakSession session, R ClientModel app = createClient(session, realm, resourceRep, false, mappedFlows); appMap.put(app.getClientId(), app); - ClientValidationUtil.validate(session, app, false, c -> { - throw new RuntimeException("Invalid client " + app.getClientId() + ": " + c.getError()); + ValidationUtil.validateClient(session, app, false, r -> { + throw new RuntimeException("Invalid client " + app.getClientId() + ": " + r.getAllErrorsAsString()); }); } return appMap; diff --git a/server-spi-private/src/main/java/org/keycloak/storage/adapter/InMemoryUserAdapter.java b/server-spi-private/src/main/java/org/keycloak/storage/adapter/InMemoryUserAdapter.java index a1704cac6293..f2f541586f88 100644 --- a/server-spi-private/src/main/java/org/keycloak/storage/adapter/InMemoryUserAdapter.java +++ b/server-spi-private/src/main/java/org/keycloak/storage/adapter/InMemoryUserAdapter.java @@ -26,6 +26,7 @@ import org.keycloak.models.UserModel; import org.keycloak.models.UserModelDefaultMethods; import org.keycloak.models.utils.DefaultRoles; +import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.RoleUtils; import org.keycloak.storage.ReadOnlyException; @@ -123,6 +124,9 @@ public void setEnabled(boolean enabled) { @Override public void setSingleAttribute(String name, String value) { checkReadonly(); + if (UserModel.USERNAME.equals(name) || UserModel.EMAIL.equals(name)) { + value = KeycloakModelUtils.toLowerCaseSafe(value); + } attributes.putSingle(name, value); } @@ -130,6 +134,10 @@ public void setSingleAttribute(String name, String value) { @Override public void setAttribute(String name, List values) { checkReadonly(); + if (UserModel.USERNAME.equals(name) || UserModel.EMAIL.equals(name)) { + String lowerCasedFirstValue = KeycloakModelUtils.toLowerCaseSafe((values != null && values.size() > 0) ? values.get(0) : null); + if (lowerCasedFirstValue != null) values.set(0, lowerCasedFirstValue); + } attributes.put(name, values); } diff --git a/server-spi-private/src/main/java/org/keycloak/validation/ClientValidationContext.java b/server-spi-private/src/main/java/org/keycloak/validation/ClientValidationContext.java index aba7f7a43199..d12d0cbb1858 100644 --- a/server-spi-private/src/main/java/org/keycloak/validation/ClientValidationContext.java +++ b/server-spi-private/src/main/java/org/keycloak/validation/ClientValidationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Red Hat, Inc. and/or its affiliates + * Copyright 2020 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"); @@ -14,26 +14,31 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.keycloak.validation; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; +import org.keycloak.representations.oidc.OIDCClientRepresentation; -public interface ClientValidationContext { - - enum Event { - CREATE, - UPDATE +/** + * @author Vaclav Muzikar + */ +public class ClientValidationContext extends DefaultValidationContext { + public ClientValidationContext(Event event, KeycloakSession session, ClientModel objectToValidate) { + super(event, session, objectToValidate); } - Event getEvent(); - - KeycloakSession getSession(); + public static class OIDCContext extends ClientValidationContext { + private final OIDCClientRepresentation oidcClient; - ClientModel getClient(); - - String getError(); - - ClientValidationContext invalid(String error); + public OIDCContext(Event event, KeycloakSession session, ClientModel objectToValidate, OIDCClientRepresentation oidcClient) { + super(event, session, objectToValidate); + this.oidcClient = oidcClient; + } + public OIDCClientRepresentation getOIDCClient() { + return oidcClient; + } + } } diff --git a/server-spi-private/src/main/java/org/keycloak/validation/ClientValidationProvider.java b/server-spi-private/src/main/java/org/keycloak/validation/ClientValidationProvider.java index 347b44b17a2d..0f1b144488e0 100644 --- a/server-spi-private/src/main/java/org/keycloak/validation/ClientValidationProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/validation/ClientValidationProvider.java @@ -16,11 +16,12 @@ */ package org.keycloak.validation; -import org.keycloak.provider.Provider; +import org.keycloak.models.ClientModel; -public interface ClientValidationProvider extends Provider { +public interface ClientValidationProvider extends Validator { - void validate(ClientValidationContext context); + // for a special case when performing OIDC client registration + ValidationResult validate(ClientValidationContext.OIDCContext validationContext); @Override default void close() { diff --git a/server-spi-private/src/main/java/org/keycloak/validation/ClientValidationUtil.java b/server-spi-private/src/main/java/org/keycloak/validation/ClientValidationUtil.java deleted file mode 100644 index beac8722b2b1..000000000000 --- a/server-spi-private/src/main/java/org/keycloak/validation/ClientValidationUtil.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2019 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.validation; - -import org.keycloak.models.ClientModel; -import org.keycloak.models.KeycloakSession; - -import javax.ws.rs.BadRequestException; - -public class ClientValidationUtil { - - public static void validate(KeycloakSession session, ClientModel client, boolean create, ErrorHandler errorHandler) throws BadRequestException { - ClientValidationProvider provider = session.getProvider(ClientValidationProvider.class); - if (provider != null) { - DefaultClientValidationContext context = new DefaultClientValidationContext(create ? ClientValidationContext.Event.CREATE : ClientValidationContext.Event.UPDATE, session, client); - provider.validate(context); - - if (!context.isValid()) { - errorHandler.onError(context); - } - } - } - - public interface ErrorHandler { - - void onError(ClientValidationContext context); - - } - - private static class DefaultClientValidationContext implements ClientValidationContext { - - private Event event; - private KeycloakSession session; - private ClientModel client; - - private String error; - - public DefaultClientValidationContext(Event event, KeycloakSession session, ClientModel client) { - this.event = event; - this.session = session; - this.client = client; - } - - public boolean isValid() { - return error == null; - } - - public String getError() { - return error; - } - - @Override - public Event getEvent() { - return event; - } - - @Override - public KeycloakSession getSession() { - return session; - } - - @Override - public ClientModel getClient() { - return client; - } - - @Override - public ClientValidationContext invalid(String error) { - this.error = error; - return this; - } - } - -} diff --git a/server-spi-private/src/main/java/org/keycloak/validation/DefaultValidationContext.java b/server-spi-private/src/main/java/org/keycloak/validation/DefaultValidationContext.java new file mode 100644 index 000000000000..27022158e991 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/validation/DefaultValidationContext.java @@ -0,0 +1,77 @@ +/* + * Copyright 2020 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.validation; + +import org.keycloak.models.KeycloakSession; + +import java.util.HashSet; +import java.util.Set; + +/** + * @author Vaclav Muzikar + */ +public abstract class DefaultValidationContext implements ValidationContext { + + private final Event event; + private final KeycloakSession session; + private final T objectToValidate; + private final Set errors; + + public DefaultValidationContext(Event event, KeycloakSession session, T objectToValidate) { + this.event = event; + this.session = session; + this.objectToValidate = objectToValidate; + this.errors = new HashSet<>(); + } + + @Override + public Event getEvent() { + return event; + } + + @Override + public KeycloakSession getSession() { + return session; + } + + @Override + public T getObjectToValidate() { + return objectToValidate; + } + + @Override + public ValidationContext addError(String message) { + return addError(null, message, null); + } + + @Override + public ValidationContext addError(String fieldId, String message) { + return addError(fieldId, message, null); + } + + @Override + public ValidationContext addError(String fieldId, String message, String localizedMessageKey, Object... localizedMessageParams) { + errors.add(new ValidationError(fieldId, message, localizedMessageKey, localizedMessageParams)); + return this; + } + + @Override + public ValidationResult toResult() { + return new ValidationResult(new HashSet<>(errors)); + } +} diff --git a/server-spi-private/src/main/java/org/keycloak/validation/ValidationContext.java b/server-spi-private/src/main/java/org/keycloak/validation/ValidationContext.java new file mode 100644 index 000000000000..6d1784bbf10a --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/validation/ValidationContext.java @@ -0,0 +1,39 @@ +/* + * Copyright 2019 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.validation; + +import org.keycloak.models.KeycloakSession; + +public interface ValidationContext { + + enum Event { + CREATE, + UPDATE + } + + Event getEvent(); + + KeycloakSession getSession(); + + T getObjectToValidate(); + + ValidationContext addError(String message); + ValidationContext addError(String fieldId, String message); + ValidationContext addError(String fieldId, String message, String localizedMessageKey, Object... localizedMessageParams); + + ValidationResult toResult(); +} diff --git a/server-spi-private/src/main/java/org/keycloak/validation/ValidationError.java b/server-spi-private/src/main/java/org/keycloak/validation/ValidationError.java new file mode 100644 index 000000000000..4be55895ca67 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/validation/ValidationError.java @@ -0,0 +1,87 @@ +/* + * Copyright 2020 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.validation; + +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.Objects; +import java.util.Properties; + +/** + * @author Vaclav Muzikar + */ +public class ValidationError { + private final String fieldId; + private final String message; + private final String localizedMessageKey; + private final Object[] localizedMessageParameters; + + public ValidationError(String fieldId, String message, String localizedMessageKey, Object[] localizedMessageParameters) { + if (message == null) { + throw new IllegalArgumentException("Message must be set"); + } + + this.fieldId = fieldId; + this.message = message; + this.localizedMessageKey = localizedMessageKey; + this.localizedMessageParameters = localizedMessageParameters; + } + + public String getFieldId() { + return fieldId; + } + + public String getLocalizedMessageKey() { + return localizedMessageKey; + } + + public Object[] getLocalizedMessageParams() { + return localizedMessageParameters; + } + + public String getMessage() { + return message; + } + + public String getLocalizedMessage(Properties messagesBundle) { + if (getLocalizedMessageKey() != null) { + return MessageFormat.format(messagesBundle.getProperty(getLocalizedMessageKey(), getMessage()), getLocalizedMessageParams()); + } + else { + return getMessage(); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ValidationError error = (ValidationError) o; + return Objects.equals(fieldId, error.fieldId) && + message.equals(error.message) && + Objects.equals(localizedMessageKey, error.localizedMessageKey) && + Arrays.equals(localizedMessageParameters, error.localizedMessageParameters); + } + + @Override + public int hashCode() { + int result = Objects.hash(fieldId, message, localizedMessageKey); + result = 31 * result + Arrays.hashCode(localizedMessageParameters); + return result; + } +} diff --git a/server-spi-private/src/main/java/org/keycloak/validation/ValidationResult.java b/server-spi-private/src/main/java/org/keycloak/validation/ValidationResult.java new file mode 100644 index 000000000000..dfd1ed94224b --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/validation/ValidationResult.java @@ -0,0 +1,69 @@ +/* + * Copyright 2020 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.validation; + +import java.util.Collections; +import java.util.Properties; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * @author Vaclav Muzikar + */ +public class ValidationResult { + private final boolean valid; + private final Set errors; + + public ValidationResult(Set errors) { + this.valid = errors.size() == 0; + this.errors = Collections.unmodifiableSet(errors); + } + + public boolean isValid() { + return valid; + } + + public Set getErrors() { + return errors; + } + + public String getAllErrorsAsString() { + return getAllErrorsAsString(ValidationError::getMessage); + } + + public String getAllLocalizedErrorsAsString(Properties messagesBundle) { + return getAllErrorsAsString(x -> x.getLocalizedMessage(messagesBundle)); + } + + protected String getAllErrorsAsString(Function function) { + return errors.stream().map(function).collect(Collectors.joining("; ")); + } + + public boolean fieldHasError(String fieldId) { + if (fieldId == null) { + return false; + } + for (ValidationError error : errors) { + if (fieldId.equals(error.getFieldId())) { + return true; + } + } + return false; + } +} diff --git a/server-spi-private/src/main/java/org/keycloak/validation/ValidationUtil.java b/server-spi-private/src/main/java/org/keycloak/validation/ValidationUtil.java new file mode 100644 index 000000000000..ee66ecaca89d --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/validation/ValidationUtil.java @@ -0,0 +1,56 @@ +/* + * Copyright 2019 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.validation; + +import org.keycloak.models.ClientModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.representations.oidc.OIDCClientRepresentation; + +import javax.ws.rs.BadRequestException; + +public class ValidationUtil { + + public static void validateClient(KeycloakSession session, ClientModel client, boolean create, ErrorHandler errorHandler) throws BadRequestException { + validateClient(session, client, null, create, errorHandler); + } + + public static void validateClient(KeycloakSession session, ClientModel client, OIDCClientRepresentation oidcClient, boolean create, ErrorHandler errorHandler) throws BadRequestException { + ClientValidationProvider provider = session.getProvider(ClientValidationProvider.class); + if (provider != null) { + ValidationContext.Event event = create ? ValidationContext.Event.CREATE : ValidationContext.Event.UPDATE; + ValidationResult result; + + if (oidcClient != null) { + result = provider.validate(new ClientValidationContext.OIDCContext(event, session, client, oidcClient)); + } + else { + result = provider.validate(new ClientValidationContext(event, session, client)); + } + + if (!result.isValid()) { + errorHandler.onError(result); + } + } + } + + public interface ErrorHandler { + + void onError(ValidationResult context); + + } + +} diff --git a/server-spi-private/src/main/java/org/keycloak/validation/Validator.java b/server-spi-private/src/main/java/org/keycloak/validation/Validator.java new file mode 100644 index 000000000000..46abf08a77ad --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/validation/Validator.java @@ -0,0 +1,27 @@ +/* + * Copyright 2020 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.validation; + +import org.keycloak.provider.Provider; + +/** + * @author Vaclav Muzikar + */ +public interface Validator extends Provider { + ValidationResult validate(ValidationContext validationContext); +} diff --git a/server-spi-private/src/test/java/org/keycloak/models/BrowserSecurityHeadersTest.java b/server-spi-private/src/test/java/org/keycloak/models/BrowserSecurityHeadersTest.java index 648925fb9b5a..7535e32c893c 100644 --- a/server-spi-private/src/test/java/org/keycloak/models/BrowserSecurityHeadersTest.java +++ b/server-spi-private/src/test/java/org/keycloak/models/BrowserSecurityHeadersTest.java @@ -8,10 +8,10 @@ public class BrowserSecurityHeadersTest { @Test public void contentSecurityPolicyBuilderTest() { - assertEquals("frame-src 'self'; frame-ancestors 'self'; object-src 'none';", BrowserSecurityHeaders.ContentSecurityPolicyBuilder.create().build()); - assertEquals("frame-ancestors 'self'; object-src 'none';", BrowserSecurityHeaders.ContentSecurityPolicyBuilder.create().frameSrc(null).build()); - assertEquals("frame-src 'self'; object-src 'none';", BrowserSecurityHeaders.ContentSecurityPolicyBuilder.create().frameAncestors(null).build()); - assertEquals("frame-src 'custom-frame-src'; frame-ancestors 'custom-frame-ancestors'; object-src 'none';", BrowserSecurityHeaders.ContentSecurityPolicyBuilder.create().frameSrc("'custom-frame-src'").frameAncestors("'custom-frame-ancestors'").build()); + assertEquals("frame-src 'self'; frame-ancestors 'self'; object-src 'none';", ContentSecurityPolicyBuilder.create().build()); + assertEquals("frame-ancestors 'self'; object-src 'none';", ContentSecurityPolicyBuilder.create().frameSrc(null).build()); + assertEquals("frame-src 'self'; object-src 'none';", ContentSecurityPolicyBuilder.create().frameAncestors(null).build()); + assertEquals("frame-src 'custom-frame-src'; frame-ancestors 'custom-frame-ancestors'; object-src 'none';", ContentSecurityPolicyBuilder.create().frameSrc("'custom-frame-src'").frameAncestors("'custom-frame-ancestors'").build()); } } diff --git a/server-spi/pom.xml b/server-spi/pom.xml index 20123ffe61f7..9bd654afa8b4 100755 --- a/server-spi/pom.xml +++ b/server-spi/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml 4.0.0 diff --git a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java index 1a9d7f6f2cab..7703d4d315e1 100644 --- a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java +++ b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java @@ -340,9 +340,9 @@ public void setCreatedTimestamp(Long timestamp) { public void setSingleAttribute(String name, String value) { if (UserModel.USERNAME.equals(name)) { setUsername(value); + } else { + getFederatedStorage().setSingleAttribute(realm, this.getId(), mapAttribute(name), value); } - getFederatedStorage().setSingleAttribute(realm, this.getId(), mapAttribute(name), value); - } @Override @@ -354,10 +354,10 @@ public void removeAttribute(String name) { @Override public void setAttribute(String name, List values) { if (UserModel.USERNAME.equals(name)) { - setUsername(values.get(0)); + setUsername((values != null && values.size() > 0) ? values.get(0) : null); + } else { + getFederatedStorage().setAttribute(realm, this.getId(), mapAttribute(name), values); } - getFederatedStorage().setAttribute(realm, this.getId(), mapAttribute(name), values); - } @Override diff --git a/services/pom.xml b/services/pom.xml index 9c181952293d..9f660a0a48ef 100755 --- a/services/pom.xml +++ b/services/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml 4.0.0 diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationSelectionResolver.java b/services/src/main/java/org/keycloak/authentication/AuthenticationSelectionResolver.java index fd96f3272908..65726e9e959d 100644 --- a/services/src/main/java/org/keycloak/authentication/AuthenticationSelectionResolver.java +++ b/services/src/main/java/org/keycloak/authentication/AuthenticationSelectionResolver.java @@ -201,7 +201,7 @@ private static boolean addAllExecutionsFromSubflow(AuthenticationProcessor proce // For conditional execution, we must check if condition is true. Otherwise return false, which means trying next // requiredExecution in the list - return !flow.isConditionalSubflowDisabled(ex); + return !flow.isConditionalSubflowDisabled(ex, false); }).findFirst().orElse(null); diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticatorUtil.java b/services/src/main/java/org/keycloak/authentication/AuthenticatorUtil.java index 1c18b66d57a0..75f8083b4ff9 100755 --- a/services/src/main/java/org/keycloak/authentication/AuthenticatorUtil.java +++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorUtil.java @@ -17,11 +17,13 @@ package org.keycloak.authentication; -import org.keycloak.models.AuthenticationExecutionModel; -import org.keycloak.models.RealmModel; - import java.util.LinkedList; import java.util.List; +import org.keycloak.models.AuthenticatedClientSessionModel; +import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.Constants; +import org.keycloak.models.RealmModel; +import org.keycloak.sessions.AuthenticationSessionModel; /** * @author Bill Burke @@ -75,4 +77,28 @@ public static boolean isRequired(RealmModel realm, String flowId, String authPro } return execution.isRequired(); } + + public static boolean isLevelOfAuthenticationForced(AuthenticationSessionModel authSession) { + return Boolean.parseBoolean(authSession.getClientNote(Constants.FORCE_LEVEL_OF_AUTHENTICATION)); + } + + public static int getRequestedLevelOfAuthentication(AuthenticationSessionModel authSession) { + String requiredLoa = authSession.getClientNote(Constants.REQUESTED_LEVEL_OF_AUTHENTICATION); + return requiredLoa == null ? -1 : Integer.parseInt(requiredLoa); + } + + public static int getCurrentLevelOfAuthentication(AuthenticationSessionModel authSession) { + String authSessionLoa = authSession.getAuthNote(Constants.LEVEL_OF_AUTHENTICATION); + String userSessionLoa = authSession.getUserSessionNotes().get(Constants.LEVEL_OF_AUTHENTICATION); + return authSessionLoa == null + ? userSessionLoa == null ? -1 : Integer.parseInt(userSessionLoa) + : userSessionLoa == null ? Integer.parseInt(authSessionLoa) + : Math.max(Integer.parseInt(authSessionLoa), Integer.parseInt(userSessionLoa)); + } + + public static boolean isLevelOfAuthenticationSatisfied(AuthenticationSessionModel authSession) { + int requestedLoa = AuthenticatorUtil.getRequestedLevelOfAuthentication(authSession); + int currentLoa = AuthenticatorUtil.getCurrentLevelOfAuthentication(authSession); + return currentLoa >= requestedLoa && requestedLoa >= 0; + } } diff --git a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java index 4e512a15f7b7..9d32ed3e242b 100755 --- a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java +++ b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java @@ -17,26 +17,27 @@ package org.keycloak.authentication; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; +import javax.ws.rs.HttpMethod; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; import org.jboss.logging.Logger; import org.keycloak.OAuth2Constants; import org.keycloak.authentication.authenticators.conditional.ConditionalAuthenticator; import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.AuthenticationFlowModel; import org.keycloak.models.Constants; -import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; +import org.keycloak.models.UserSessionModel; +import org.keycloak.services.ErrorPage; import org.keycloak.services.ServicesLogger; +import org.keycloak.services.messages.Messages; import org.keycloak.sessions.AuthenticationSessionModel; -import javax.ws.rs.HttpMethod; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.Response; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.stream.Collectors; - /** * @author Bill Burke * @version $Revision: 1 $ @@ -168,6 +169,8 @@ public Response processAction(String actionExecution) { /** * Called after "actionExecutionModel" execution is finished (Either successful or attempted). Find the next appropriate authentication * flow where the authentication should continue and continue with authentication process. + * The method recursively continues with the parent flow + * until finally the top flow is processed. * * @param actionExecutionModel * @return Response if some more forms should be displayed during authentication. Null otherwise. @@ -184,8 +187,8 @@ private Response continueAuthenticationAfterSuccessfulAction(AuthenticationExecu } else { Response response = processSingleFlowExecutionModel(parentFlowExecution, false); if (response == null) { - processor.getAuthenticationSession().removeAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION); - return processFlow(); + // the parent flow is now the last action that has been executed, continue with that until the top flow is reached + return continueAuthenticationAfterSuccessfulAction(parentFlowExecution); } else { return response; } @@ -240,23 +243,24 @@ public Response processFlow() { fillListsOfExecutions(executions, requiredList, alternativeList); //handle required elements : all required elements need to be executed - boolean requiredElementsSuccessful = true; + boolean flowSuccessful = true; Iterator requiredIListIterator = requiredList.listIterator(); while (requiredIListIterator.hasNext()) { AuthenticationExecutionModel required = requiredIListIterator.next(); //Conditional flows must be considered disabled (non-existent) if their condition evaluates to false. - if (required.isConditional() && isConditionalSubflowDisabled(required)) { + //If the flow has been processed before it will not be removed to consider its execution status. + if (required.isConditional() && !isProcessed(required) && isConditionalSubflowDisabled(required, true)) { requiredIListIterator.remove(); continue; } Response response = processSingleFlowExecutionModel(required, true); - requiredElementsSuccessful &= processor.isSuccessful(required) || isSetupRequired(required); + flowSuccessful = processor.isSuccessful(required) || isSetupRequired(required); if (response != null) { return response; } // Some required elements were not successful and did not return response. // We can break as we know that the whole subflow would be considered unsuccessful as well - if (!requiredElementsSuccessful) { + if (!flowSuccessful) { break; } } @@ -264,12 +268,13 @@ public Response processFlow() { //Evaluate alternative elements only if there are no required elements. This may also occur if there was only condition elements if (requiredList.isEmpty()) { //check if an alternative is already successful, in case we are returning in the flow after an action - if (alternativeList.stream().anyMatch(alternative -> processor.isSuccessful(alternative) || isSetupRequired(alternative))) { + if (isLevelOfAuthenticationSatisfied() && alternativeList.stream().anyMatch(alternative -> processor.isSuccessful(alternative) || isSetupRequired(alternative))) { successful = true; return null; } - //handle alternative elements: the first alternative element to be satisfied is enough + //handle alternative elements: the first alternative element to be satisfied is enough if LOA is also satisfied + flowSuccessful = false; for (AuthenticationExecutionModel alternative : alternativeList) { try { Response response = processSingleFlowExecutionModel(alternative, true); @@ -277,8 +282,12 @@ public Response processFlow() { return response; } if (processor.isSuccessful(alternative) || isSetupRequired(alternative)) { - successful = true; - return null; + if (isLevelOfAuthenticationSatisfied()) { + successful = true; + return null; + } else { + flowSuccessful = true; + } } } catch (AuthenticationFlowException afe) { //consuming the error is not good here from an administrative point of view, but the user, since he has alternatives, should be able to go to another alternative and continue @@ -286,8 +295,12 @@ public Response processFlow() { processor.getAuthenticationSession().setExecutionStatus(alternative.getId(), AuthenticationSessionModel.ExecutionStatus.ATTEMPTED); } } + } + if (!flow.isTopLevel() || isLevelOfAuthenticationSatisfied() || !isLevelOfAuthenticationForced() || isFullAuthenticationRequested()) { + successful = flowSuccessful; } else { - successful = requiredElementsSuccessful; + throw new AuthenticationFlowException(AuthenticationFlowError.INVALID_CREDENTIALS, + ErrorPage.error(processor.getSession(), processor.getAuthenticationSession(), Response.Status.BAD_REQUEST, Messages.INSUFFICIENT_LEVEL_OF_AUTHENTICATION)); } return null; } @@ -321,9 +334,10 @@ void fillListsOfExecutions(List executionsToProces /** * Checks if the conditional subflow passed in parameter is disabled. * @param model + * @param storeResult whether to store the result of the conditional evaluations * @return */ - boolean isConditionalSubflowDisabled(AuthenticationExecutionModel model) { + boolean isConditionalSubflowDisabled(AuthenticationExecutionModel model, boolean storeResult) { if (model == null || !model.isAuthenticatorFlow() || !model.isConditional()) { return false; }; @@ -332,7 +346,7 @@ boolean isConditionalSubflowDisabled(AuthenticationExecutionModel model) { .filter(this::isConditionalAuthenticator) .filter(s -> s.isEnabled()) .collect(Collectors.toList()); - return conditionalAuthenticatorList.isEmpty() || conditionalAuthenticatorList.stream().anyMatch(m-> conditionalNotMatched(m, modelList)); + return conditionalAuthenticatorList.isEmpty() || conditionalAuthenticatorList.stream().anyMatch(m-> conditionalNotMatched(m, modelList, storeResult)); } private boolean isConditionalAuthenticator(AuthenticationExecutionModel model) { @@ -347,7 +361,7 @@ private AuthenticatorFactory getAuthenticatorFactory(AuthenticationExecutionMode return factory; } - private boolean conditionalNotMatched(AuthenticationExecutionModel model, List executionList) { + private boolean conditionalNotMatched(AuthenticationExecutionModel model, List executionList, boolean storeResult) { AuthenticatorFactory factory = getAuthenticatorFactory(model); ConditionalAuthenticator authenticator = (ConditionalAuthenticator) createAuthenticator(factory); AuthenticationProcessor.Result context = processor.createAuthenticatorContext(model, authenticator, executionList); @@ -361,8 +375,11 @@ private boolean conditionalNotMatched(AuthenticationExecutionModel model, List createAuthenticationSelectionList(Au return AuthenticationSelectionResolver.createAuthenticationSelectionList(processor, model); } + private boolean isFullAuthenticationRequested() { + return AuthenticatorUtil.getRequestedLevelOfAuthentication(processor.getAuthenticationSession()) < 0; + } + + private boolean isLevelOfAuthenticationSatisfied() { + return AuthenticatorUtil.isLevelOfAuthenticationSatisfied(processor.getAuthenticationSession()); + } + + private boolean isLevelOfAuthenticationForced() { + return AuthenticatorUtil.isLevelOfAuthenticationForced(processor.getAuthenticationSession()); + } public Response processResult(AuthenticationProcessor.Result result, boolean isAction) { AuthenticationExecutionModel execution = result.getExecution(); diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java index e0b2363de3bb..e6ba64a492dd 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java @@ -74,12 +74,11 @@ protected void authenticateImpl(AuthenticationFlowContext context, SerializedBro UserModel federatedUser = session.users().addUser(realm, username); federatedUser.setEnabled(true); - federatedUser.setEmail(brokerContext.getEmail()); - federatedUser.setFirstName(brokerContext.getFirstName()); - federatedUser.setLastName(brokerContext.getLastName()); for (Map.Entry> attr : serializedCtx.getAttributes().entrySet()) { - federatedUser.setAttribute(attr.getKey(), attr.getValue()); + if (!UserModel.USERNAME.equalsIgnoreCase(attr.getKey())) { + federatedUser.setAttribute(attr.getKey(), attr.getValue()); + } } AuthenticatorConfigModel config = context.getAuthenticatorConfig(); diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticator.java index e2e1ee1de44d..6574a972d934 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/CookieAuthenticator.java @@ -19,6 +19,8 @@ import org.keycloak.authentication.AuthenticationFlowContext; import org.keycloak.authentication.Authenticator; +import org.keycloak.authentication.AuthenticatorUtil; +import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; @@ -46,14 +48,15 @@ public void authenticate(AuthenticationFlowContext context) { } else { AuthenticationSessionModel clientSession = context.getAuthenticationSession(); LoginProtocol protocol = context.getSession().getProvider(LoginProtocol.class, clientSession.getProtocol()); + clientSession.setUserSessionNote(Constants.LEVEL_OF_AUTHENTICATION, authResult.getSession().getNote(Constants.LEVEL_OF_AUTHENTICATION)); + context.setUser(authResult.getUser()); // Cookie re-authentication is skipped if re-authentication is required - if (protocol.requireReauthentication(authResult.getSession(), clientSession)) { + if (protocol.requireReauthentication(authResult.getSession(), clientSession) + || !AuthenticatorUtil.isLevelOfAuthenticationSatisfied(clientSession)) { context.attempted(); } else { context.getSession().setAttribute(AuthenticationManager.SSO_AUTH, "true"); - - context.setUser(authResult.getUser()); context.attachUserSession(authResult.getSession()); context.success(); } diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/SetLoaAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/SetLoaAuthenticator.java new file mode 100644 index 000000000000..32437a9ca780 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/SetLoaAuthenticator.java @@ -0,0 +1,73 @@ +package org.keycloak.authentication.authenticators.browser; + +import org.jboss.logging.Logger; +import org.keycloak.authentication.AuthenticationFlowContext; +import org.keycloak.authentication.Authenticator; +import org.keycloak.authentication.AuthenticatorUtil; +import org.keycloak.models.Constants; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.sessions.AuthenticationSessionModel; + +public class SetLoaAuthenticator implements Authenticator { + + private static final Logger logger = Logger.getLogger(SetLoaAuthenticator.class); + + static final String LEVEL = "loa-level"; + static final String STORE_IN_USER_SESSION = "loa-store-in-user-session"; + + @Override + public void authenticate(AuthenticationFlowContext context) { + AuthenticationSessionModel authSession = context.getAuthenticationSession(); + int currentLoa = Math.max(getConfiguredLoa(context), AuthenticatorUtil.getCurrentLevelOfAuthentication(authSession)); + authSession.setAuthNote(Constants.LEVEL_OF_AUTHENTICATION, String.valueOf(currentLoa)); + if (isStoreInUserSession(context)) { + authSession.setUserSessionNote(Constants.LEVEL_OF_AUTHENTICATION, String.valueOf(currentLoa)); + } + // This authenticator only sets the LOA. + // It does not check if the requested LOA has been satisfied. + // That is done by the flow processor. + context.success(); + } + + @Override + public void action(AuthenticationFlowContext context) { + } + + @Override + public boolean requiresUser() { + return false; + } + + @Override + public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { + return true; + } + + @Override + public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { + } + + @Override + public void close() { + } + + private boolean isStoreInUserSession(AuthenticationFlowContext context) { + try { + return Boolean.parseBoolean(context.getAuthenticatorConfig().getConfig().get(STORE_IN_USER_SESSION)); + } catch (NullPointerException | NumberFormatException e) { + logger.errorv("Invalid configuration: {0}", STORE_IN_USER_SESSION); + return false; + } + } + + private int getConfiguredLoa(AuthenticationFlowContext context) { + try { + return Integer.parseInt(context.getAuthenticatorConfig().getConfig().get(LEVEL)); + } catch (NullPointerException | NumberFormatException e) { + logger.errorv("Invalid configuration: {0}", LEVEL); + return -1; + } + } +} diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/SetLoaAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/SetLoaAuthenticatorFactory.java new file mode 100644 index 000000000000..a322d2df63a9 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/SetLoaAuthenticatorFactory.java @@ -0,0 +1,93 @@ +package org.keycloak.authentication.authenticators.browser; + +import java.util.List; +import org.keycloak.Config; +import org.keycloak.authentication.Authenticator; +import org.keycloak.authentication.AuthenticatorFactory; +import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.provider.ProviderConfigurationBuilder; + +public class SetLoaAuthenticatorFactory implements AuthenticatorFactory { + + private static final String PROVIDER_ID = "set-level-of-authentication"; + private static final SetLoaAuthenticator SINGLETON = new SetLoaAuthenticator(); + private static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = new AuthenticationExecutionModel.Requirement[]{ + AuthenticationExecutionModel.Requirement.REQUIRED, AuthenticationExecutionModel.Requirement.DISABLED + }; + + private static final List CONFIG = ProviderConfigurationBuilder.create() + .property() + .name(SetLoaAuthenticator.LEVEL) + .label(SetLoaAuthenticator.LEVEL) + .helpText(SetLoaAuthenticator.LEVEL + ".tooltip") + .type(ProviderConfigProperty.STRING_TYPE) + .add() + .property() + .name(SetLoaAuthenticator.STORE_IN_USER_SESSION) + .label(SetLoaAuthenticator.STORE_IN_USER_SESSION) + .helpText(SetLoaAuthenticator.STORE_IN_USER_SESSION + ".tooltip") + .type(ProviderConfigProperty.BOOLEAN_TYPE) + .defaultValue("true") + .add() + .build(); + + @Override + public String getDisplayType() { + return "Set Level of Authentication"; + } + + @Override + public String getReferenceCategory() { + return "loa"; + } + + @Override + public boolean isConfigurable() { + return true; + } + + @Override + public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { + return REQUIREMENT_CHOICES; + } + + @Override + public boolean isUserSetupAllowed() { + return false; + } + + @Override + public String getHelpText() { + return "Set the Level of Authentication (LOA)."; + } + + @Override + public List getConfigProperties() { + return CONFIG; + } + + @Override + public Authenticator create(KeycloakSession session) { + return SINGLETON; + } + + @Override + public void init(Config.Scope config) { + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + } + + @Override + public void close() { + } + + @Override + public String getId() { + return PROVIDER_ID; + } +} diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/conditional/ConditionalLoaAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/conditional/ConditionalLoaAuthenticator.java new file mode 100644 index 000000000000..f5d3e655a8b0 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/authenticators/conditional/ConditionalLoaAuthenticator.java @@ -0,0 +1,48 @@ +package org.keycloak.authentication.authenticators.conditional; + +import org.jboss.logging.Logger; +import org.keycloak.authentication.AuthenticationFlowContext; +import org.keycloak.authentication.AuthenticatorUtil; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.sessions.AuthenticationSessionModel; + +public class ConditionalLoaAuthenticator implements ConditionalAuthenticator { + + static final String LEVEL = "loa-condition-level"; + + private static final Logger logger = Logger.getLogger(ConditionalLoaAuthenticator.class); + + @Override + public boolean matchCondition(AuthenticationFlowContext context) { + AuthenticationSessionModel authSession = context.getAuthenticationSession(); + int currentLoa = AuthenticatorUtil.getCurrentLevelOfAuthentication(authSession); + int requestedLoa = AuthenticatorUtil.getRequestedLevelOfAuthentication(authSession); + int configuredLoa = getConfiguredLoa(context); + return currentLoa < configuredLoa && (requestedLoa >= configuredLoa || requestedLoa < 0); + } + + private int getConfiguredLoa(AuthenticationFlowContext context) { + try { + return Integer.parseInt(context.getAuthenticatorConfig().getConfig().get(LEVEL)); + } catch (NullPointerException | NumberFormatException e) { + logger.errorv("Invalid configuration: {0}", LEVEL); + return Integer.MAX_VALUE; + } + } + + @Override + public void action(AuthenticationFlowContext context) { } + + @Override + public boolean requiresUser() { + return false; + } + + @Override + public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { } + + @Override + public void close() { } +} diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/conditional/ConditionalLoaAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/conditional/ConditionalLoaAuthenticatorFactory.java new file mode 100644 index 000000000000..fad198544e9d --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/authenticators/conditional/ConditionalLoaAuthenticatorFactory.java @@ -0,0 +1,81 @@ +package org.keycloak.authentication.authenticators.conditional; + +import java.util.List; +import org.keycloak.Config; +import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.provider.ProviderConfigurationBuilder; + +public class ConditionalLoaAuthenticatorFactory implements ConditionalAuthenticatorFactory { + + public static final String PROVIDER_ID = "conditional-level-of-authentication"; + private static final ConditionalLoaAuthenticator SINGLETON = new ConditionalLoaAuthenticator(); + private static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = new AuthenticationExecutionModel.Requirement[]{ + AuthenticationExecutionModel.Requirement.REQUIRED, + AuthenticationExecutionModel.Requirement.DISABLED + }; + + private static final List CONFIG = ProviderConfigurationBuilder.create() + .property() + .name(ConditionalLoaAuthenticator.LEVEL) + .label(ConditionalLoaAuthenticator.LEVEL) + .helpText(ConditionalLoaAuthenticator.LEVEL + ".tooltip") + .type(ProviderConfigProperty.STRING_TYPE) + .add() + .build(); + + @Override + public void init(Config.Scope config) { } + + @Override + public void postInit(KeycloakSessionFactory factory) { } + + @Override + public void close() { } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public String getDisplayType() { + return "Condition - Level of Authentication"; + } + + @Override + public String getReferenceCategory() { + return "condition"; + } + + @Override + public boolean isConfigurable() { + return true; + } + + @Override + public AuthenticationExecutionModel.Requirement[] getRequirementChoices() { + return REQUIREMENT_CHOICES; + } + + @Override + public boolean isUserSetupAllowed() { + return false; + } + + @Override + public String getHelpText() { + return "Flow is executed only if the configured LOA or a higher one has been requested but not yet satisfied."; + } + + @Override + public List getConfigProperties() { + return CONFIG; + } + + @Override + public ConditionalAuthenticator getSingleton() { + return SINGLETON; + } +} diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java index c46b88b22c1e..9d10e7fe4160 100755 --- a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java +++ b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java @@ -258,6 +258,7 @@ protected Response handleSamlRequest(String samlRequest, String relayState) { RequestAbstractType requestAbstractType = (RequestAbstractType) holder.getSamlObject(); // validate destination if (requestAbstractType.getDestination() == null && containsUnencryptedSignature(holder)) { + event.event(EventType.IDENTITY_PROVIDER_RESPONSE); event.detail(Details.REASON, "missing_required_destination"); event.error(Errors.INVALID_REQUEST); return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.INVALID_REQUEST); @@ -521,6 +522,7 @@ public Response handleSamlResponse(String samlResponse, String relayState, Strin StatusResponseType statusResponse = (StatusResponseType)holder.getSamlObject(); // validate destination if (statusResponse.getDestination() == null && containsUnencryptedSignature(holder)) { + event.event(EventType.IDENTITY_PROVIDER_RESPONSE); event.detail(Details.REASON, "missing_required_destination"); event.error(Errors.INVALID_SAML_LOGOUT_RESPONSE); return ErrorPage.error(session, null, Response.Status.BAD_REQUEST, Messages.INVALID_REQUEST); diff --git a/services/src/main/java/org/keycloak/headers/DefaultSecurityHeadersProvider.java b/services/src/main/java/org/keycloak/headers/DefaultSecurityHeadersProvider.java index 7664628a8d89..519170093566 100644 --- a/services/src/main/java/org/keycloak/headers/DefaultSecurityHeadersProvider.java +++ b/services/src/main/java/org/keycloak/headers/DefaultSecurityHeadersProvider.java @@ -18,6 +18,7 @@ import org.jboss.logging.Logger; import org.keycloak.models.BrowserSecurityHeaders; +import org.keycloak.models.ContentSecurityPolicyBuilder; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; @@ -26,8 +27,11 @@ import javax.ws.rs.container.ContainerResponseContext; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; +import java.util.Collections; import java.util.Map; +import static org.keycloak.models.BrowserSecurityHeaders.CONTENT_SECURITY_POLICY; + public class DefaultSecurityHeadersProvider implements SecurityHeadersProvider { private static final Logger LOGGER = Logger.getLogger(DefaultSecurityHeadersProvider.class); @@ -44,7 +48,7 @@ public DefaultSecurityHeadersProvider(KeycloakSession session) { if (realm != null) { headerValues = realm.getBrowserSecurityHeaders(); } else { - headerValues = BrowserSecurityHeaders.defaultHeaders; + headerValues = Collections.emptyMap(); } } @@ -81,27 +85,31 @@ public void addHeaders(ContainerRequestContext requestContext, ContainerResponse } private void addGenericHeaders(MultivaluedMap headers) { - addHeader(BrowserSecurityHeaders.STRICT_TRANSPORT_SECURITY_KEY, headers); - addHeader(BrowserSecurityHeaders.X_CONTENT_TYPE_OPTIONS_KEY, headers); - addHeader(BrowserSecurityHeaders.X_XSS_PROTECTION_KEY, headers); + addHeader(BrowserSecurityHeaders.STRICT_TRANSPORT_SECURITY, headers); + addHeader(BrowserSecurityHeaders.X_CONTENT_TYPE_OPTIONS, headers); + addHeader(BrowserSecurityHeaders.X_XSS_PROTECTION, headers); + addHeader(BrowserSecurityHeaders.REFERRER_POLICY, headers); } private void addRestHeaders(MultivaluedMap headers) { - addHeader(BrowserSecurityHeaders.STRICT_TRANSPORT_SECURITY_KEY, headers); - addHeader(BrowserSecurityHeaders.X_FRAME_OPTIONS_KEY, headers); - addHeader(BrowserSecurityHeaders.X_CONTENT_TYPE_OPTIONS_KEY, headers); - addHeader(BrowserSecurityHeaders.X_XSS_PROTECTION_KEY, headers); + addHeader(BrowserSecurityHeaders.STRICT_TRANSPORT_SECURITY, headers); + addHeader(BrowserSecurityHeaders.X_FRAME_OPTIONS, headers); + addHeader(BrowserSecurityHeaders.X_CONTENT_TYPE_OPTIONS, headers); + addHeader(BrowserSecurityHeaders.X_XSS_PROTECTION, headers); + addHeader(BrowserSecurityHeaders.REFERRER_POLICY, headers); } private void addHtmlHeaders(MultivaluedMap headers) { - BrowserSecurityHeaders.headerAttributeMap.keySet().forEach(k -> addHeader(k, headers)); + for (BrowserSecurityHeaders header : BrowserSecurityHeaders.values()) { + addHeader(header, headers); + } // TODO This will be refactored as part of introducing a more strict CSP header if (options != null) { - BrowserSecurityHeaders.ContentSecurityPolicyBuilder csp = BrowserSecurityHeaders.ContentSecurityPolicyBuilder.create(); + ContentSecurityPolicyBuilder csp = ContentSecurityPolicyBuilder.create(); if (options.isAllowAnyFrameAncestor()) { - headers.remove(BrowserSecurityHeaders.X_FRAME_OPTIONS); + headers.remove(BrowserSecurityHeaders.X_FRAME_OPTIONS.getHeaderName()); csp.frameAncestors(null); } @@ -111,17 +119,16 @@ private void addHtmlHeaders(MultivaluedMap headers) { csp.frameSrc(allowedFrameSrc); } - if (BrowserSecurityHeaders.CONTENT_SECURITY_POLICY_DEFAULT.equals(headers.getFirst(BrowserSecurityHeaders.CONTENT_SECURITY_POLICY))) { - headers.putSingle(BrowserSecurityHeaders.CONTENT_SECURITY_POLICY, csp.build()); + if (CONTENT_SECURITY_POLICY.getDefaultValue().equals(headers.getFirst(CONTENT_SECURITY_POLICY.getHeaderName()))) { + headers.putSingle(CONTENT_SECURITY_POLICY.getHeaderName(), csp.build()); } } } - private void addHeader(String key, MultivaluedMap headers) { - String header = BrowserSecurityHeaders.headerAttributeMap.get(key); - String value = headerValues.get(key); + private void addHeader(BrowserSecurityHeaders header, MultivaluedMap headers) { + String value = headerValues.getOrDefault(header.getKey(), header.getDefaultValue()); if (value != null && !value.isEmpty()) { - headers.putSingle(header, value); + headers.putSingle(header.getHeaderName(), value); } } 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 976a1422aaa6..b11a4cd4dea5 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java @@ -17,12 +17,19 @@ package org.keycloak.protocol.oidc; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ValueNode; +import java.io.IOException; +import java.util.Collections; +import java.util.List; import org.jboss.logging.Logger; import org.jboss.resteasy.spi.HttpRequest; import org.keycloak.OAuth2Constants; import org.keycloak.OAuthErrorException; import org.keycloak.TokenCategory; import org.keycloak.TokenVerifier; +import org.keycloak.authentication.AuthenticatorUtil; import org.keycloak.cluster.ClusterProvider; import org.keycloak.common.ClientConnection; import org.keycloak.common.VerificationException; @@ -39,6 +46,7 @@ import org.keycloak.models.ClientModel; import org.keycloak.models.ClientScopeModel; import org.keycloak.models.ClientSessionContext; +import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; @@ -54,9 +62,11 @@ import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper; import org.keycloak.protocol.oidc.mappers.OIDCIDTokenMapper; import org.keycloak.protocol.oidc.mappers.UserInfoTokenMapper; +import org.keycloak.protocol.oidc.utils.AcrUtils; import org.keycloak.protocol.oidc.utils.OIDCResponseType; import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessTokenResponse; +import org.keycloak.representations.ClaimsParameter; import org.keycloak.representations.IDToken; import org.keycloak.representations.JsonWebToken; import org.keycloak.representations.RefreshToken; @@ -68,6 +78,7 @@ import org.keycloak.services.util.DefaultClientSessionContext; import org.keycloak.services.util.MtlsHoKTokenUtil; import org.keycloak.sessions.AuthenticationSessionModel; +import org.keycloak.util.JsonSerialization; import org.keycloak.util.TokenUtil; import javax.ws.rs.core.HttpHeaders; @@ -77,7 +88,6 @@ import java.util.Collection; import java.util.HashSet; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -442,6 +452,7 @@ public static ClientSessionContext attachAuthenticationSession(KeycloakSession s userSession.setNote(entry.getKey(), entry.getValue()); } + clientSession.setNote(Constants.LEVEL_OF_AUTHENTICATION, String.valueOf(AuthenticatorUtil.getCurrentLevelOfAuthentication(authSession))); clientSession.setTimestamp(Time.currentTime()); // Remove authentication session now @@ -636,10 +647,7 @@ protected AccessToken initToken(RealmModel realm, ClientModel client, UserModel token.setNonce(clientSessionCtx.getAttribute(OIDCLoginProtocol.NONCE_PARAM, String.class)); token.setScope(clientSessionCtx.getScopeString()); - // Best effort for "acr" value. Use 0 if clientSession was authenticated through cookie ( SSO ) - // TODO: Add better acr support. See KEYCLOAK-3314 - String acr = (AuthenticationManager.isSSOAuthentication(clientSession)) ? "0" : "1"; - token.setAcr(acr); + token.setAcr(getAcr(clientSession)); String authTime = session.getNote(AuthenticationManager.AUTH_TIME); if (authTime != null) { @@ -656,6 +664,29 @@ protected AccessToken initToken(RealmModel realm, ClientModel client, UserModel return token; } + private String getAcr(AuthenticatedClientSessionModel clientSession) { + int loa = Integer.parseInt(clientSession.getNote(Constants.LEVEL_OF_AUTHENTICATION)); + if (loa == -1) { + loa = AuthenticationManager.isSSOAuthentication(clientSession) ? 0 : 1; + } + + Map acrLoaMap = AcrUtils.getAcrLoaMap(clientSession.getClient()); + String acr = AcrUtils.mapLoaToAcr(loa, acrLoaMap, AcrUtils.getRequiredAcrValues( + clientSession.getNote(OIDCLoginProtocol.CLAIMS_PARAM))); + if (acr == null) { + acr = AcrUtils.mapLoaToAcr(loa, acrLoaMap, AcrUtils.getAcrValues( + clientSession.getNote(OIDCLoginProtocol.CLAIMS_PARAM), + clientSession.getNote(OIDCLoginProtocol.ACR_PARAM))); + if (acr == null) { + acr = AcrUtils.mapLoaToAcr(loa, acrLoaMap, acrLoaMap.keySet()); + if (acr == null) { + acr = String.valueOf(loa); + } + } + } + return acr; + } + private int getTokenExpiration(RealmModel realm, ClientModel client, UserSessionModel userSession, AuthenticatedClientSessionModel clientSession, boolean offlineTokenRequested) { boolean implicitFlow = false; diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java index e67a5a992678..c0c6c1c006e3 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java @@ -17,6 +17,13 @@ package org.keycloak.protocol.oidc.endpoints; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ValueNode; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import org.jboss.logging.Logger; import org.keycloak.OAuth2Constants; import org.keycloak.OAuthErrorException; @@ -37,10 +44,13 @@ import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest; import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequestParserProcessor; +import org.keycloak.protocol.oidc.utils.AcrUtils; import org.keycloak.protocol.oidc.utils.OIDCRedirectUriBuilder; import org.keycloak.protocol.oidc.utils.OIDCResponseMode; import org.keycloak.protocol.oidc.utils.OIDCResponseType; import org.keycloak.protocol.oidc.utils.RedirectUtils; +import org.keycloak.representations.ClaimsParameter; +import org.keycloak.representations.IDToken; import org.keycloak.services.ErrorPageException; import org.keycloak.services.ServicesLogger; import org.keycloak.services.Urls; @@ -52,6 +62,7 @@ import org.keycloak.services.resources.LoginActionsService; import org.keycloak.services.util.CacheControlUtil; import org.keycloak.sessions.AuthenticationSessionModel; +import org.keycloak.util.JsonSerialization; import org.keycloak.util.TokenUtil; import javax.ws.rs.Consumes; @@ -466,6 +477,28 @@ private void updateAuthenticationSession() { if (request.getCodeChallenge() != null) authenticationSession.setClientNote(OIDCLoginProtocol.CODE_CHALLENGE_PARAM, request.getCodeChallenge()); if (request.getCodeChallengeMethod() != null) authenticationSession.setClientNote(OIDCLoginProtocol.CODE_CHALLENGE_METHOD_PARAM, request.getCodeChallengeMethod()); + Map acrLoaMap = AcrUtils.getAcrLoaMap(authenticationSession.getClient()); + try { + List acrValues = AcrUtils.getRequiredAcrValues(request.getClaims()); + if (acrValues.isEmpty()) { + acrValues = AcrUtils.getAcrValues(request.getClaims(), request.getAcr()); + } else { + authenticationSession.setClientNote(Constants.FORCE_LEVEL_OF_AUTHENTICATION, "true"); + } + authenticationSession.setClientNote(Constants.REQUESTED_LEVEL_OF_AUTHENTICATION, + String.valueOf(acrValues.stream().mapToInt(acr -> { + try { + Integer loa = acrLoaMap.get(acr); + return loa == null ? Integer.parseInt(acr) : loa; + } catch (NumberFormatException e) { + // this is an unknown acr, we assume it is very high + return Integer.MAX_VALUE; + } + }).min().orElse(-1))); + } catch (IllegalArgumentException e) { + throw new ErrorPageException(session, authenticationSession, Response.Status.BAD_REQUEST, Messages.INVALID_PARAMETER, OIDCLoginProtocol.CLAIMS_PARAM); + } + if (request.getAdditionalReqParams() != null) { for (String paramName : request.getAdditionalReqParams().keySet()) { authenticationSession.setClientNote(LOGIN_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX + paramName, request.getAdditionalReqParams().get(paramName)); @@ -473,7 +506,6 @@ private void updateAuthenticationSession() { } } - private Response buildAuthorizationCodeAuthorizationResponse() { this.event.event(EventType.LOGIN); authenticationSession.setAuthNote(Details.AUTH_TYPE, CODE_AUTH_TYPE); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/AcrUtils.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/AcrUtils.java new file mode 100644 index 000000000000..a698710a8fc6 --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/AcrUtils.java @@ -0,0 +1,100 @@ +package org.keycloak.protocol.oidc.utils; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ValueNode; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.jboss.logging.Logger; +import org.keycloak.models.ClientModel; +import org.keycloak.models.Constants; +import org.keycloak.representations.ClaimsParameter; +import org.keycloak.representations.IDToken; +import org.keycloak.util.JsonSerialization; + +public class AcrUtils { + + private static final Logger LOGGER = Logger.getLogger(AcrUtils.class); + + public static List getRequiredAcrValues(String claimsParam) { + if (claimsParam != null) { + ClaimsParameter claims; + try { + claims = JsonSerialization.readValue(claimsParam, ClaimsParameter.class); + } catch (IOException e) { + throw new IllegalArgumentException("Invalid claims parameter: " + claimsParam); + } + Map idTokenClaims = claims.getIdToken(); + if (idTokenClaims != null) { + ClaimsParameter.ClaimRequest acrClaim = idTokenClaims.get(IDToken.ACR); + if (acrClaim != null && acrClaim.isEssential()) { + List values = acrClaim.getValues(); + if (values != null) { + return values.stream().map(JsonNode::asText).collect(Collectors.toList()); + } + } + } + } + return Collections.emptyList(); + } + + public static List getAcrValues(String claimsParam, String acrValuesParam) { + List acrValues = new ArrayList<>(); + if (acrValuesParam != null) { + acrValues.addAll(Arrays.asList(acrValuesParam.split(" "))); + } + if (claimsParam != null) { + ClaimsParameter claims; + try { + claims = JsonSerialization.readValue(claimsParam, ClaimsParameter.class); + } catch (IOException e) { + throw new IllegalArgumentException("Invalid claims parameter: " + claimsParam); + } + Map idTokenClaims = claims.getIdToken(); + if (idTokenClaims != null) { + ClaimsParameter.ClaimRequest acrClaim = idTokenClaims.get(IDToken.ACR); + if (acrClaim != null) { + List values = acrClaim.getValues(); + if (values != null) { + acrValues.addAll(values.stream().map(JsonNode::asText).collect(Collectors.toList())); + } + } + } + } + return acrValues; + } + + public static Map getAcrLoaMap(ClientModel client) { + String map = client.getAttribute(Constants.ACR_LOA_MAP); + if (map == null || map.isEmpty()) { + return Collections.emptyMap(); + } + try { + return JsonSerialization.readValue(map, new TypeReference>() {}); + } catch (IOException e) { + LOGGER.warn("Invalid client configuration (ACR-LOA map)"); + return Collections.emptyMap(); + } + } + + public static String mapLoaToAcr(int loa, Map acrLoaMap, Collection acrValues) { + String acr = null; + if (!acrLoaMap.isEmpty() && !acrValues.isEmpty()) { + int maxLoa = 0; + for (String requestedAcr : acrValues) { + Integer mappedLoa = acrLoaMap.get(requestedAcr); + if (mappedLoa != null && mappedLoa > maxLoa && loa >= mappedLoa) { + acr = requestedAcr; + maxLoa = mappedLoa; + } + } + } + return acr; + } +} diff --git a/services/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java b/services/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java index 5a284468fcc5..b49d0a6e71dc 100755 --- a/services/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java +++ b/services/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java @@ -46,6 +46,7 @@ public Response createSaml(String descriptor) { ClientRepresentation client = session.getProvider(ClientDescriptionConverter.class, EntityDescriptorDescriptionConverter.ID).convertToInternal(descriptor); EntityDescriptorClientRegistrationContext context = new EntityDescriptorClientRegistrationContext(session, client, this); client = create(context); + validateClient(client, true); URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build(); return Response.created(uri).entity(client).build(); } diff --git a/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationContext.java b/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationContext.java index f6bea930fc9b..0232c41a28c1 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationContext.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationContext.java @@ -19,8 +19,6 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.services.validation.ClientValidator; -import org.keycloak.services.validation.ValidationMessages; /** * @author Marek Posolda @@ -52,8 +50,4 @@ public ClientRegistrationProvider getProvider() { return provider; } - @Override - public boolean validateClient(ValidationMessages validationMessages) { - return ClientValidator.validate(client, validationMessages); - } } diff --git a/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java index 927a470064cf..a473b0aa2521 100755 --- a/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java @@ -19,18 +19,22 @@ import org.keycloak.events.EventBuilder; import org.keycloak.events.EventType; -import org.keycloak.models.*; +import org.keycloak.models.ClientInitialAccessModel; +import org.keycloak.models.ClientModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ModelDuplicateException; +import org.keycloak.models.RealmModel; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.oidc.OIDCClientRepresentation; import org.keycloak.services.ErrorResponseException; import org.keycloak.services.ForbiddenException; import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyManager; import org.keycloak.services.clientregistration.policy.RegistrationAuth; import org.keycloak.services.managers.ClientManager; import org.keycloak.services.managers.RealmManager; -import org.keycloak.services.validation.ValidationMessages; -import org.keycloak.validation.ClientValidationUtil; +import org.keycloak.validation.ValidationUtil; import javax.ws.rs.core.Response; @@ -54,16 +58,6 @@ public ClientRepresentation create(ClientRegistrationContext context) { RegistrationAuth registrationAuth = auth.requireCreate(context); - ValidationMessages validationMessages = new ValidationMessages(); - if (!context.validateClient(validationMessages)) { - String errorCode = validationMessages.fieldHasError("redirectUris") ? ErrorCodes.INVALID_REDIRECT_URI : ErrorCodes.INVALID_CLIENT_METADATA; - throw new ErrorResponseException( - errorCode, - validationMessages.getStringMessages(), - Response.Status.BAD_REQUEST - ); - } - try { RealmModel realm = session.getContext().getRealm(); ClientModel clientModel = ClientManager.createClient(session, realm, client, true); @@ -82,11 +76,6 @@ public ClientRepresentation create(ClientRegistrationContext context) { client.setSecret(clientModel.getSecret()); - ClientValidationUtil.validate(session, clientModel, true, c -> { - session.getTransactionManager().setRollbackOnly(); - throw new ErrorResponseException(ErrorCodes.INVALID_CLIENT_METADATA, c.getError(), Response.Status.BAD_REQUEST); - }); - String registrationAccessToken = ClientRegistrationTokenUtils.updateRegistrationAccessToken(session, clientModel, registrationAuth); client.setRegistrationAccessToken(registrationAccessToken); @@ -133,24 +122,9 @@ public ClientRepresentation update(String clientId, ClientRegistrationContext co throw new ErrorResponseException(ErrorCodes.INVALID_CLIENT_METADATA, "Client Identifier modified", Response.Status.BAD_REQUEST); } - ValidationMessages validationMessages = new ValidationMessages(); - if (!context.validateClient(validationMessages)) { - String errorCode = validationMessages.fieldHasError("redirectUris") ? ErrorCodes.INVALID_REDIRECT_URI : ErrorCodes.INVALID_CLIENT_METADATA; - throw new ErrorResponseException( - errorCode, - validationMessages.getStringMessages(), - Response.Status.BAD_REQUEST - ); - } - RepresentationToModel.updateClient(rep, client); RepresentationToModel.updateClientProtocolMappers(rep, client); - ClientValidationUtil.validate(session, client, false, c -> { - session.getTransactionManager().setRollbackOnly(); - throw new ErrorResponseException(ErrorCodes.INVALID_CLIENT_METADATA, c.getError(), Response.Status.BAD_REQUEST); - }); - rep = ModelToRepresentation.toRepresentation(client, session); if (auth.isRegistrationAccessToken()) { @@ -178,6 +152,18 @@ public void delete(String clientId) { } } + public void validateClient(ClientModel clientModel, OIDCClientRepresentation oidcClient, boolean create) { + ValidationUtil.validateClient(session, clientModel, oidcClient, create, r -> { + session.getTransactionManager().setRollbackOnly(); + String errorCode = r.fieldHasError("redirectUris") ? ErrorCodes.INVALID_REDIRECT_URI : ErrorCodes.INVALID_CLIENT_METADATA; + throw new ErrorResponseException(errorCode, r.getAllErrorsAsString(), Response.Status.BAD_REQUEST); + }); + } + + public void validateClient(ClientRepresentation clientRep, boolean create) { + validateClient(session.getContext().getRealm().getClientByClientId(clientRep.getClientId()), null, create); + } + @Override public void setAuth(ClientRegistrationAuth auth) { this.auth = auth; diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationContext.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationContext.java index 0ccdb7b80cb0..d67079df93ad 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationContext.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationContext.java @@ -19,7 +19,6 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.services.validation.ValidationMessages; /** * @author Marek Posolda @@ -32,6 +31,4 @@ public interface ClientRegistrationContext { ClientRegistrationProvider getProvider(); - boolean validateClient(ValidationMessages validationMessages); - } diff --git a/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationContext.java b/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationContext.java index 08aeaef702c0..685ed864167e 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationContext.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationContext.java @@ -19,8 +19,6 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.services.validation.PairwiseClientValidator; -import org.keycloak.services.validation.ValidationMessages; /** * @author Marek Posolda @@ -31,8 +29,4 @@ public DefaultClientRegistrationContext(KeycloakSession session, ClientRepresent super(session, client, provider); } - @Override - public boolean validateClient(ValidationMessages validationMessages) { - return super.validateClient(validationMessages) && PairwiseClientValidator.validate(session, client, validationMessages); - } } diff --git a/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java index de2a61516868..a0b484647ab7 100755 --- a/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java @@ -48,6 +48,7 @@ public DefaultClientRegistrationProvider(KeycloakSession session) { public Response createDefault(ClientRepresentation client) { DefaultClientRegistrationContext context = new DefaultClientRegistrationContext(session, client, this); client = create(context); + validateClient(client, true); URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build(); return Response.created(uri).entity(client).build(); } @@ -68,6 +69,7 @@ public Response getDefault(@PathParam("clientId") String clientId) { public Response updateDefault(@PathParam("clientId") String clientId, ClientRepresentation client) { DefaultClientRegistrationContext context = new DefaultClientRegistrationContext(session, client, this); client = update(clientId, context); + validateClient(client, false); return Response.ok(client).build(); } diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationContext.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationContext.java index 9b9c5b414e0d..036de311662e 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationContext.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationContext.java @@ -17,18 +17,11 @@ package org.keycloak.services.clientregistration.oidc; -import java.util.HashSet; -import java.util.Set; - import org.keycloak.models.KeycloakSession; -import org.keycloak.protocol.oidc.utils.SubjectType; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.oidc.OIDCClientRepresentation; import org.keycloak.services.clientregistration.AbstractClientRegistrationContext; import org.keycloak.services.clientregistration.ClientRegistrationProvider; -import org.keycloak.services.clientregistration.DefaultClientRegistrationContext; -import org.keycloak.services.validation.PairwiseClientValidator; -import org.keycloak.services.validation.ValidationMessages; /** * @author Marek Posolda @@ -42,21 +35,4 @@ public OIDCClientRegistrationContext(KeycloakSession session, ClientRepresentati this.oidcRep = oidcRep; } - @Override - public boolean validateClient(ValidationMessages validationMessages) { - boolean valid = super.validateClient(validationMessages); - - String rootUrl = client.getRootUrl(); - Set redirectUris = new HashSet<>(); - if (client.getRedirectUris() != null) redirectUris.addAll(client.getRedirectUris()); - - SubjectType subjectType = SubjectType.parse(oidcRep.getSubjectType()); - String sectorIdentifierUri = oidcRep.getSectorIdentifierUri(); - - // If sector_identifier_uri is in oidc config, then always validate it - if (SubjectType.PAIRWISE == subjectType || (sectorIdentifierUri != null && !sectorIdentifierUri.isEmpty())) { - valid = valid && PairwiseClientValidator.validate(session, rootUrl, redirectUris, oidcRep.getSectorIdentifierUri(), validationMessages); - } - return valid; - } } diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java index d2a03a16a4aa..6601aa2ae417 100755 --- a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java @@ -86,6 +86,8 @@ public Response createOIDC(OIDCClientRepresentation clientOIDC) { updatePairwiseSubMappers(clientModel, SubjectType.parse(clientOIDC.getSubjectType()), clientOIDC.getSectorIdentifierUri()); updateClientRepWithProtocolMappers(clientModel, client); + validateClient(clientModel, clientOIDC, true); + URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build(); clientOIDC = DescriptionConverter.toExternalResponse(session, client, uri); clientOIDC.setClientIdIssuedAt(Time.currentTime()); @@ -122,6 +124,8 @@ public Response updateOIDC(@PathParam("clientId") String clientId, OIDCClientRep updatePairwiseSubMappers(clientModel, SubjectType.parse(clientOIDC.getSubjectType()), clientOIDC.getSectorIdentifierUri()); updateClientRepWithProtocolMappers(clientModel, client); + validateClient(clientModel, clientOIDC, false); + URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build(); clientOIDC = DescriptionConverter.toExternalResponse(session, client, uri); return Response.ok(clientOIDC).build(); diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java index 961716adb418..495debaf79cf 100755 --- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java +++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java @@ -97,7 +97,12 @@ public RealmModel createRealm(String name) { } public RealmModel createRealm(String id, String name) { - if (id == null) id = KeycloakModelUtils.generateId(); + if (id == null) { + id = KeycloakModelUtils.generateId(); + } + else { + ReservedCharValidator.validate(id); + } ReservedCharValidator.validate(name); RealmModel realm = model.createRealm(id, name); realm.setName(name); @@ -227,7 +232,7 @@ public String getRealmAdminClientId(RealmRepresentation realm) { protected void setupRealmDefaults(RealmModel realm) { - realm.setBrowserSecurityHeaders(BrowserSecurityHeaders.defaultHeaders); + realm.setBrowserSecurityHeaders(BrowserSecurityHeaders.realmDefaultHeaders); // brute force realm.setBruteForceProtected(false); // default settings off for now todo set it on @@ -505,6 +510,9 @@ public RealmModel importRealm(RealmRepresentation rep, boolean skipUserDependent if (id == null) { id = KeycloakModelUtils.generateId(); } + else { + ReservedCharValidator.validate(id); + } RealmModel realm = model.createRealm(id, rep.getRealm()); ReservedCharValidator.validate(rep.getRealm()); realm.setName(rep.getRealm()); diff --git a/services/src/main/java/org/keycloak/services/messages/Messages.java b/services/src/main/java/org/keycloak/services/messages/Messages.java index 1816c0ae5675..585302a0262e 100755 --- a/services/src/main/java/org/keycloak/services/messages/Messages.java +++ b/services/src/main/java/org/keycloak/services/messages/Messages.java @@ -221,6 +221,8 @@ public class Messages { public static final String IDENTITY_PROVIDER_LOGIN_FAILURE = "identityProviderLoginFailure"; + public static final String INSUFFICIENT_LEVEL_OF_AUTHENTICATION = "insufficientLevelOfAuthentication"; + public static final String FAILED_LOGOUT = "failedLogout"; public static final String CONSENT_DENIED="consentDenied"; diff --git a/services/src/main/java/org/keycloak/services/resources/Cors.java b/services/src/main/java/org/keycloak/services/resources/Cors.java index 33c90e79f955..dd5431a8c568 100755 --- a/services/src/main/java/org/keycloak/services/resources/Cors.java +++ b/services/src/main/java/org/keycloak/services/resources/Cors.java @@ -137,7 +137,7 @@ public Cors exposedHeaders(String... exposedHeaders) { public Response build() { String origin = request.getHttpHeaders().getRequestHeaders().getFirst(ORIGIN_HEADER); - if (origin == null) { + if (origin == null || origin.equals("null")) { logger.trace("No origin header ignoring"); return builder.build(); } @@ -184,7 +184,7 @@ public Response build() { public void build(HttpResponse response) { String origin = request.getHttpHeaders().getRequestHeaders().getFirst(ORIGIN_HEADER); - if (origin == null) { + if (origin == null || origin.equals("null")) { logger.trace("No origin header ignoring"); return; } diff --git a/services/src/main/java/org/keycloak/services/resources/account/AccountFormService.java b/services/src/main/java/org/keycloak/services/resources/account/AccountFormService.java index a2f7932a4007..00d59006edd7 100755 --- a/services/src/main/java/org/keycloak/services/resources/account/AccountFormService.java +++ b/services/src/main/java/org/keycloak/services/resources/account/AccountFormService.java @@ -157,7 +157,7 @@ public void init() { String requestOrigin = UriUtils.getOrigin(session.getContext().getUri().getBaseUri()); String origin = headers.getRequestHeaders().getFirst("Origin"); - if (origin != null && !requestOrigin.equals(origin)) { + if (origin != null && !origin.equals("null") && !requestOrigin.equals(origin)) { throw new ForbiddenException(); } diff --git a/services/src/main/java/org/keycloak/services/resources/account/resources/ResourceService.java b/services/src/main/java/org/keycloak/services/resources/account/resources/ResourceService.java index 7541137fb4d0..71cf81a46ddc 100644 --- a/services/src/main/java/org/keycloak/services/resources/account/resources/ResourceService.java +++ b/services/src/main/java/org/keycloak/services/resources/account/resources/ResourceService.java @@ -36,6 +36,7 @@ import org.jboss.resteasy.spi.HttpRequest; import org.keycloak.authorization.model.PermissionTicket; import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.models.AccountRoles; import org.keycloak.models.KeycloakSession; import org.keycloak.models.UserModel; import org.keycloak.models.UserProvider; @@ -118,6 +119,8 @@ public Response user(@QueryParam("value") String value) { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response revoke(List permissions) { + auth.require(AccountRoles.MANAGE_ACCOUNT); + if (permissions == null || permissions.isEmpty()) { throw new BadRequestException("invalid_permissions"); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java index f59611e5019b..567ce955af9c 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java @@ -22,7 +22,6 @@ import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.authorization.admin.AuthorizationService; import org.keycloak.common.ClientConnection; -import org.keycloak.common.Profile; import org.keycloak.common.util.Time; import org.keycloak.events.Errors; import org.keycloak.events.admin.OperationType; @@ -63,11 +62,8 @@ import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement; import org.keycloak.services.resources.admin.permissions.AdminPermissions; -import org.keycloak.services.validation.ClientValidator; -import org.keycloak.services.validation.PairwiseClientValidator; -import org.keycloak.services.validation.ValidationMessages; -import org.keycloak.utils.ProfileHelper; -import org.keycloak.validation.ClientValidationUtil; +import org.keycloak.utils.ReservedCharValidator; +import org.keycloak.validation.ValidationUtil; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -87,10 +83,8 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Properties; import static java.lang.Boolean.TRUE; -import org.keycloak.utils.ReservedCharValidator; /** @@ -138,16 +132,6 @@ public ProtocolMappersResource getProtocolMappers() { public Response update(final ClientRepresentation rep) { auth.clients().requireConfigure(client); - ValidationMessages validationMessages = new ValidationMessages(); - if (!ClientValidator.validate(rep, validationMessages) || !PairwiseClientValidator.validate(session, rep, validationMessages)) { - Properties messages = AdminRoot.getMessages(session, realm, auth.adminAuth().getToken().getLocale()); - throw new ErrorResponseException( - validationMessages.getStringMessages(), - validationMessages.getStringMessages(messages), - Response.Status.BAD_REQUEST - ); - } - try { session.clientPolicy().triggerOnEvent(new AdminClientUpdateContext(rep, auth.adminAuth(), client)); } catch (ClientPolicyException cpe) { @@ -157,9 +141,12 @@ public Response update(final ClientRepresentation rep) { try { updateClientFromRep(rep, client, session); - ClientValidationUtil.validate(session, client, false, c -> { + ValidationUtil.validateClient(session, client, false, r -> { session.getTransactionManager().setRollbackOnly(); - throw new ErrorResponseException(Errors.INVALID_INPUT ,c.getError(), Response.Status.BAD_REQUEST); + throw new ErrorResponseException( + Errors.INVALID_INPUT, + r.getAllLocalizedErrorsAsString(AdminRoot.getMessages(session, realm, auth.adminAuth().getToken().getLocale())), + Response.Status.BAD_REQUEST); }); adminEvent.operation(OperationType.UPDATE).resourcePath(session.getContext().getUri()).representation(rep).success(); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java index f120b412d86e..766b2c994da5 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java @@ -40,10 +40,7 @@ import org.keycloak.services.managers.ClientManager; import org.keycloak.services.managers.RealmManager; import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; -import org.keycloak.services.validation.ClientValidator; -import org.keycloak.services.validation.PairwiseClientValidator; -import org.keycloak.services.validation.ValidationMessages; -import org.keycloak.validation.ClientValidationUtil; +import org.keycloak.validation.ValidationUtil; import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; @@ -60,7 +57,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Properties; import static java.lang.Boolean.TRUE; @@ -179,16 +175,6 @@ private AuthorizationService getAuthorizationService(ClientModel clientModel) { public Response createClient(final ClientRepresentation rep) { auth.clients().requireManage(); - ValidationMessages validationMessages = new ValidationMessages(); - if (!ClientValidator.validate(rep, validationMessages) || !PairwiseClientValidator.validate(session, rep, validationMessages)) { - Properties messages = AdminRoot.getMessages(session, realm, auth.adminAuth().getToken().getLocale()); - throw new ErrorResponseException( - validationMessages.getStringMessages(), - validationMessages.getStringMessages(messages), - Response.Status.BAD_REQUEST - ); - } - try { session.clientPolicy().triggerOnEvent(new AdminClientRegisterContext(rep, auth.adminAuth())); } catch (ClientPolicyException cpe) { @@ -220,9 +206,12 @@ public Response createClient(final ClientRepresentation rep) { } } - ClientValidationUtil.validate(session, clientModel, true, c -> { + ValidationUtil.validateClient(session, clientModel, true, r -> { session.getTransactionManager().setRollbackOnly(); - throw new ErrorResponseException(Errors.INVALID_INPUT, c.getError(), Response.Status.BAD_REQUEST); + throw new ErrorResponseException( + Errors.INVALID_INPUT, + r.getAllLocalizedErrorsAsString(AdminRoot.getMessages(session, realm, auth.adminAuth().getToken().getLocale())), + Response.Status.BAD_REQUEST); }); return Response.created(session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build()).build(); diff --git a/services/src/main/java/org/keycloak/services/validation/ClientValidator.java b/services/src/main/java/org/keycloak/services/validation/ClientValidator.java deleted file mode 100644 index 22290c5a12db..000000000000 --- a/services/src/main/java/org/keycloak/services/validation/ClientValidator.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * - * * 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.services.validation; - -import org.keycloak.representations.idm.ClientRepresentation; - -/** - * @author Vaclav Muzikar - */ -public class ClientValidator { - /** - * Checks if the Client's Redirect URIs doesn't contain any URI fragments (like http://example.org/auth#fragment) - * - * @see KEYCLOAK-3421 - * @param client - * @param messages - * @return true if Redirect URIs doesn't contain any URI with fragments - */ - public static boolean validate(ClientRepresentation client, ValidationMessages messages) { - boolean isValid = true; - - if (client.getRedirectUris() != null) { - long urisWithFragmentCount = client.getRedirectUris().stream().filter(p -> p.contains("#")).count(); - if (urisWithFragmentCount > 0) { - messages.add("redirectUris", "Redirect URIs must not contain an URI fragment", "clientRedirectURIsFragmentError"); - isValid = false; - } - } - - if (client.getRootUrl() != null && client.getRootUrl().contains("#")) { - messages.add("rootUrl", "Root URL must not contain an URL fragment", "clientRootURLFragmentError"); - isValid = false; - } - - return isValid; - } -} diff --git a/services/src/main/java/org/keycloak/services/validation/PairwiseClientValidator.java b/services/src/main/java/org/keycloak/services/validation/PairwiseClientValidator.java deleted file mode 100644 index f502fae661d3..000000000000 --- a/services/src/main/java/org/keycloak/services/validation/PairwiseClientValidator.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.keycloak.services.validation; - -import org.keycloak.models.KeycloakSession; -import org.keycloak.protocol.ProtocolMapperConfigException; -import org.keycloak.protocol.oidc.mappers.PairwiseSubMapperHelper; -import org.keycloak.protocol.oidc.utils.PairwiseSubMapperUtils; -import org.keycloak.protocol.oidc.utils.PairwiseSubMapperValidator; -import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.representations.idm.ProtocolMapperRepresentation; - -import java.util.HashSet; -import java.util.List; -import java.util.Set; - - -/** - * @author Martin Hardselius - */ -public class PairwiseClientValidator { - - public static boolean validate(KeycloakSession session, ClientRepresentation client, ValidationMessages messages) { - String rootUrl = client.getRootUrl(); - Set redirectUris = new HashSet<>(); - boolean valid = true; - - List foundPairwiseMappers = PairwiseSubMapperUtils.getPairwiseSubMappers(client); - - for (ProtocolMapperRepresentation foundPairwise : foundPairwiseMappers) { - String sectorIdentifierUri = PairwiseSubMapperHelper.getSectorIdentifierUri(foundPairwise); - if (client.getRedirectUris() != null) redirectUris.addAll(client.getRedirectUris()); - valid = valid && validate(session, rootUrl, redirectUris, sectorIdentifierUri, messages); - } - - return true; - } - - public static boolean validate(KeycloakSession session, String rootUrl, Set redirectUris, String sectorIdentifierUri, ValidationMessages messages) { - try { - PairwiseSubMapperValidator.validate(session, rootUrl, redirectUris, sectorIdentifierUri); - } catch (ProtocolMapperConfigException e) { - messages.add(e.getMessage(), e.getMessageKey()); - return false; - } - return true; - } - -} diff --git a/services/src/main/java/org/keycloak/services/validation/ValidationMessage.java b/services/src/main/java/org/keycloak/services/validation/ValidationMessage.java deleted file mode 100644 index 7e4dac12a5b7..000000000000 --- a/services/src/main/java/org/keycloak/services/validation/ValidationMessage.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * - * * 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.services.validation; - -import java.text.MessageFormat; -import java.util.Properties; - -/** - * @author Vaclav Muzikar - */ -public class ValidationMessage { - private String fieldId; - private String message; - private String localizedMessageKey; - private Object[] localizedMessageParameters; - - public ValidationMessage(String message) { - this.message = message; - } - - public ValidationMessage(String message, String localizedMessageKey, Object... localizedMessageParameters) { - this.message = message; - this.localizedMessageKey = localizedMessageKey; - this.localizedMessageParameters = localizedMessageParameters; - } - - public String getFieldId() { - return fieldId; - } - - public void setFieldId(String fieldId) { - this.fieldId = fieldId; - } - - public String getLocalizedMessageKey() { - return localizedMessageKey; - } - - public void setLocalizedMessageKey(String localizedMessageKey) { - this.localizedMessageKey = localizedMessageKey; - } - - public Object[] getLocalizedMessageParameters() { - return localizedMessageParameters; - } - - public void setLocalizedMessageParameters(Object[] localizedMessageParameters) { - this.localizedMessageParameters = localizedMessageParameters; - } - - public String getMessage() { - return message; - } - - public String getMessage(Properties localizedMessages) { - if (getLocalizedMessageKey() != null) { - return MessageFormat.format(localizedMessages.getProperty(getLocalizedMessageKey(), getMessage()), getLocalizedMessageParameters()); - } - else { - return getMessage(); - } - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - ValidationMessage message1 = (ValidationMessage) o; - - if (getFieldId() != null ? !getFieldId().equals(message1.getFieldId()) : message1.getFieldId() != null) - return false; - return getMessage() != null ? getMessage().equals(message1.getMessage()) : message1.getMessage() == null; - - } - - @Override - public int hashCode() { - int result = getFieldId() != null ? getFieldId().hashCode() : 0; - result = 31 * result + (getMessage() != null ? getMessage().hashCode() : 0); - return result; - } -} diff --git a/services/src/main/java/org/keycloak/services/validation/ValidationMessages.java b/services/src/main/java/org/keycloak/services/validation/ValidationMessages.java deleted file mode 100644 index 82db3742c2ef..000000000000 --- a/services/src/main/java/org/keycloak/services/validation/ValidationMessages.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * - * * 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.services.validation; - -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Properties; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; - - -/** - * @author Vaclav Muzikar - */ -public class ValidationMessages { - private Set messages = new LinkedHashSet<>(); - - public ValidationMessages() {} - - public ValidationMessages(String... messages) { - for (String message : messages) { - add(message); - } - } - - public void add(String message) { - messages.add(new ValidationMessage(message)); - } - - public void add(String message, String localizedMessageKey) { - messages.add(new ValidationMessage(message, localizedMessageKey)); - } - - public void add(String fieldId, String message, String localizedMessageKey) { - ValidationMessage validationMessage = new ValidationMessage(message, localizedMessageKey); - validationMessage.setFieldId(fieldId); - add(validationMessage); - } - - public void add(ValidationMessage message) { - messages.add(message); - } - - public boolean fieldHasError(String fieldId) { - if (fieldId == null) { - return false; - } - for (ValidationMessage message : messages) { - if (fieldId.equals(message.getFieldId())) { - return true; - } - } - return false; - } - - public Set getMessages() { - return Collections.unmodifiableSet(messages); - } - - protected String getStringMessages(Function function) { - return messages.stream().map(function).collect(Collectors.joining("; ")); - } - - public String getStringMessages() { - return getStringMessages(ValidationMessage::getMessage); - } - - public String getStringMessages(Properties localizedMessages) { - return getStringMessages(x -> x.getMessage(localizedMessages)); - } -} diff --git a/services/src/main/java/org/keycloak/theme/FolderTheme.java b/services/src/main/java/org/keycloak/theme/FolderTheme.java index f15bddbf332b..28a1c1120127 100644 --- a/services/src/main/java/org/keycloak/theme/FolderTheme.java +++ b/services/src/main/java/org/keycloak/theme/FolderTheme.java @@ -93,7 +93,7 @@ public InputStream getResourceAsStream(String path) throws IOException { } File file = new File(resourcesDir, path); - if (!file.isFile() || !file.getCanonicalPath().startsWith(resourcesDir.getCanonicalPath())) { + if (!file.isFile() || !file.getCanonicalPath().startsWith(resourcesDir.getCanonicalPath() + File.separator)) { return null; } else { return file.toURI().toURL().openStream(); diff --git a/services/src/main/java/org/keycloak/theme/FolderThemeProvider.java b/services/src/main/java/org/keycloak/theme/FolderThemeProvider.java index 5c75f56f2be0..1ea3123af8a3 100755 --- a/services/src/main/java/org/keycloak/theme/FolderThemeProvider.java +++ b/services/src/main/java/org/keycloak/theme/FolderThemeProvider.java @@ -84,7 +84,15 @@ public void close() { } private File getThemeDir(String name, Theme.Type type) { - return new File(themesDir, name + File.separator + type.name().toLowerCase()); + File f = new File(themesDir, name + File.separator + type.name().toLowerCase()); + try { + if (!f.getCanonicalPath().startsWith(themesDir.getCanonicalPath() + File.separator)) { + return null; + } + } catch (IOException e) { + return null; + } + return f; } } diff --git a/services/src/main/java/org/keycloak/validation/DefaultClientValidationProvider.java b/services/src/main/java/org/keycloak/validation/DefaultClientValidationProvider.java index 554c7a62cebf..12628df3c4f8 100644 --- a/services/src/main/java/org/keycloak/validation/DefaultClientValidationProvider.java +++ b/services/src/main/java/org/keycloak/validation/DefaultClientValidationProvider.java @@ -17,72 +17,189 @@ package org.keycloak.validation; import org.keycloak.models.ClientModel; +import org.keycloak.protocol.ProtocolMapperConfigException; +import org.keycloak.protocol.oidc.mappers.PairwiseSubMapperHelper; +import org.keycloak.protocol.oidc.utils.PairwiseSubMapperUtils; +import org.keycloak.protocol.oidc.utils.PairwiseSubMapperValidator; +import org.keycloak.protocol.oidc.utils.SubjectType; +import org.keycloak.representations.idm.ProtocolMapperRepresentation; +import org.keycloak.representations.oidc.OIDCClientRepresentation; import org.keycloak.services.util.ResolveRelative; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; -import java.net.URL; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation; public class DefaultClientValidationProvider implements ClientValidationProvider { + private enum FieldMessages { + ROOT_URL("rootUrl", + "Root URL is not a valid URL", "clientRootURLInvalid", + "Root URL must not contain an URL fragment", "clientRootURLFragmentError", + "Root URL uses an illegal scheme", "clientRootURLIllegalSchemeError"), + + BASE_URL("baseUrl", + "Base URL is not a valid URL", "clientBaseURLInvalid", + null, null, + "Base URL uses an illegal scheme", "clientBaseURLIllegalSchemeError"), + + REDIRECT_URIS("redirectUris", + "A redirect URI is not a valid URI", "clientRedirectURIsInvalid", + "Redirect URIs must not contain an URI fragment", "clientRedirectURIsFragmentError", + "A redirect URI uses an illegal scheme", "clientRedirectURIsIllegalSchemeError"); + + private String fieldId; + + private String invalid; + private String invalidKey; + + private String fragment; + private String fragmentKey; + + private String scheme; + private String schemeKey; + + FieldMessages(String fieldId, String invalid, String invalidKey, String fragment, String fragmentKey, String scheme, String schemeKey) { + this.fieldId = fieldId; + this.invalid = invalid; + this.invalidKey = invalidKey; + this.fragment = fragment; + this.fragmentKey = fragmentKey; + this.scheme = scheme; + this.schemeKey = schemeKey; + } + + public String getFieldId() { + return fieldId; + } + + public String getInvalid() { + return invalid; + } - private ClientValidationContext context; + public String getInvalidKey() { + return invalidKey; + } + + public String getFragment() { + return fragment; + } + + public String getFragmentKey() { + return fragmentKey; + } + + public String getScheme() { + return scheme; + } + + public String getSchemeKey() { + return schemeKey; + } + } // TODO Before adding more validation consider using a library for validation @Override - public void validate(ClientValidationContext context) { - this.context = context; + public ValidationResult validate(ValidationContext context) { + validateUrls(context); + validatePairwiseInClientModel(context); - try { - validate(context.getClient()); - } catch (ValidationException e) { - context.invalid(e.getMessage()); - } + return context.toResult(); } - private void validate(ClientModel client) throws ValidationException { + @Override + public ValidationResult validate(ClientValidationContext.OIDCContext context) { + validateUrls(context); + validatePairwiseInOIDCClient(context); + + return context.toResult(); + } + + private void validateUrls(ValidationContext context) { + ClientModel client = context.getObjectToValidate(); + // Use a fake URL for validating relative URLs as we may not be validating clients in the context of a request (import at startup) String authServerUrl = "https://localhost/auth"; - String resolvedRootUrl = ResolveRelative.resolveRootUrl(authServerUrl, authServerUrl, client.getRootUrl()); - String resolvedBaseUrl = ResolveRelative.resolveRelativeUri(authServerUrl, authServerUrl, resolvedRootUrl, client.getBaseUrl()); + String rootUrl = ResolveRelative.resolveRootUrl(authServerUrl, authServerUrl, client.getRootUrl()); - validateRootUrl(resolvedRootUrl); - validateBaseUrl(resolvedBaseUrl); - } + // don't need to use actual rootUrl here as it'd interfere with others URL validations + String baseUrl = ResolveRelative.resolveRelativeUri(authServerUrl, authServerUrl, authServerUrl, client.getBaseUrl()); - private void validateRootUrl(String rootUrl) throws ValidationException { - if (rootUrl != null && !rootUrl.isEmpty()) { - basicHttpUrlCheck("rootUrl", rootUrl); - } + checkUri(FieldMessages.ROOT_URL, rootUrl, context, true, true); + checkUri(FieldMessages.BASE_URL, baseUrl, context, true, false); + client.getRedirectUris().stream() + .map(u -> ResolveRelative.resolveRelativeUri(authServerUrl, authServerUrl, rootUrl, u)) + .forEach(u -> checkUri(FieldMessages.REDIRECT_URIS, u, context, false, true)); } - private void validateBaseUrl(String baseUrl) throws ValidationException { - if (baseUrl != null && !baseUrl.isEmpty()) { - basicHttpUrlCheck("baseUrl", baseUrl); + private void checkUri(FieldMessages field, String url, ValidationContext context, boolean checkValidUrl, boolean checkFragment) { + if (url == null || url.isEmpty()) { + return; } - } - private void basicHttpUrlCheck(String field, String url) throws ValidationException { - boolean valid = true; try { - URI uri = new URL(url).toURI(); - if (uri.getScheme() == null || uri.getScheme().isEmpty()) { + URI uri = new URI(url); + + boolean valid = true; + if (uri.getScheme() != null && (uri.getScheme().equals("data") || uri.getScheme().equals("javascript"))) { + context.addError(field.getFieldId(), field.getScheme(), field.getSchemeKey()); valid = false; } - } catch (MalformedURLException | URISyntaxException e) { - valid = false; + + // KEYCLOAK-3421 + if (checkFragment && uri.getFragment() != null) { + context.addError(field.getFieldId(), field.getFragment(), field.getFragmentKey()); + valid = false; + } + + // Don't check if URL is valid if there are other problems with it; otherwise it could lead to duplicit errors. + // This cannot be moved higher because it acts on differently based on environment (e.g. sometimes it checks + // scheme, sometimes it doesn't). + if (checkValidUrl && valid) { + uri.toURL(); // throws an exception + } + } + catch (MalformedURLException | IllegalArgumentException | URISyntaxException e) { + context.addError(field.getFieldId(), field.getInvalid(), field.getInvalidKey()); + } + } + + private void validatePairwiseInClientModel(ValidationContext context) { + List foundPairwiseMappers = PairwiseSubMapperUtils.getPairwiseSubMappers(toRepresentation(context.getObjectToValidate(), context.getSession())); + + for (ProtocolMapperRepresentation foundPairwise : foundPairwiseMappers) { + String sectorIdentifierUri = PairwiseSubMapperHelper.getSectorIdentifierUri(foundPairwise); + validatePairwise(context, sectorIdentifierUri); } + } + + private void validatePairwiseInOIDCClient(ClientValidationContext.OIDCContext context) { + OIDCClientRepresentation oidcRep = context.getOIDCClient(); - if (!valid) { - throw new ValidationException("Invalid URL in " + field); + SubjectType subjectType = SubjectType.parse(oidcRep.getSubjectType()); + String sectorIdentifierUri = oidcRep.getSectorIdentifierUri(); + + // If sector_identifier_uri is in oidc config, then always validate it + if (SubjectType.PAIRWISE == subjectType || (sectorIdentifierUri != null && !sectorIdentifierUri.isEmpty())) { + validatePairwise(context, oidcRep.getSectorIdentifierUri()); } } - class ValidationException extends Exception { + private void validatePairwise(ValidationContext context, String sectorIdentifierUri) { + ClientModel client = context.getObjectToValidate(); + String rootUrl = client.getRootUrl(); + Set redirectUris = new HashSet<>(); + if (client.getRedirectUris() != null) redirectUris.addAll(client.getRedirectUris()); - public ValidationException(String message) { - super(message, null, false, false); + try { + PairwiseSubMapperValidator.validate(context.getSession(), rootUrl, redirectUris, sectorIdentifierUri); + } catch (ProtocolMapperConfigException e) { + context.addError("pairWise", e.getMessage(), e.getMessageKey()); } } diff --git a/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory b/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory index c4ee319c14b1..81773a26057b 100755 --- a/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory +++ b/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory @@ -23,8 +23,10 @@ org.keycloak.authentication.authenticators.browser.OTPFormAuthenticatorFactory org.keycloak.authentication.authenticators.browser.ScriptBasedAuthenticatorFactory org.keycloak.authentication.authenticators.browser.SpnegoAuthenticatorFactory org.keycloak.authentication.authenticators.browser.IdentityProviderAuthenticatorFactory +org.keycloak.authentication.authenticators.browser.SetLoaAuthenticatorFactory org.keycloak.authentication.authenticators.conditional.ConditionalRoleAuthenticatorFactory org.keycloak.authentication.authenticators.conditional.ConditionalUserConfiguredAuthenticatorFactory +org.keycloak.authentication.authenticators.conditional.ConditionalLoaAuthenticatorFactory org.keycloak.authentication.authenticators.directgrant.ValidateOTP org.keycloak.authentication.authenticators.directgrant.ValidatePassword org.keycloak.authentication.authenticators.directgrant.ValidateUsername diff --git a/testsuite/db-allocator-plugin/pom.xml b/testsuite/db-allocator-plugin/pom.xml index d7faf101a807..91e804707159 100644 --- a/testsuite/db-allocator-plugin/pom.xml +++ b/testsuite/db-allocator-plugin/pom.xml @@ -22,7 +22,7 @@ keycloak-testsuite-pom org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/pom.xml b/testsuite/integration-arquillian/pom.xml index 59da0c2b4b84..6f2f422264e4 100644 --- a/testsuite/integration-arquillian/pom.xml +++ b/testsuite/integration-arquillian/pom.xml @@ -24,7 +24,7 @@ org.keycloak keycloak-testsuite-pom - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml diff --git a/testsuite/integration-arquillian/servers/app-server/app-server-spi/pom.xml b/testsuite/integration-arquillian/servers/app-server/app-server-spi/pom.xml index da3a451447f4..a77e4a965c28 100644 --- a/testsuite/integration-arquillian/servers/app-server/app-server-spi/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/app-server-spi/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/jboss/eap/pom.xml b/testsuite/integration-arquillian/servers/app-server/jboss/eap/pom.xml index cf90757b0bf9..ab18cf029c3b 100644 --- a/testsuite/integration-arquillian/servers/app-server/jboss/eap/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/jboss/eap/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server-jboss - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/jboss/eap6/pom.xml b/testsuite/integration-arquillian/servers/app-server/jboss/eap6/pom.xml index 9351e75208fd..2c98fcc6afc4 100644 --- a/testsuite/integration-arquillian/servers/app-server/jboss/eap6/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/jboss/eap6/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server-jboss - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/jboss/pom.xml b/testsuite/integration-arquillian/servers/app-server/jboss/pom.xml index ea7e16515672..899139f2d4c8 100644 --- a/testsuite/integration-arquillian/servers/app-server/jboss/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/jboss/pom.xml @@ -22,7 +22,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/jboss/relative/eap/pom.xml b/testsuite/integration-arquillian/servers/app-server/jboss/relative/eap/pom.xml index 9e8c3a7d8c83..93d9a8c5964a 100644 --- a/testsuite/integration-arquillian/servers/app-server/jboss/relative/eap/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/jboss/relative/eap/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server-jboss-relative - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/jboss/relative/pom.xml b/testsuite/integration-arquillian/servers/app-server/jboss/relative/pom.xml index 63df2d72a6ce..12819faaaa81 100644 --- a/testsuite/integration-arquillian/servers/app-server/jboss/relative/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/jboss/relative/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server-jboss - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/jboss/relative/wildfly/pom.xml b/testsuite/integration-arquillian/servers/app-server/jboss/relative/wildfly/pom.xml index c915b4419322..633b2ebbbb30 100644 --- a/testsuite/integration-arquillian/servers/app-server/jboss/relative/wildfly/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/jboss/relative/wildfly/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server-jboss-relative - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/jboss/wildfly-deprecated/pom.xml b/testsuite/integration-arquillian/servers/app-server/jboss/wildfly-deprecated/pom.xml index 6f919530382d..836318fcc5e9 100644 --- a/testsuite/integration-arquillian/servers/app-server/jboss/wildfly-deprecated/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/jboss/wildfly-deprecated/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server-jboss - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/jboss/wildfly/pom.xml b/testsuite/integration-arquillian/servers/app-server/jboss/wildfly/pom.xml index 67d5ebd9798d..9877a3703435 100644 --- a/testsuite/integration-arquillian/servers/app-server/jboss/wildfly/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/jboss/wildfly/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server-jboss - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/jetty/92/pom.xml b/testsuite/integration-arquillian/servers/app-server/jetty/92/pom.xml index 52e03f42d139..21cacc297492 100644 --- a/testsuite/integration-arquillian/servers/app-server/jetty/92/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/jetty/92/pom.xml @@ -18,7 +18,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server-jetty - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/jetty/93/pom.xml b/testsuite/integration-arquillian/servers/app-server/jetty/93/pom.xml index 16158bd12d25..6056c5f6c891 100644 --- a/testsuite/integration-arquillian/servers/app-server/jetty/93/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/jetty/93/pom.xml @@ -18,7 +18,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server-jetty - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/jetty/94/pom.xml b/testsuite/integration-arquillian/servers/app-server/jetty/94/pom.xml index 6389cd1e0ca0..9741495c8c4b 100644 --- a/testsuite/integration-arquillian/servers/app-server/jetty/94/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/jetty/94/pom.xml @@ -18,7 +18,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server-jetty - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/jetty/common/pom.xml b/testsuite/integration-arquillian/servers/app-server/jetty/common/pom.xml index cd8d0c576a0f..aa688a4a0e26 100644 --- a/testsuite/integration-arquillian/servers/app-server/jetty/common/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/jetty/common/pom.xml @@ -18,7 +18,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server-jetty - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/jetty/pom.xml b/testsuite/integration-arquillian/servers/app-server/jetty/pom.xml index 7fdbe4e3c9cf..1850e9546e9d 100644 --- a/testsuite/integration-arquillian/servers/app-server/jetty/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/jetty/pom.xml @@ -18,7 +18,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/karaf/fuse63/pom.xml b/testsuite/integration-arquillian/servers/app-server/karaf/fuse63/pom.xml index f27e51b0ab5f..bc31479c0292 100644 --- a/testsuite/integration-arquillian/servers/app-server/karaf/fuse63/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/karaf/fuse63/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server-karaf - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/karaf/fuse7x/pom.xml b/testsuite/integration-arquillian/servers/app-server/karaf/fuse7x/pom.xml index 7cb4a9d64373..cc397ccb6c26 100644 --- a/testsuite/integration-arquillian/servers/app-server/karaf/fuse7x/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/karaf/fuse7x/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server-karaf - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/karaf/pom.xml b/testsuite/integration-arquillian/servers/app-server/karaf/pom.xml index 64ccd4f35ca6..c632ed84ab84 100644 --- a/testsuite/integration-arquillian/servers/app-server/karaf/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/karaf/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/pom.xml b/testsuite/integration-arquillian/servers/app-server/pom.xml index 4fae41dbb1b8..7ac111421fb8 100644 --- a/testsuite/integration-arquillian/servers/app-server/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/tomcat/common/pom.xml b/testsuite/integration-arquillian/servers/app-server/tomcat/common/pom.xml index bce615e18619..c5fb794db671 100644 --- a/testsuite/integration-arquillian/servers/app-server/tomcat/common/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/tomcat/common/pom.xml @@ -5,7 +5,7 @@ integration-arquillian-servers-app-server-tomcat org.keycloak.testsuite - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/tomcat/pom.xml b/testsuite/integration-arquillian/servers/app-server/tomcat/pom.xml index 1f8f6bd58159..dab6ae6bfa58 100644 --- a/testsuite/integration-arquillian/servers/app-server/tomcat/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/tomcat/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/tomcat/tomcat7/pom.xml b/testsuite/integration-arquillian/servers/app-server/tomcat/tomcat7/pom.xml index 06a8c0fce499..99fe466f2828 100644 --- a/testsuite/integration-arquillian/servers/app-server/tomcat/tomcat7/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/tomcat/tomcat7/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server-tomcat - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/tomcat/tomcat8/pom.xml b/testsuite/integration-arquillian/servers/app-server/tomcat/tomcat8/pom.xml index 2cdb5f088d5f..a51f21ef565e 100644 --- a/testsuite/integration-arquillian/servers/app-server/tomcat/tomcat8/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/tomcat/tomcat8/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server-tomcat - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/tomcat/tomcat9/pom.xml b/testsuite/integration-arquillian/servers/app-server/tomcat/tomcat9/pom.xml index 44bc617e927a..485184d51eb5 100644 --- a/testsuite/integration-arquillian/servers/app-server/tomcat/tomcat9/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/tomcat/tomcat9/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server-tomcat - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/undertow/pom.xml b/testsuite/integration-arquillian/servers/app-server/undertow/pom.xml index e5a270a7d4b5..58c65f6da560 100644 --- a/testsuite/integration-arquillian/servers/app-server/undertow/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/undertow/pom.xml @@ -18,7 +18,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/eap/pom.xml b/testsuite/integration-arquillian/servers/auth-server/jboss/eap/pom.xml index 2ad51219d26c..439cbcb8e7d3 100644 --- a/testsuite/integration-arquillian/servers/auth-server/jboss/eap/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/eap/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-auth-server-jboss - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/legacy/pom.xml b/testsuite/integration-arquillian/servers/auth-server/jboss/legacy/pom.xml index 0b56450990cf..d7d75aa1d1a2 100644 --- a/testsuite/integration-arquillian/servers/auth-server/jboss/legacy/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/legacy/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-auth-server-jboss - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml b/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml index 7648d6a67b81..e8d788cd37bb 100644 --- a/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-auth-server - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/wildfly/pom.xml b/testsuite/integration-arquillian/servers/auth-server/jboss/wildfly/pom.xml index 5065cb7125e6..2b3c603b863a 100644 --- a/testsuite/integration-arquillian/servers/auth-server/jboss/wildfly/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/wildfly/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-auth-server-jboss - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/auth-server/pom.xml b/testsuite/integration-arquillian/servers/auth-server/pom.xml index 9ee052ef33a7..7d09d7d4575e 100644 --- a/testsuite/integration-arquillian/servers/auth-server/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/auth-server/quarkus/pom.xml b/testsuite/integration-arquillian/servers/auth-server/quarkus/pom.xml index 54767c69d0f5..ce50bfe4b6a9 100644 --- a/testsuite/integration-arquillian/servers/auth-server/quarkus/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/quarkus/pom.xml @@ -5,7 +5,7 @@ integration-arquillian-servers-auth-server org.keycloak.testsuite - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/auth-server/services/pom.xml b/testsuite/integration-arquillian/servers/auth-server/services/pom.xml index af0a9a81f3b6..f13718919af8 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/services/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-auth-server - 11.0.0-SNAPSHOT + 11.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/pom.xml b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/pom.xml index 8a805f3da46c..208589f4aa6f 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/pom.xml @@ -24,7 +24,7 @@ org.keycloak.testsuite integration-arquillian-servers-auth-server-services - 11.0.0-SNAPSHOT + 11.0.3 integration-arquillian-testsuite-providers diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestJavascriptResource.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestJavascriptResource.java index b8da8ec73262..3ccd8dc53255 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestJavascriptResource.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestJavascriptResource.java @@ -41,6 +41,14 @@ public String getJavascriptTestingEnvironment() throws IOException { return resourceToString("/javascript/index.html"); } + @GET + @Path("/init-in-head.html") + @Produces(MediaType.TEXT_HTML) + public String getJavascriptTestingEnvironmentWithInitInHead() throws IOException { + session.getProvider(SecurityHeadersProvider.class).options().skipHeaders(); + return resourceToString("/javascript/init-in-head.html"); + } + @GET @Path("/silent-check-sso.html") @Produces(MediaType.TEXT_HTML) diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/javascript/init-in-head.html b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/javascript/init-in-head.html new file mode 100644 index 000000000000..5cea6bd60898 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/javascript/init-in-head.html @@ -0,0 +1,65 @@ + + + + + + + + + +

Result

+

+
+

Events

+

+
+
+
+
+
diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/pom.xml b/testsuite/integration-arquillian/servers/auth-server/undertow/pom.xml
index 0dc474eaff8d..d3ee9912cdc3 100644
--- a/testsuite/integration-arquillian/servers/auth-server/undertow/pom.xml
+++ b/testsuite/integration-arquillian/servers/auth-server/undertow/pom.xml
@@ -21,7 +21,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-servers-auth-server
-        11.0.0-SNAPSHOT
+        11.0.3
     
     4.0.0
 
diff --git a/testsuite/integration-arquillian/servers/cache-server/jboss/infinispan/pom.xml b/testsuite/integration-arquillian/servers/cache-server/jboss/infinispan/pom.xml
index 1b8b7561bbd4..259ace322df0 100644
--- a/testsuite/integration-arquillian/servers/cache-server/jboss/infinispan/pom.xml
+++ b/testsuite/integration-arquillian/servers/cache-server/jboss/infinispan/pom.xml
@@ -21,7 +21,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-servers-cache-server-jboss
-        11.0.0-SNAPSHOT
+        11.0.3
     
     4.0.0
 
diff --git a/testsuite/integration-arquillian/servers/cache-server/jboss/jdg/pom.xml b/testsuite/integration-arquillian/servers/cache-server/jboss/jdg/pom.xml
index d95edea1fb50..433bbf4b9764 100644
--- a/testsuite/integration-arquillian/servers/cache-server/jboss/jdg/pom.xml
+++ b/testsuite/integration-arquillian/servers/cache-server/jboss/jdg/pom.xml
@@ -21,7 +21,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-servers-cache-server-jboss
-        11.0.0-SNAPSHOT
+        11.0.3
     
     4.0.0
 
diff --git a/testsuite/integration-arquillian/servers/cache-server/jboss/pom.xml b/testsuite/integration-arquillian/servers/cache-server/jboss/pom.xml
index ae75fbf6acc2..c998ebee9d1c 100644
--- a/testsuite/integration-arquillian/servers/cache-server/jboss/pom.xml
+++ b/testsuite/integration-arquillian/servers/cache-server/jboss/pom.xml
@@ -21,7 +21,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-servers-cache-server
-        11.0.0-SNAPSHOT
+        11.0.3
     
     4.0.0
 
diff --git a/testsuite/integration-arquillian/servers/cache-server/pom.xml b/testsuite/integration-arquillian/servers/cache-server/pom.xml
index 5a3ca9dc6feb..3898cce2d8ac 100644
--- a/testsuite/integration-arquillian/servers/cache-server/pom.xml
+++ b/testsuite/integration-arquillian/servers/cache-server/pom.xml
@@ -21,7 +21,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-servers
-        11.0.0-SNAPSHOT
+        11.0.3
     
     4.0.0
 
diff --git a/testsuite/integration-arquillian/servers/migration/pom.xml b/testsuite/integration-arquillian/servers/migration/pom.xml
index 1431430bc64c..c19ec79983fc 100644
--- a/testsuite/integration-arquillian/servers/migration/pom.xml
+++ b/testsuite/integration-arquillian/servers/migration/pom.xml
@@ -21,7 +21,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-servers
-        11.0.0-SNAPSHOT
+        11.0.3
     
     4.0.0
 
diff --git a/testsuite/integration-arquillian/servers/pom.xml b/testsuite/integration-arquillian/servers/pom.xml
index af8451149f48..b02f2b88e883 100644
--- a/testsuite/integration-arquillian/servers/pom.xml
+++ b/testsuite/integration-arquillian/servers/pom.xml
@@ -21,7 +21,7 @@
     
         org.keycloak.testsuite
         integration-arquillian
-        11.0.0-SNAPSHOT
+        11.0.3
     
     4.0.0
 
diff --git a/testsuite/integration-arquillian/test-apps/app-profile-jee/pom.xml b/testsuite/integration-arquillian/test-apps/app-profile-jee/pom.xml
index a0b66805eb14..3baf54559c21 100644
--- a/testsuite/integration-arquillian/test-apps/app-profile-jee/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/app-profile-jee/pom.xml
@@ -5,7 +5,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-test-apps
-        11.0.0-SNAPSHOT
+        11.0.3
     
     
     keycloak-test-app-profile-jee
diff --git a/testsuite/integration-arquillian/test-apps/cors/angular-product/pom.xml b/testsuite/integration-arquillian/test-apps/cors/angular-product/pom.xml
index ee0525074994..2823928c9ed2 100755
--- a/testsuite/integration-arquillian/test-apps/cors/angular-product/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/cors/angular-product/pom.xml
@@ -21,7 +21,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-test-apps-cors-parent
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     4.0.0
diff --git a/testsuite/integration-arquillian/test-apps/cors/database-service/pom.xml b/testsuite/integration-arquillian/test-apps/cors/database-service/pom.xml
index c234e29c7670..fb4ab1d73089 100755
--- a/testsuite/integration-arquillian/test-apps/cors/database-service/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/cors/database-service/pom.xml
@@ -21,7 +21,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-test-apps-cors-parent
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     4.0.0
diff --git a/testsuite/integration-arquillian/test-apps/cors/pom.xml b/testsuite/integration-arquillian/test-apps/cors/pom.xml
index d76b4739d43e..5f7826c5ffde 100644
--- a/testsuite/integration-arquillian/test-apps/cors/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/cors/pom.xml
@@ -5,7 +5,7 @@
     
         integration-arquillian-test-apps
         org.keycloak.testsuite
-        11.0.0-SNAPSHOT
+        11.0.3
     
     4.0.0
 
diff --git a/testsuite/integration-arquillian/test-apps/fuse/camel-fuse7-undertow/pom.xml b/testsuite/integration-arquillian/test-apps/fuse/camel-fuse7-undertow/pom.xml
index 1c78441ecc15..eaca3491fd8b 100755
--- a/testsuite/integration-arquillian/test-apps/fuse/camel-fuse7-undertow/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/fuse/camel-fuse7-undertow/pom.xml
@@ -21,7 +21,7 @@
     
         integration-arquillian-test-apps-fuse-parent
         org.keycloak.testsuite
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     4.0.0
diff --git a/testsuite/integration-arquillian/test-apps/fuse/camel/pom.xml b/testsuite/integration-arquillian/test-apps/fuse/camel/pom.xml
index 6edb3a5f9f26..006135c254d2 100755
--- a/testsuite/integration-arquillian/test-apps/fuse/camel/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/fuse/camel/pom.xml
@@ -21,7 +21,7 @@
     
         integration-arquillian-test-apps-fuse-parent
         org.keycloak.testsuite
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     4.0.0
diff --git a/testsuite/integration-arquillian/test-apps/fuse/customer-app-fuse/pom.xml b/testsuite/integration-arquillian/test-apps/fuse/customer-app-fuse/pom.xml
index 4353f49c430a..f3fe705c3f68 100755
--- a/testsuite/integration-arquillian/test-apps/fuse/customer-app-fuse/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/fuse/customer-app-fuse/pom.xml
@@ -21,7 +21,7 @@
     
         integration-arquillian-test-apps-fuse-parent
         org.keycloak.testsuite
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     4.0.0
diff --git a/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxrs-fuse7-undertow/pom.xml b/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxrs-fuse7-undertow/pom.xml
index 9f1e509847cc..83e543dee3a3 100755
--- a/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxrs-fuse7-undertow/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxrs-fuse7-undertow/pom.xml
@@ -21,7 +21,7 @@
     
         integration-arquillian-test-apps-fuse-parent
         org.keycloak.testsuite
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     4.0.0
diff --git a/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxrs/pom.xml b/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxrs/pom.xml
index bbf6ae1b2ba8..df5595c87c91 100755
--- a/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxrs/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxrs/pom.xml
@@ -21,7 +21,7 @@
     
         integration-arquillian-test-apps-fuse-parent
         org.keycloak.testsuite
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     4.0.0
diff --git a/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxws-fuse7-undertow/pom.xml b/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxws-fuse7-undertow/pom.xml
index 8cefdbd52f1c..ccf7ea1c3258 100755
--- a/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxws-fuse7-undertow/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxws-fuse7-undertow/pom.xml
@@ -21,7 +21,7 @@
     
         integration-arquillian-test-apps-fuse-parent
         org.keycloak.testsuite
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     4.0.0
diff --git a/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxws/pom.xml b/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxws/pom.xml
index 8dd95bd5d163..31d3bba17c72 100755
--- a/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxws/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxws/pom.xml
@@ -21,7 +21,7 @@
     
         integration-arquillian-test-apps-fuse-parent
         org.keycloak.testsuite
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     4.0.0
diff --git a/testsuite/integration-arquillian/test-apps/fuse/external-config/pom.xml b/testsuite/integration-arquillian/test-apps/fuse/external-config/pom.xml
index 478d25d6921f..fb06b7e1da1a 100755
--- a/testsuite/integration-arquillian/test-apps/fuse/external-config/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/fuse/external-config/pom.xml
@@ -21,7 +21,7 @@
     
         integration-arquillian-test-apps-fuse-parent
         org.keycloak.testsuite
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     Keycloak Examples - External Config
diff --git a/testsuite/integration-arquillian/test-apps/fuse/features/pom.xml b/testsuite/integration-arquillian/test-apps/fuse/features/pom.xml
index 4912d7351d6a..4b22546cd206 100755
--- a/testsuite/integration-arquillian/test-apps/fuse/features/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/fuse/features/pom.xml
@@ -21,7 +21,7 @@
     
         integration-arquillian-test-apps-fuse-parent
         org.keycloak.testsuite
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     4.0.0
diff --git a/testsuite/integration-arquillian/test-apps/fuse/pom.xml b/testsuite/integration-arquillian/test-apps/fuse/pom.xml
index 4883cb81d3e5..899d64c0a62f 100755
--- a/testsuite/integration-arquillian/test-apps/fuse/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/fuse/pom.xml
@@ -20,7 +20,7 @@
     
         integration-arquillian-test-apps
         org.keycloak.testsuite
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     Fuse Test Applications
diff --git a/testsuite/integration-arquillian/test-apps/fuse/product-app-fuse/pom.xml b/testsuite/integration-arquillian/test-apps/fuse/product-app-fuse/pom.xml
index 4bdfc2be284f..5d954cc1a6f5 100755
--- a/testsuite/integration-arquillian/test-apps/fuse/product-app-fuse/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/fuse/product-app-fuse/pom.xml
@@ -21,7 +21,7 @@
     
         integration-arquillian-test-apps-fuse-parent
         org.keycloak.testsuite
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     4.0.0
diff --git a/testsuite/integration-arquillian/test-apps/fuse/product-app-fuse7-undertow/pom.xml b/testsuite/integration-arquillian/test-apps/fuse/product-app-fuse7-undertow/pom.xml
index 9a0baa22d42b..0012fa81624c 100755
--- a/testsuite/integration-arquillian/test-apps/fuse/product-app-fuse7-undertow/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/fuse/product-app-fuse7-undertow/pom.xml
@@ -21,7 +21,7 @@
     
         integration-arquillian-test-apps-fuse-parent
         org.keycloak.testsuite
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     4.0.0
diff --git a/testsuite/integration-arquillian/test-apps/hello-world-authz-service/pom.xml b/testsuite/integration-arquillian/test-apps/hello-world-authz-service/pom.xml
index 1b979ff59f2d..4cee7ad06382 100755
--- a/testsuite/integration-arquillian/test-apps/hello-world-authz-service/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/hello-world-authz-service/pom.xml
@@ -24,7 +24,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-test-apps
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     hello-world-authz-service
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/pom.xml b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/pom.xml
index 0a7812b04911..4cd899c20b8e 100755
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-html5-client/pom.xml
@@ -5,7 +5,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-test-apps-photoz-parent
-        11.0.0-SNAPSHOT
+        11.0.3
         ../pom.xml
     
 
diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/pom.xml b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/pom.xml
index c15978a9b351..0e4f3b28ba12 100755
--- a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/pom.xml
@@ -6,7 +6,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-test-apps-photoz-parent
-        11.0.0-SNAPSHOT
+        11.0.3
         ../pom.xml
     
 
diff --git a/testsuite/integration-arquillian/test-apps/photoz/pom.xml b/testsuite/integration-arquillian/test-apps/photoz/pom.xml
index 8a6a3e75115b..e4a2259015db 100755
--- a/testsuite/integration-arquillian/test-apps/photoz/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/photoz/pom.xml
@@ -6,7 +6,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-test-apps
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     integration-arquillian-test-apps-photoz-parent
diff --git a/testsuite/integration-arquillian/test-apps/pom.xml b/testsuite/integration-arquillian/test-apps/pom.xml
index 568d9c3339f0..ab3979ab547b 100644
--- a/testsuite/integration-arquillian/test-apps/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/pom.xml
@@ -5,7 +5,7 @@
     
         integration-arquillian
         org.keycloak.testsuite
-        11.0.0-SNAPSHOT
+        11.0.3
     
     4.0.0
 
diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/pom.xml b/testsuite/integration-arquillian/test-apps/servlet-authz/pom.xml
index 73bcdc1475be..df4f65733b0c 100755
--- a/testsuite/integration-arquillian/test-apps/servlet-authz/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/servlet-authz/pom.xml
@@ -6,7 +6,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-test-apps
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     servlet-authz-app
diff --git a/testsuite/integration-arquillian/test-apps/servlet-policy-enforcer/pom.xml b/testsuite/integration-arquillian/test-apps/servlet-policy-enforcer/pom.xml
index a104244ae4e6..f6b8a4257f56 100755
--- a/testsuite/integration-arquillian/test-apps/servlet-policy-enforcer/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/servlet-policy-enforcer/pom.xml
@@ -24,7 +24,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-test-apps
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     servlet-policy-enforcer
diff --git a/testsuite/integration-arquillian/test-apps/servlets/pom.xml b/testsuite/integration-arquillian/test-apps/servlets/pom.xml
index 2a53ecfeb52c..05b8e71abacb 100644
--- a/testsuite/integration-arquillian/test-apps/servlets/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/servlets/pom.xml
@@ -5,7 +5,7 @@
     
         integration-arquillian-test-apps
         org.keycloak.testsuite
-        11.0.0-SNAPSHOT
+        11.0.3
     
     4.0.0
 
diff --git a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/SerializationServlet.java b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/SerializationServlet.java
new file mode 100644
index 000000000000..eef704f86f8e
--- /dev/null
+++ b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/SerializationServlet.java
@@ -0,0 +1,82 @@
+/*
+ * 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.adapter.servlet;
+
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamClass;
+import java.io.PrintWriter;
+
+/**
+ * @author mhajas
+ */
+@WebServlet("/serialization-servlet")
+public class SerializationServlet extends HttpServlet {
+
+    @Override
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        PrintWriter pw = resp.getWriter();
+        // Serialize
+        ByteArrayOutputStream bso = new ByteArrayOutputStream();
+        ObjectOutputStream oos = new ObjectOutputStream(bso);
+        oos.writeObject(req.getUserPrincipal());
+        oos.close();
+
+        // Deserialize
+        byte[] bytes = bso.toByteArray();
+        ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
+        ObjectInputStream ois = new ObjectInputStream(bis) {
+            @Override
+            public Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
+                try {
+                    return Class.forName(desc.getName(), true, SerializationServlet.class.getClassLoader());
+                } catch (Exception e) { }
+
+                // Fall back (e.g. for primClasses)
+                return super.resolveClass(desc);
+            }
+        };
+
+        KeycloakPrincipal principal;
+        try {
+            principal = (KeycloakPrincipal) ois.readObject();
+        } catch (ClassNotFoundException e) {
+            e.printStackTrace();
+            pw.write("Deserialization failed");
+            return;
+        }
+
+        KeycloakSecurityContext ctx = principal.getKeycloakSecurityContext();
+        if (!(ctx instanceof RefreshableKeycloakSecurityContext)) {
+            pw.write("Context was not instance of RefreshableKeycloakSecurityContext");
+        }
+        
+        pw.write("Serialization/Deserialization was successful");
+    }
+}
diff --git a/testsuite/integration-arquillian/test-apps/spring-boot-adapter-app/pom.xml b/testsuite/integration-arquillian/test-apps/spring-boot-adapter-app/pom.xml
index 501aa53fcf60..b76d87c78e8e 100644
--- a/testsuite/integration-arquillian/test-apps/spring-boot-adapter-app/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/spring-boot-adapter-app/pom.xml
@@ -5,7 +5,7 @@
     
         integration-arquillian-test-apps
         org.keycloak.testsuite
-        11.0.0-SNAPSHOT
+        11.0.3
     
     4.0.0
 
diff --git a/testsuite/integration-arquillian/test-apps/test-apps-dist/pom.xml b/testsuite/integration-arquillian/test-apps/test-apps-dist/pom.xml
index b0a3d78b1f2b..d4fe59c889e5 100644
--- a/testsuite/integration-arquillian/test-apps/test-apps-dist/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/test-apps-dist/pom.xml
@@ -5,7 +5,7 @@
     
         integration-arquillian-test-apps
         org.keycloak.testsuite
-        11.0.0-SNAPSHOT
+        11.0.3
     
     4.0.0
 
diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml
index 332b54a3121d..3a6cedd97445 100644
--- a/testsuite/integration-arquillian/tests/base/pom.xml
+++ b/testsuite/integration-arquillian/tests/base/pom.xml
@@ -21,7 +21,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-tests
-        11.0.0-SNAPSHOT
+        11.0.3
     
     4.0.0
 
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SerializationServletPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SerializationServletPage.java
new file mode 100644
index 000000000000..2308cb981824
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SerializationServletPage.java
@@ -0,0 +1,48 @@
+/*
+ * 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.adapter.page;
+
+import org.jboss.arquillian.container.test.api.OperateOnDeployment;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
+
+import javax.ws.rs.core.UriBuilder;
+import java.net.URI;
+import java.net.URL;
+
+/**
+ *
+ * @author tkyjovsk
+ */
+public class SerializationServletPage extends AbstractPageWithInjectedUrl {
+
+    public static final String DEPLOYMENT_NAME = "serialization-servlet";
+
+    @ArquillianResource
+    @OperateOnDeployment(DEPLOYMENT_NAME)
+    private URL url;
+
+    @Override
+    public URL getInjectedUrl() {
+        return url;
+    }
+    
+    public URI logout() {
+        return getUriBuilder().clone().path("logout").build();
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/JavascriptTestExecutor.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/JavascriptTestExecutor.java
index 54b33be1c530..9a2693b0d975 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/JavascriptTestExecutor.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/JavascriptTestExecutor.java
@@ -322,4 +322,9 @@ public JavascriptTestExecutor wait(long millis, JavascriptStateValidator validat
 
         return this;
     }
+
+    public JavascriptTestExecutor validateOutputField(JavascriptStateValidator validator) {
+        validator.validate(jsDriver, output, events);
+        return this;
+    }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountFormServiceTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountFormServiceTest.java
index 2b08f03daec9..65e59ef93a88 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountFormServiceTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountFormServiceTest.java
@@ -80,9 +80,13 @@
 
 import org.hamcrest.Matchers;
 import org.junit.Assume;
+
+import static org.hamcrest.Matchers.anyOf;
 import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.hasItems;
+import static org.hamcrest.Matchers.hasSize;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThat;
@@ -1129,8 +1133,8 @@ public void sessions() {
         Assert.assertTrue(sessionsPage.isCurrent());
 
         List> sessions = sessionsPage.getSessions();
-        Assert.assertEquals(1, sessions.size());
-        Assert.assertEquals("127.0.0.1", sessions.get(0).get(0));
+        assertThat(sessions, hasSize(1));
+        assertThat(sessions.get(0).get(0), anyOf(equalTo("127.0.0.1"), equalTo("0:0:0:0:0:0:0:1")));
 
         // Create second session
         try {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResourcesRestServiceTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResourcesRestServiceTest.java
index a07f294b9a7a..60416d1d141e 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResourcesRestServiceTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResourcesRestServiceTest.java
@@ -16,24 +16,6 @@
  */
 package org.keycloak.testsuite.account;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import javax.ws.rs.core.Response;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Consumer;
-
 import com.fasterxml.jackson.core.type.TypeReference;
 import org.junit.Test;
 import org.keycloak.admin.client.resource.AuthorizationResource;
@@ -57,9 +39,29 @@
 import org.keycloak.services.resources.account.resources.AbstractResourceService.Permission;
 import org.keycloak.services.resources.account.resources.AbstractResourceService.Resource;
 import org.keycloak.testsuite.util.ClientBuilder;
+import org.keycloak.testsuite.util.TokenUtil;
 import org.keycloak.testsuite.util.UserBuilder;
 import org.keycloak.util.JsonSerialization;
 
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 /**
  * @author Pedro Igor
  */
@@ -403,6 +405,47 @@ public void failShareResourceInvalidPermissions() throws Exception {
         assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus());
     }
 
+    @Test
+    public void testEndpointPermissions() throws Exception {
+        // resource for view-account-access
+        String resourceId;
+        ResourceRepresentation resource = new ResourceRepresentation();
+        resource.setOwnerManagedAccess(true);
+        resource.setOwner(findUser("view-account-access").getId());
+        resource.setName("Resource view-account-access");
+        resource.setDisplayName("Display Name view-account-access");
+        resource.setIconUri("Icon Uri view-account-access");
+        resource.addScope("Scope A", "Scope B", "Scope C", "Scope D");
+        resource.setUri("http://resourceServer.com/resources/view-account-access");
+        try (Response response1 = getResourceServer().authorization().resources().create(resource)) {
+            resourceId = response1.readEntity(ResourceRepresentation.class).getId();
+        }
+
+        final String resourcesUrl = getAccountUrl("resources");
+        final String sharedWithOthersUrl = resourcesUrl + "/shared-with-others";
+        final String sharedWithMeUrl = resourcesUrl + "/shared-with-me";
+        final String resourceUrl = resourcesUrl + "/" + resourceId;
+        final String permissionsUrl = resourceUrl + "/permissions";
+        final String requestsUrl = resourceUrl + "/permissions/requests";
+
+        TokenUtil viewProfileTokenUtil = new TokenUtil("view-account-access", "password");
+        TokenUtil noAccessTokenUtil = new TokenUtil("no-account-access", "password");
+
+        // test read access
+        for (String url : Arrays.asList(resourcesUrl, sharedWithOthersUrl, sharedWithMeUrl, resourceUrl, permissionsUrl, requestsUrl)) {
+            assertEquals( "no-account-access GET " + url, 403,
+                    SimpleHttp.doGet(url, httpClient).acceptJson().auth(noAccessTokenUtil.getToken()).asStatus());
+            assertEquals("view-account-access GET " + url,200,
+                    SimpleHttp.doGet(url, httpClient).acceptJson().auth(viewProfileTokenUtil.getToken()).asStatus());
+        }
+
+        // test write access
+        assertEquals( "no-account-access PUT " + permissionsUrl, 403,
+                SimpleHttp.doPut(permissionsUrl, httpClient).acceptJson().auth(noAccessTokenUtil.getToken()).json(Collections.emptyList()).asStatus());
+        assertEquals( "view-account-access PUT " + permissionsUrl, 403,
+                SimpleHttp.doPut(permissionsUrl, httpClient).acceptJson().auth(viewProfileTokenUtil.getToken()).json(Collections.emptyList()).asStatus());
+    }
+
     @Test
     public void testRevokePermission() throws Exception {
         List users = Arrays.asList("jdoe", "alice");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/SessionRestServiceTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/SessionRestServiceTest.java
index 82e1d78a7b5c..9e9512ce24bf 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/SessionRestServiceTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/SessionRestServiceTest.java
@@ -16,10 +16,13 @@
  */
 package org.keycloak.testsuite.account;
 
+import static org.hamcrest.Matchers.anyOf;
+import static org.hamcrest.Matchers.equalTo;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import java.io.IOException;
 import java.util.List;
@@ -132,18 +135,20 @@ public void testGetSessions() throws Exception {
 
         for (SessionRepresentation session : sessions) {
             assertNotNull(session.getId());
-            assertEquals("127.0.0.1", session.getIpAddress());
+            assertThat(session.getIpAddress(), anyOf(equalTo("127.0.0.1"), equalTo("0:0:0:0:0:0:0:1")));
             assertTrue(session.getLastAccess() > 0);
             assertTrue(session.getExpires() > 0);
             assertTrue(session.getStarted() > 0);
             assertThat(session.getClients(), Matchers.hasItem(Matchers.hasProperty("clientId",
-                    Matchers.anyOf(Matchers.is("direct-grant"), Matchers.is("public-client-0")))));
+                    anyOf(Matchers.is("direct-grant"), Matchers.is("public-client-0")))));
         }
     }
 
     @Test
     @AuthServerContainerExclude(AuthServer.REMOTE)
     public void testGetDevicesResponse() throws Exception {
+        assumeTrue("Browser must be htmlunit. Otherwise we are not able to set desired BrowserHeaders",
+                System.getProperty("browser").equals("htmlUnit"));
         oauth.setBrowserHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0) Gecko/20100101 Firefox/15.0.1");
         OAuthClient.AccessTokenResponse tokenResponse = codeGrant("public-client-0");
         joinSsoSession("public-client-1");
@@ -168,14 +173,17 @@ public void testGetDevicesResponse() throws Exception {
         List clients = session.getClients();
         assertEquals(2, clients.size());
         assertThat(session.getClients(), Matchers.hasItem(Matchers.hasProperty("clientId",
-                Matchers.anyOf(Matchers.is("public-client-0"), Matchers.is("public-client-1")))));
+                anyOf(Matchers.is("public-client-0"), Matchers.is("public-client-1")))));
         assertThat(session.getClients(), Matchers.hasItem(Matchers.hasProperty("clientName",
-                Matchers.anyOf(Matchers.is("Public Client 0"), Matchers.is("Public Client 1")))));
+                anyOf(Matchers.is("Public Client 0"), Matchers.is("Public Client 1")))));
     }
 
     @Test
     public void testGetDevicesSessions() throws Exception {
         ContainerAssume.assumeAuthServerUndertow();
+        assumeTrue("Browser must be htmlunit. Otherwise we are not able to set desired BrowserHeaders",
+                System.getProperty("browser").equals("htmlUnit"));
+
         WebDriver firstBrowser = oauth.getDriver();
 
         // first browser authenticates from Fedora
@@ -343,6 +351,9 @@ public void testLogoutAll() throws IOException {
     @Test
     @AuthServerContainerExclude(AuthServer.REMOTE)
     public void testNullOrEmptyUserAgent() throws Exception {
+        assumeTrue("Browser must be htmlunit. Otherwise we are not able to set desired BrowserHeaders",
+                System.getProperty("browser").equals("htmlUnit"));
+
         oauth.setBrowserHeader("User-Agent", null);
         OAuthClient.AccessTokenResponse tokenResponse = codeGrant("public-client-0");
 
@@ -367,6 +378,9 @@ public void testNullOrEmptyUserAgent() throws Exception {
 
     @Test
     public void testNonBrowserSession() throws Exception {
+        assumeTrue("Browser must be htmlunit. Otherwise we are not able to set desired BrowserHeaders",
+                System.getProperty("browser").equals("htmlUnit"));
+
         // one device
         oauth.setBrowserHeader("User-Agent", "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0.1");
         codeGrant("public-client-0");
@@ -382,7 +396,7 @@ public void testNonBrowserSession() throws Exception {
         assertEquals(2, devices.size());
 
         assertThat(devices,
-                Matchers.hasItems(Matchers.hasProperty("os", Matchers.anyOf(Matchers.is("Fedora"), Matchers.is("Other")))));
+                Matchers.hasItems(Matchers.hasProperty("os", anyOf(Matchers.is("Fedora"), Matchers.is("Other")))));
 
         // three because tests use another client when booting tests
         assertEquals(3, devices.stream().filter(deviceRepresentation -> "Other".equals(deviceRepresentation.getOs()))
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/KeycloakPrincipalSerializationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/KeycloakPrincipalSerializationTest.java
new file mode 100644
index 000000000000..8066446fc894
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/KeycloakPrincipalSerializationTest.java
@@ -0,0 +1,41 @@
+package org.keycloak.testsuite.adapter.servlet;
+
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.graphene.page.Page;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Test;
+import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest;
+import org.keycloak.testsuite.adapter.page.SerializationServletPage;
+import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
+import org.keycloak.testsuite.utils.arquillian.ContainerConstants;
+
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertThat;
+
+
+@AppServerContainer(ContainerConstants.APP_SERVER_UNDERTOW)
+@AppServerContainer(ContainerConstants.APP_SERVER_WILDFLY)
+@AppServerContainer(ContainerConstants.APP_SERVER_EAP)
+// The purpose of this class is to test KeycloakPrincipal serialization on different app-server-jdks
+public class KeycloakPrincipalSerializationTest extends AbstractServletsAdapterTest {
+    @Page
+    protected SerializationServletPage serializationServlet;
+
+    @Deployment(name = SerializationServletPage.DEPLOYMENT_NAME)
+    protected static WebArchive serializationServlet() {
+        return servletDeployment(SerializationServletPage.DEPLOYMENT_NAME, SerializationServlet.class, ErrorServlet.class, ServletTestUtils.class);
+    }
+
+    @Test
+    public void testKeycloakPrincipalSerialization() {
+        serializationServlet.navigateTo();
+        testRealmLoginPage.form().login("bburke@redhat.com", "password");
+
+        assertThat(driver.getPageSource(), containsString("Serialization/Deserialization was successful"));
+        assertThat(driver.getPageSource(), not(containsString("Context was not instance of RefreshableKeycloakSecurityContext")));
+        assertThat(driver.getPageSource(), not(containsString("Deserialization failed")));
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/AdminHeadersTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/AdminHeadersTest.java
index 04a32f13af04..41fcdfd212b8 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/AdminHeadersTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/AdminHeadersTest.java
@@ -1,22 +1,20 @@
 package org.keycloak.testsuite.admin;
 
-import org.apache.http.client.methods.CloseableHttpResponse;
-import org.apache.http.client.methods.HttpOptions;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClientBuilder;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.keycloak.models.BrowserSecurityHeaders;
-import org.keycloak.services.resources.Cors;
 import org.keycloak.testsuite.util.UserBuilder;
 
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
 import java.io.IOException;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
 
 public class AdminHeadersTest extends AbstractAdminTest {
 
@@ -42,14 +40,19 @@ public void testHeaders() {
         Response response = realm.users().create(UserBuilder.create().username("headers-user").build());
         MultivaluedMap h = response.getHeaders();
 
-        assertEquals(BrowserSecurityHeaders.STRICT_TRANSPORT_SECURITY_DEFAULT, h.getFirst(BrowserSecurityHeaders.STRICT_TRANSPORT_SECURITY));
-        assertEquals(BrowserSecurityHeaders.X_FRAME_OPTIONS_DEFAULT, h.getFirst(BrowserSecurityHeaders.X_FRAME_OPTIONS));
-        assertEquals(BrowserSecurityHeaders.X_CONTENT_TYPE_OPTIONS_DEFAULT, h.getFirst(BrowserSecurityHeaders.X_CONTENT_TYPE_OPTIONS));
-        assertEquals(BrowserSecurityHeaders.X_XSS_PROTECTION_DEFAULT, h.getFirst(BrowserSecurityHeaders.X_XSS_PROTECTION));
+        assertDefaultValue(BrowserSecurityHeaders.STRICT_TRANSPORT_SECURITY, h);
+        assertDefaultValue(BrowserSecurityHeaders.X_FRAME_OPTIONS, h);
+        assertDefaultValue(BrowserSecurityHeaders.X_CONTENT_TYPE_OPTIONS, h);
+        assertDefaultValue(BrowserSecurityHeaders.X_XSS_PROTECTION, h);
+        assertDefaultValue(BrowserSecurityHeaders.REFERRER_POLICY, h);
 
         response.close();
     }
 
+    private void assertDefaultValue(BrowserSecurityHeaders header, MultivaluedMap h) {
+        assertThat(h.getFirst(header.getHeaderName()), is(equalTo(header.getDefaultValue())));
+    }
+
     private String getAdminUrl(String resource) {
         return suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth/admin/" + resource;
     }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java
index 7f5642d63adc..ea5b0d42ae4d 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java
@@ -51,15 +51,19 @@
 import javax.ws.rs.NotFoundException;
 import javax.ws.rs.core.Response;
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
 
+import static java.util.Arrays.asList;
 import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.junit.Assert.*;
 import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
 import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
@@ -104,90 +108,101 @@ public void createClientVerify() {
     }
 
     @Test
-    public void createClientValidation() {
-        ClientRepresentation rep = new ClientRepresentation();
-        rep.setClientId("my-app");
-        rep.setDescription("my-app description");
-        rep.setEnabled(true);
-
-        rep.setRootUrl("invalid");
-        createClientExpectingValidationError(rep, "Invalid URL in rootUrl");
-
-        rep.setRootUrl(null);
-        rep.setBaseUrl("invalid");
-        createClientExpectingValidationError(rep, "Invalid URL in baseUrl");
-
-        rep.setRootUrl(null);
-        rep.setBaseUrl("/valid");
-        createClientExpectingSuccessfulClientCreation(rep);
-
-        rep.setRootUrl("");
-        rep.setBaseUrl("/valid");
-        createClientExpectingSuccessfulClientCreation(rep);
+    public void testInvalidUrlClientValidation() {
+        testClientUriValidation("Root URL is not a valid URL",
+                "Base URL is not a valid URL",
+                null,
+                "invalid", "myapp://some-fake-app");
     }
 
     @Test
-    public void updateClientValidation() {
-        ClientRepresentation rep = createClient();
-
-        rep.setClientId("my-app");
-        rep.setDescription("my-app description");
-        rep.setEnabled(true);
-
-        rep.setRootUrl("invalid");
-        updateClientExpectingValidationError(rep, "Invalid URL in rootUrl");
-
-        rep.setRootUrl(null);
-        rep.setBaseUrl("invalid");
-        updateClientExpectingValidationError(rep, "Invalid URL in baseUrl");
-
-        ClientRepresentation stored = realm.clients().get(rep.getId()).toRepresentation();
-        assertNull(stored.getRootUrl());
-        assertNull(stored.getBaseUrl());
+    public void testIllegalSchemeClientValidation() {
+        testClientUriValidation("Root URL uses an illegal scheme",
+                "Base URL uses an illegal scheme",
+                "A redirect URI uses an illegal scheme",
+                "data:text/html;base64,PHNjcmlwdD5jb25maXJtKGRvY3VtZW50LmRvbWFpbik7PC9zY3JpcHQ+",
+                "javascript:confirm(document.domain)/*"
+        );
+    }
 
-        rep.setRootUrl(null);
-        rep.setBaseUrl("/valid");
-        updateClientExpectingSuccessfulClientUpdate(rep, null, "/valid");
+    // KEYCLOAK-3421
+    @Test
+    public void testFragmentProhibitedClientValidation() {
+        testClientUriValidation("Root URL must not contain an URL fragment",
+                null,
+                "Redirect URIs must not contain an URI fragment",
+                "http://redhat.com/abcd#someFragment"
+        );
+    }
 
-        rep.setRootUrl("");
-        rep.setBaseUrl("/valid");
-        updateClientExpectingSuccessfulClientUpdate(rep, "", "/valid");
+    private void testClientUriValidation(String expectedRootUrlError, String expectedBaseUrlError, String expectedRedirectUrisError, String... testUrls) {
+        testClientUriValidation(false, expectedRootUrlError, expectedBaseUrlError, expectedRedirectUrisError, testUrls);
+        testClientUriValidation(true, expectedRootUrlError, expectedBaseUrlError, expectedRedirectUrisError, testUrls);
     }
 
-    private void createClientExpectingValidationError(ClientRepresentation rep, String expectedError) {
-        Response response = realm.clients().create(rep);
+    private void testClientUriValidation(boolean create, String expectedRootUrlError, String expectedBaseUrlError, String expectedRedirectUrisError, String... testUrls) {
+        ClientRepresentation rep;
+        if (create) {
+            rep = new ClientRepresentation();
+            rep.setClientId("my-app2");
+            rep.setEnabled(true);
+        }
+        else {
+            rep = createClient();
+        }
 
-        assertEquals(400, response.getStatus());
-        OAuth2ErrorRepresentation error = response.readEntity(OAuth2ErrorRepresentation.class);
-        assertEquals("invalid_input", error.getError());
-        assertEquals(expectedError, error.getErrorDescription());
+        for (String testUrl : testUrls) {
+            if (expectedRootUrlError != null) {
+                rep.setRootUrl(testUrl);
+                createOrUpdateClientExpectingValidationErrors(rep, create, expectedRootUrlError);
+            }
+            rep.setRootUrl(null);
 
-        assertNull(response.getLocation());
+            if (expectedBaseUrlError != null) {
+                rep.setBaseUrl(testUrl);
+                createOrUpdateClientExpectingValidationErrors(rep, create, expectedBaseUrlError);
+            }
+            rep.setBaseUrl(null);
 
-        response.close();
-    }
+            if (expectedRedirectUrisError != null) {
+                rep.setRedirectUris(Collections.singletonList(testUrl));
+                createOrUpdateClientExpectingValidationErrors(rep, create, expectedRedirectUrisError);
+            }
+            rep.setRedirectUris(null);
 
-    private void createClientExpectingSuccessfulClientCreation(ClientRepresentation rep) {
-        Response response = realm.clients().create(rep);
-        assertEquals(201, response.getStatus());
+            if (expectedRootUrlError != null) rep.setRootUrl(testUrl);
+            if (expectedBaseUrlError != null) rep.setBaseUrl(testUrl);
+            if (expectedRedirectUrisError != null) rep.setRedirectUris(Collections.singletonList(testUrl));
+            createOrUpdateClientExpectingValidationErrors(rep, create, expectedRootUrlError, expectedBaseUrlError, expectedRedirectUrisError);
 
-        String id = ApiUtil.getCreatedId(response);
-        realm.clients().get(id).remove();
-
-        response.close();
+            rep.setRootUrl(null);
+            rep.setBaseUrl(null);
+            rep.setRedirectUris(null);
+        }
     }
 
-    private void updateClientExpectingValidationError(ClientRepresentation rep, String expectedError) {
-        try {
-            realm.clients().get(rep.getId()).update(rep);
-            fail("Expected exception");
-        } catch (BadRequestException e) {
-            Response response = e.getResponse();
-            assertEquals(400, response.getStatus());
-            OAuth2ErrorRepresentation error = response.readEntity(OAuth2ErrorRepresentation.class);
-            assertEquals("invalid_input", error.getError());
-            assertEquals(expectedError, error.getErrorDescription());
+    private void createOrUpdateClientExpectingValidationErrors(ClientRepresentation rep, boolean create, String... expectedErrors) {
+        Response response = null;
+        if (create) {
+            response = realm.clients().create(rep);
         }
+        else {
+            try {
+                realm.clients().get(rep.getId()).update(rep);
+                fail("Expected exception");
+            }
+            catch (BadRequestException e) {
+                response = e.getResponse();
+            }
+        }
+
+        expectedErrors = Arrays.stream(expectedErrors).filter(Objects::nonNull).toArray(String[]::new);
+
+        assertEquals(response.getStatus(), 400);
+        OAuth2ErrorRepresentation errorRep = response.readEntity(OAuth2ErrorRepresentation.class);
+        List actualErrors = asList(errorRep.getErrorDescription().split("; "));
+        assertThat(actualErrors, containsInAnyOrder(expectedErrors));
+        assertEquals("invalid_input", errorRep.getError());
     }
 
     private void updateClientExpectingSuccessfulClientUpdate(ClientRepresentation rep, String expectedRootUrl, String expectedBaseUrl) {
@@ -369,55 +384,6 @@ public void serviceAccount() {
         assertNull(userRep.getEmail());
     }
 
-    // KEYCLOAK-3421
-    @Test
-    public void createClientWithFragments() {
-        ClientRepresentation client = ClientBuilder.create()
-                .clientId("client-with-fragment")
-                .rootUrl("http://localhost/base#someFragment")
-                .redirectUris("http://localhost/auth", "http://localhost/auth#fragment", "http://localhost/auth*", "/relative")
-                .build();
-
-        Response response = realm.clients().create(client);
-        assertUriFragmentError(response);
-    }
-
-    // KEYCLOAK-3421
-    @Test
-    public void updateClientWithFragments() {
-        ClientRepresentation client = ClientBuilder.create()
-                .clientId("client-with-fragment")
-                .redirectUris("http://localhost/auth", "http://localhost/auth*")
-                .build();
-        Response response = realm.clients().create(client);
-        String clientUuid = ApiUtil.getCreatedId(response);
-        ClientResource clientResource = realm.clients().get(clientUuid);
-        getCleanup().addClientUuid(clientUuid);
-        response.close();
-
-        client = clientResource.toRepresentation();
-        client.setRootUrl("http://localhost/base#someFragment");
-        List redirectUris = client.getRedirectUris();
-        redirectUris.add("http://localhost/auth#fragment");
-        redirectUris.add("/relative");
-        client.setRedirectUris(redirectUris);
-
-        try {
-            clientResource.update(client);
-            fail("Should fail");
-        }
-        catch (BadRequestException e) {
-            assertUriFragmentError(e.getResponse());
-        }
-    }
-
-    private void assertUriFragmentError(Response response) {
-        assertEquals(response.getStatus(), 400);
-        String error = response.readEntity(OAuth2ErrorRepresentation.class).getError();
-        assertTrue("Error response doesn't mention Redirect URIs fragments", error.contains("Redirect URIs must not contain an URI fragment"));
-        assertTrue("Error response doesn't mention Root URL fragments", error.contains("Root URL must not contain an URL fragment"));
-    }
-
     @Test
     public void pushRevocation() {
         testingClient.testApp().clearAdminActions();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java
index 96e922a76a9c..514dd7b14e25 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java
@@ -214,6 +214,11 @@ private List> expectedAuthProviders() {
         addProviderInfo(result, "set-attribute", "Set user attribute",
                 "Set a user attribute");
 
+        addProviderInfo(result, "set-level-of-authentication", "Set Level of Authentication",
+                "Set the Level of Authentication (LOA).");
+        addProviderInfo(result, "conditional-level-of-authentication", "Condition - Level of Authentication",
+                "Flow is executed only if the configured LOA or a higher one has been requested but not yet satisfied.");
+
         return result;
     }
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthorizationAPITest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthorizationAPITest.java
index c58b0572c44f..7784d99a028f 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthorizationAPITest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/AuthorizationAPITest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertNotNull;
 
 import java.io.IOException;
+import java.util.Collections;
 import java.util.List;
 
 import javax.ws.rs.core.Response;
@@ -44,6 +45,7 @@
 import org.keycloak.testsuite.Assert;
 import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
 import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
+import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls;
 import org.keycloak.testsuite.util.ClientBuilder;
 import org.keycloak.testsuite.util.OAuthClient;
 import org.keycloak.testsuite.util.RealmBuilder;
@@ -83,7 +85,7 @@ public void addTestRealms(List testRealms) {
                     .redirectUris("http://localhost/resource-server-test")
                     .defaultRoles("uma_protection")
                     .directAccessGrants()
-                    .pairwise("http://pairwise.com"))
+                    .pairwise(TestApplicationResourceUrls.pairwiseSectorIdentifierUri()))
                 .client(ClientBuilder.create().clientId(TEST_CLIENT)
                     .secret("secret")
                     .authorizationServicesEnabled(true)
@@ -95,6 +97,8 @@ public void addTestRealms(List testRealms) {
                         .redirectUris("http://localhost/test-client")
                         .directAccessGrants())
                 .build());
+
+        testingClient.testApp().oidcClientEndpoints().setSectorIdentifierRedirectUris(Collections.singletonList("http://localhost/resource-server-test"));
     }
 
     @Before
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java
index ce8665c46586..d4dcb1f9e95e 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/authz/EntitlementAPITest.java
@@ -93,6 +93,7 @@
 import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
 import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
 import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls;
 import org.keycloak.testsuite.util.ClientBuilder;
 import org.keycloak.testsuite.util.OAuthClient;
 import org.keycloak.testsuite.util.RealmBuilder;
@@ -142,7 +143,7 @@ public void addTestRealms(List testRealms) {
                         .authorizationServicesEnabled(true)
                         .redirectUris("http://localhost/resource-server-test")
                         .defaultRoles("uma_protection")
-                        .pairwise("http://pairwise.com")
+                        .pairwise(TestApplicationResourceUrls.pairwiseSectorIdentifierUri())
                         .directAccessGrants())
                 .client(ClientBuilder.create().clientId(TEST_CLIENT)
                         .secret("secret")
@@ -153,13 +154,19 @@ public void addTestRealms(List testRealms) {
                         .secret("secret")
                         .authorizationServicesEnabled(true)
                         .redirectUris("http://localhost/test-client")
-                        .pairwise("http://pairwise.com")
+                        .pairwise(TestApplicationResourceUrls.pairwiseSectorIdentifierUri())
                         .directAccessGrants())
                 .client(ClientBuilder.create().clientId(PUBLIC_TEST_CLIENT)
                         .secret("secret")
                         .redirectUris("http://localhost:8180/auth/realms/master/app/auth/*", "https://localhost:8543/auth/realms/master/app/auth/*")
                         .publicClient())
                 .build());
+
+        configureSectorIdentifierRedirectUris();
+    }
+
+    private void configureSectorIdentifierRedirectUris() {
+        testingClient.testApp().oidcClientEndpoints().setSectorIdentifierRedirectUris(Arrays.asList("http://localhost/resource-server-test", "http://localhost/test-client"));
     }
 
     @Before
@@ -1936,6 +1943,7 @@ public void testOfflineRequestingPartyToken() throws Exception {
         controller.stop(suiteContext.getAuthServerInfo().getQualifier());
         controller.start(suiteContext.getAuthServerInfo().getQualifier());
         reconnectAdminClient();
+        configureSectorIdentifierRedirectUris();
 
         TokenIntrospectionResponse introspectionResponse = authzClient.protection().introspectRequestingPartyToken(response.getToken());
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java
index 50f837e4db66..81ca0373c0e8 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java
@@ -28,14 +28,19 @@
 import org.keycloak.representations.idm.ProtocolMapperRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
-import org.keycloak.services.clientregistration.ErrorCodes;
+import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
 import org.keycloak.util.JsonSerialization;
 
 import javax.ws.rs.NotFoundException;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
 
+import static java.util.Arrays.asList;
+import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.nullValue;
 import static org.hamcrest.core.Is.is;
 import static org.junit.Assert.assertEquals;
@@ -43,7 +48,8 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.fail;
-import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
+import static org.keycloak.services.clientregistration.ErrorCodes.INVALID_CLIENT_METADATA;
+import static org.keycloak.services.clientregistration.ErrorCodes.INVALID_REDIRECT_URI;
 
 /**
  * @author Stian Thorgersen
@@ -161,45 +167,112 @@ public void registerClientWithNonAsciiChars() throws ClientRegistrationException
     }
 
     @Test
-    public void registerClientValidation() throws IOException {
-    	authCreateClients();
-    	ClientRepresentation client = buildClient();
-    	client.setRootUrl("invalid");
-
-    	try {
-            registerClient(client);
-        } catch (ClientRegistrationException e) {
-            HttpErrorException c = (HttpErrorException) e.getCause();
-            assertEquals(400, c.getStatusLine().getStatusCode());
-
-            OAuth2ErrorRepresentation error = JsonSerialization.readValue(c.getErrorResponse(), OAuth2ErrorRepresentation.class);
+    public void testInvalidUrlClientValidation() {
+        testClientUriValidation("Root URL is not a valid URL",
+                "Base URL is not a valid URL",
+                null,
+                "invalid", "myapp://some-fake-app");
+    }
 
-            assertEquals("invalid_client_metadata", error.getError());
-            assertEquals("Invalid URL in rootUrl", error.getErrorDescription());
-        }
+    @Test
+    public void testIllegalSchemeClientValidation() {
+        testClientUriValidation("Root URL uses an illegal scheme",
+                "Base URL uses an illegal scheme",
+                "A redirect URI uses an illegal scheme",
+                "data:text/html;base64,PHNjcmlwdD5jb25maXJtKGRvY3VtZW50LmRvbWFpbik7PC9zY3JpcHQ+",
+                "javascript:confirm(document.domain)/*"
+        );
     }
 
+    // KEYCLOAK-3421
     @Test
-    public void updateClientValidation() throws IOException, ClientRegistrationException {
-        registerClientAsAdmin();
+    public void testFragmentProhibitedClientValidation() {
+        testClientUriValidation("Root URL must not contain an URL fragment",
+                null,
+                "Redirect URIs must not contain an URI fragment",
+                "http://redhat.com/abcd#someFragment"
+        );
+    }
 
-        ClientRepresentation client = reg.get(CLIENT_ID);
-        client.setRootUrl("invalid");
+    private void testClientUriValidation(String expectedRootUrlError, String expectedBaseUrlError, String expectedRedirectUrisError, String... testUrls) {
+        testClientUriValidation(true, expectedRootUrlError, expectedBaseUrlError, expectedRedirectUrisError, testUrls);
+        testClientUriValidation(false, expectedRootUrlError, expectedBaseUrlError, expectedRedirectUrisError, testUrls);
+    }
 
-    	try {
-            reg.update(client);
-        } catch (ClientRegistrationException e) {
-            HttpErrorException c = (HttpErrorException) e.getCause();
-            assertEquals(400, c.getStatusLine().getStatusCode());
+    private void testClientUriValidation(boolean register, String expectedRootUrlError, String expectedBaseUrlError, String expectedRedirectUrisError, String... testUrls) {
+        ClientRepresentation rep;
+        if (register) {
+            authCreateClients();
+            rep = buildClient();
+        }
+        else {
+            try {
+                registerClientAsAdmin();
+                rep = reg.get(CLIENT_ID);
+            }
+            catch (ClientRegistrationException e) {
+                throw new RuntimeException(e);
+            }
+        }
 
-            OAuth2ErrorRepresentation error = JsonSerialization.readValue(c.getErrorResponse(), OAuth2ErrorRepresentation.class);
+        for (String testUrl : testUrls) {
+            if (expectedRootUrlError != null) {
+                rep.setRootUrl(testUrl);
+                registerOrUpdateClientExpectingValidationErrors(rep, register, false, expectedRootUrlError);
+            }
+            rep.setRootUrl(null);
+
+            if (expectedBaseUrlError != null) {
+                rep.setBaseUrl(testUrl);
+                registerOrUpdateClientExpectingValidationErrors(rep, register, false, expectedBaseUrlError);
+            }
+            rep.setBaseUrl(null);
+
+            if (expectedRedirectUrisError != null) {
+                rep.setRedirectUris(Collections.singletonList(testUrl));
+                registerOrUpdateClientExpectingValidationErrors(rep, register, true, expectedRedirectUrisError);
+            }
+            rep.setRedirectUris(null);
+
+            if (expectedRootUrlError != null) rep.setRootUrl(testUrl);
+            if (expectedBaseUrlError != null) rep.setBaseUrl(testUrl);
+            if (expectedRedirectUrisError != null) rep.setRedirectUris(Collections.singletonList(testUrl));
+            registerOrUpdateClientExpectingValidationErrors(rep, register, expectedRedirectUrisError != null, expectedRootUrlError, expectedBaseUrlError, expectedRedirectUrisError);
+
+            rep.setRootUrl(null);
+            rep.setBaseUrl(null);
+            rep.setRedirectUris(null);
+        }
+    }
 
-            assertEquals("invalid_client_metadata", error.getError());
-            assertEquals("Invalid URL in rootUrl", error.getErrorDescription());
+    private void registerOrUpdateClientExpectingValidationErrors(ClientRepresentation rep, boolean register, boolean redirectUris, String... expectedErrors) {
+        HttpErrorException errorException = null;
+        try {
+            if (register) {
+                registerClient(rep);
+            }
+            else {
+                reg.update(rep);
+            }
+            fail("Expected exception");
+        }
+        catch (ClientRegistrationException e) {
+            errorException = (HttpErrorException) e.getCause();
         }
 
-        ClientRepresentation updatedClient = reg.get(CLIENT_ID);
-        assertNull(updatedClient.getRootUrl());
+        expectedErrors = Arrays.stream(expectedErrors).filter(Objects::nonNull).toArray(String[]::new);
+
+        assertEquals(errorException.getStatusLine().getStatusCode(), 400);
+        OAuth2ErrorRepresentation errorRep;
+        try {
+            errorRep = JsonSerialization.readValue(errorException.getErrorResponse(), OAuth2ErrorRepresentation.class);
+        }
+        catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        List actualErrors = asList(errorRep.getErrorDescription().split("; "));
+        assertThat(actualErrors, containsInAnyOrder(expectedErrors));
+        assertEquals(redirectUris ? INVALID_REDIRECT_URI : INVALID_CLIENT_METADATA, errorRep.getError());
     }
 
     @Test
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/error/UncaughtErrorPageTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/error/UncaughtErrorPageTest.java
index 92454d2ec6df..7c8d4f07f0b5 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/error/UncaughtErrorPageTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/error/UncaughtErrorPageTest.java
@@ -1,9 +1,7 @@
 package org.keycloak.testsuite.error;
 
-import org.apache.commons.io.IOUtils;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpPost;
-import org.apache.http.client.methods.HttpRequestBase;
 import org.apache.http.entity.StringEntity;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClientBuilder;
@@ -14,7 +12,6 @@
 import org.keycloak.broker.provider.util.SimpleHttp;
 import org.keycloak.common.util.StreamUtil;
 import org.keycloak.models.BrowserSecurityHeaders;
-import org.keycloak.representations.idm.ErrorRepresentation;
 import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.testsuite.AbstractKeycloakTest;
@@ -31,7 +28,6 @@
 import java.nio.charset.Charset;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 
 import static org.junit.Assert.*;
 
@@ -126,14 +122,13 @@ public void uncaughtErrorHeaders() throws IOException {
         try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
             SimpleHttp.Response response = SimpleHttp.doGet(uri.toString(), client).header("Accept", MediaType.TEXT_HTML_UTF_8).asResponse();
 
-            for (Map.Entry e : BrowserSecurityHeaders.headerAttributeMap.entrySet()) {
-                String header = e.getValue();
-                String expectedValue = BrowserSecurityHeaders.defaultHeaders.get(e.getKey());
+            for (BrowserSecurityHeaders header : BrowserSecurityHeaders.values()) {
+                String expectedValue = header.getDefaultValue();
 
                 if (expectedValue == null || expectedValue.isEmpty()) {
-                    assertNull(response.getFirstHeader(header));
+                    assertNull(response.getFirstHeader(header.getHeaderName()));
                 } else {
-                    assertEquals(expectedValue, response.getFirstHeader(header));
+                    assertEquals(expectedValue, response.getFirstHeader(header.getHeaderName()));
                 }
             }
         }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPGroupMapperSyncWithGroupsPathTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPGroupMapperSyncWithGroupsPathTest.java
index ae8f6bcd0ca3..20c6bcbfaf35 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPGroupMapperSyncWithGroupsPathTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPGroupMapperSyncWithGroupsPathTest.java
@@ -17,6 +17,8 @@
 
 package org.keycloak.testsuite.federation.ldap;
 
+import javax.ws.rs.core.Response;
+
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.ClassRule;
@@ -28,6 +30,7 @@
 import org.keycloak.models.LDAPConstants;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.representations.idm.ComponentRepresentation;
 import org.keycloak.storage.ldap.LDAPStorageProvider;
 import org.keycloak.storage.ldap.LDAPUtils;
 import org.keycloak.storage.ldap.idm.model.LDAPObject;
@@ -37,6 +40,7 @@
 import org.keycloak.storage.ldap.mappers.membership.group.GroupLDAPStorageMapperFactory;
 import org.keycloak.storage.ldap.mappers.membership.group.GroupMapperConfig;
 import org.keycloak.storage.user.SynchronizationResult;
+import org.keycloak.testsuite.admin.ApiUtil;
 import org.keycloak.testsuite.util.LDAPRule;
 import org.keycloak.testsuite.util.LDAPTestUtils;
 
@@ -206,4 +210,30 @@ public void test02_syncWithDropNonExistingGroups() throws Exception {
             Assert.assertNotNull(KeycloakModelUtils.findGroupByPath(realm, "/outside"));
         });
     }
+
+    // KEYCLOAK-14892
+    @Test
+    public void test03_createConfigurationWithoutGroupPath() throws Exception {
+        ComponentRepresentation groupMapperRep = findMapperRepByName("groupsMapper");
+
+        groupMapperRep.setId(null);
+        groupMapperRep.setName("different");
+        groupMapperRep.getConfig().remove(GroupMapperConfig.LDAP_GROUPS_PATH);
+        groupMapperRep.getConfig().remove(GroupMapperConfig.LDAP_GROUPS_PATH);
+
+        // Test that attempt to create configuration without LDAP_GROUPS_PATH configured should be still allowed due the backwards compatibility
+        Response response = adminClient.realm("test").components().add(groupMapperRep);
+        String newMapperId = ApiUtil.getCreatedId(response);
+
+        try {
+            response.close();
+
+            // Load the mapper and assert default group path set
+            ComponentRepresentation newMapper = adminClient.realm("test").components().component(newMapperId).toRepresentation();
+            Assert.assertEquals(GroupMapperConfig.DEFAULT_LDAP_GROUPS_PATH, newMapper.getConfig().getFirst(GroupMapperConfig.LDAP_GROUPS_PATH));
+        } finally {
+            // revert new mapper
+            adminClient.realm("test").components().component(newMapperId).remove();
+        }
+    }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPProvidersFullNameMapperTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPProvidersFullNameMapperTest.java
new file mode 100644
index 000000000000..b561491a56e4
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPProvidersFullNameMapperTest.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2017 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.federation.ldap;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.storage.ldap.LDAPStorageProvider;
+import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapper;
+import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapperFactory;
+import org.keycloak.storage.ldap.mappers.LDAPStorageMapper;
+import org.keycloak.storage.ldap.mappers.UserAttributeLDAPStorageMapper;
+import org.keycloak.testsuite.util.LDAPRule;
+import org.keycloak.testsuite.util.LDAPTestUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Marek Posolda
+ * @author Martin Idel
+ */
+public class LDAPProvidersFullNameMapperTest extends AbstractLDAPTest {
+
+    @ClassRule
+    public static LDAPRule ldapRule = new LDAPRule();
+
+    @Override
+    protected LDAPRule getLDAPRule() {
+        return ldapRule;
+    }
+
+    @Override
+    protected void afterImportTestRealm() {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            LDAPTestUtils.addZipCodeLDAPMapper(appRealm, ctx.getLdapModel());
+            LDAPTestUtils.removeAllLDAPUsers(ctx.getLdapProvider(), appRealm);
+
+            appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true);
+
+            // assert that user "fullnameUser" is not in local DB
+            Assert.assertNull(session.users().getUserByUsername("fullname", appRealm));
+
+            // Add the user with some fullName into LDAP directly. Ensure that fullName is saved into "cn" attribute in LDAP (currently mapped to model firstName)
+            ComponentModel ldapModel = LDAPTestUtils.getLdapProviderModel(session, appRealm);
+
+            // add fullname mapper to the provider and remove "firstNameMapper". For this test, we will simply map full name to the LDAP attribute, which was before firstName ( "givenName" on active directory, "cn" on other LDAP servers)
+            ComponentModel firstNameMapper = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "first name");
+            String ldapFirstNameAttributeName = firstNameMapper.getConfig().getFirst(UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE);
+            appRealm.removeComponent(firstNameMapper);
+
+            ComponentModel fullNameMapperModel = KeycloakModelUtils.createComponentModel("full name", ldapModel.getId(), FullNameLDAPStorageMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(),
+                    FullNameLDAPStorageMapper.LDAP_FULL_NAME_ATTRIBUTE, ldapFirstNameAttributeName,
+                    FullNameLDAPStorageMapper.READ_ONLY, "false");
+            appRealm.addComponentModel(fullNameMapperModel);
+        });
+    }
+
+    @Test
+    public void testUpdatingFirstNameAndLastNamePropagatesToFullnameMapper() {
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            ComponentModel ldapModel = LDAPTestUtils.getLdapProviderModel(session, appRealm);
+            LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+            LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "fullname", "James", "Dee", "fullname@email.org", null, "4578");
+
+            // Assert user is successfully imported in Keycloak DB now with correct firstName and lastName
+            LDAPTestAsserts.assertUserImported(session.users(), appRealm, "fullname", "James", "Dee", "fullname@email.org", "4578");
+        });
+
+        // Assert user will be changed in LDAP too
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            UserModel fullnameUser = session.users().getUserByUsername("fullname", appRealm);
+            fullnameUser.setFirstName("James2");
+            fullnameUser.setLastName("Dee2");
+        });
+
+        // Assert changed user available in Keycloak
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            // Assert user is successfully imported in Keycloak DB now with correct firstName and lastName
+            LDAPTestAsserts.assertUserImported(session.users(), appRealm, "fullname", "James2", "Dee2", "fullname@email.org", "4578");
+
+            // Remove "fullnameUser" to assert he is removed from LDAP.
+            UserModel fullnameUser = session.users().getUserByUsername("fullname", appRealm);
+            session.users().removeUser(appRealm, fullnameUser);
+        });
+    }
+
+    @Test
+    public void testUpdatingAttributesWorksEvenWithEmptyAttributes() {
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            ComponentModel ldapModel = LDAPTestUtils.getLdapProviderModel(session, appRealm);
+            LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+            LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "fullname", "James", "Dee", "fullname@email.org", null, "4578");
+
+            LDAPTestAsserts.assertUserImported(session.users(), appRealm, "fullname", "James", "Dee", "fullname@email.org", "4578");
+        });
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            UserModel fullnameUser = session.users().getUserByUsername("fullname", appRealm);
+            fullnameUser.setAttribute("myAttribute", Collections.singletonList("test"));
+            fullnameUser.setAttribute("myEmptyAttribute", new ArrayList<>());
+            fullnameUser.setAttribute("myNullAttribute", null);
+        });
+
+        // Assert changed user available in Keycloak
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            // Assert user is successfully imported in Keycloak DB now with correct firstName and lastName
+            LDAPTestAsserts.assertUserImported(session.users(), appRealm, "fullname", "James", "Dee", "fullname@email.org", "4578");
+
+            UserModel fullnameUser = session.users().getUserByUsername("fullname", appRealm);
+            assertThat(fullnameUser.getAttribute("myAttribute"), contains("test"));
+            assertThat(fullnameUser.getAttribute("myEmptyAttribute"), is(empty()));
+            assertThat(fullnameUser.getAttribute("myNullAttribute"), is(empty()));
+
+            // Remove "fullnameUser" to assert he is removed from LDAP.
+            session.users().removeUser(appRealm, fullnameUser);
+        });
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPProvidersIntegrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPProvidersIntegrationTest.java
index f5059e48f027..67b1c682f69a 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPProvidersIntegrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPProvidersIntegrationTest.java
@@ -36,10 +36,10 @@
 import org.keycloak.models.cache.CachedUserModel;
 import org.keycloak.models.credential.PasswordCredentialModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
-import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.idm.ComponentRepresentation;
 import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.storage.ReadOnlyException;
@@ -49,8 +49,6 @@
 import org.keycloak.storage.ldap.LDAPConfig;
 import org.keycloak.storage.ldap.LDAPStorageProvider;
 import org.keycloak.storage.ldap.idm.model.LDAPObject;
-import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapper;
-import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapperFactory;
 import org.keycloak.storage.ldap.mappers.HardcodedLDAPAttributeMapper;
 import org.keycloak.storage.ldap.mappers.HardcodedLDAPAttributeMapperFactory;
 import org.keycloak.storage.ldap.mappers.HardcodedLDAPGroupStorageMapper;
@@ -68,7 +66,6 @@
 
 import javax.naming.AuthenticationException;
 import javax.ws.rs.core.Response;
-
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
@@ -554,80 +551,6 @@ public void testCommaInUsername() {
     }
 
 
-    // TODO: Rather separate test class for fullNameMapper to better test all the possibilities
-    @Test
-    public void testFullNameMapper() {
-
-        ComponentRepresentation firstNameMapperRep = testingClient.server().fetch(session -> {
-            LDAPTestContext ctx = LDAPTestContext.init(session);
-            RealmModel appRealm = ctx.getRealm();
-
-            // assert that user "fullnameUser" is not in local DB
-            Assert.assertNull(session.users().getUserByUsername("fullname", appRealm));
-
-            // Add the user with some fullName into LDAP directly. Ensure that fullName is saved into "cn" attribute in LDAP (currently mapped to model firstName)
-            ComponentModel ldapModel = LDAPTestUtils.getLdapProviderModel(session, appRealm);
-            LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
-            LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "fullname", "James Dee", "Dee", "fullname@email.org", null, "4578");
-
-            // add fullname mapper to the provider and remove "firstNameMapper". For this test, we will simply map full name to the LDAP attribute, which was before firstName ( "givenName" on active directory, "cn" on other LDAP servers)
-            ComponentModel firstNameMapper =  LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "first name");
-            String ldapFirstNameAttributeName = firstNameMapper.getConfig().getFirst(UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE);
-            appRealm.removeComponent(firstNameMapper);
-
-            ComponentRepresentation firstNameMapperRepp = ModelToRepresentation.toRepresentation(session, firstNameMapper, true);
-
-            ComponentModel fullNameMapperModel = KeycloakModelUtils.createComponentModel("full name", ldapModel.getId(), FullNameLDAPStorageMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(),
-                    FullNameLDAPStorageMapper.LDAP_FULL_NAME_ATTRIBUTE, ldapFirstNameAttributeName,
-                    FullNameLDAPStorageMapper.READ_ONLY, "false");
-            appRealm.addComponentModel(fullNameMapperModel);
-
-            return firstNameMapperRepp;
-        }, ComponentRepresentation.class);
-
-        testingClient.server().run(session -> {
-            LDAPTestContext ctx = LDAPTestContext.init(session);
-            RealmModel appRealm = ctx.getRealm();
-
-            // Assert user is successfully imported in Keycloak DB now with correct firstName and lastName
-            LDAPTestAsserts.assertUserImported(session.users(), appRealm, "fullname", "James", "Dee", "fullname@email.org", "4578");
-        });
-
-        // Assert user will be changed in LDAP too
-        testingClient.server().run(session -> {
-            LDAPTestContext ctx = LDAPTestContext.init(session);
-            RealmModel appRealm = ctx.getRealm();
-
-            UserModel fullnameUser = session.users().getUserByUsername("fullname", appRealm);
-            fullnameUser.setFirstName("James2");
-            fullnameUser.setLastName("Dee2");
-        });
-
-
-        // Assert changed user available in Keycloak
-        testingClient.server().run(session -> {
-            LDAPTestContext ctx = LDAPTestContext.init(session);
-            RealmModel appRealm = ctx.getRealm();
-
-            // Assert user is successfully imported in Keycloak DB now with correct firstName and lastName
-            LDAPTestAsserts.assertUserImported(session.users(), appRealm, "fullname", "James2", "Dee2", "fullname@email.org", "4578");
-
-            // Remove "fullnameUser" to assert he is removed from LDAP. Revert mappers to previous state
-            UserModel fullnameUser = session.users().getUserByUsername("fullname", appRealm);
-            session.users().removeUser(appRealm, fullnameUser);
-
-            // Revert mappers
-            ComponentModel fullNameMapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ctx.getLdapModel(), "full name");
-            appRealm.removeComponent(fullNameMapperModel);
-        });
-
-        firstNameMapperRep.setId(null);
-        Response response = testRealm().components().add(firstNameMapperRep);
-        Assert.assertEquals(201, response.getStatus());
-        response.close();
-    }
-
-
     @Test
     public void testHardcodedAttributeMapperTest() throws Exception {
         // Create hardcoded mapper for "description"
@@ -1221,4 +1144,27 @@ public void testEmailVerifiedFromImport(){
             Assert.assertFalse(userNotVerified.get(0).isEmailVerified());
         });
     }
+
+    @Test
+    public void testUserAttributeLDAPStorageMapperHandlingUsernameLowercasing() {
+        setEditingUsernameAllowed(false);
+
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            UserModel johnkeycloak = session.users().getUserByUsername("johnkeycloak", appRealm);
+            // If the username was case sensitive in the username-cn mapper, then this would throw an exception
+            johnkeycloak.setSingleAttribute(UserModel.USERNAME, "JohnKeycloak");
+        });
+
+        // Cleanup
+        setEditingUsernameAllowed(true);
+    }
+
+    private void setEditingUsernameAllowed(boolean allowed) {
+        RealmRepresentation realmRepresentation = testRealm().toRepresentation();
+        realmRepresentation.setEditUsernameAllowed(allowed);
+        testRealm().update(realmRepresentation);
+    }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPReadOnlyTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPReadOnlyTest.java
new file mode 100644
index 000000000000..a56d1842c652
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPReadOnlyTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2020 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.federation.ldap;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.credential.OTPCredentialModel;
+import org.keycloak.models.utils.TimeBasedOTP;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.storage.ReadOnlyException;
+import org.keycloak.storage.StorageId;
+import org.keycloak.storage.UserStorageProvider;
+import org.keycloak.storage.ldap.LDAPStorageProvider;
+import org.keycloak.storage.ldap.idm.model.LDAPObject;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.LoginConfigTotpPage;
+import org.keycloak.testsuite.util.LDAPRule;
+import org.keycloak.testsuite.util.LDAPTestUtils;
+import org.openqa.selenium.By;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test for more advanced scenarios related to LDAP read-only mode
+ *
+ * @author Marek Posolda
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class LDAPReadOnlyTest extends AbstractLDAPTest  {
+
+    @ClassRule
+    public static LDAPRule ldapRule = new LDAPRule();
+
+    @Override
+    protected LDAPRule getLDAPRule() {
+        return ldapRule;
+    }
+
+    @Page
+    protected LoginConfigTotpPage totpPage;
+
+    private TimeBasedOTP totp = new TimeBasedOTP();
+
+    @Override
+    protected void afterImportTestRealm() {
+        testingClient.server().run(session -> {
+            LDAPTestContext ctx = LDAPTestContext.init(session);
+            RealmModel appRealm = ctx.getRealm();
+
+            LDAPTestUtils.addZipCodeLDAPMapper(appRealm, ctx.getLdapModel());
+
+            // Delete all LDAP users and add some new for testing
+            LDAPTestUtils.removeAllLDAPUsers(ctx.getLdapProvider(), appRealm);
+
+            LDAPObject john = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234");
+            LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), john, "Password1");
+
+            LDAPObject existing = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "existing", "Existing", "Foo", "existing@email.org", null, "5678");
+
+            appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true);
+
+            LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ctx.getLdapModel());
+            ldapFedProvider.getModel().put(LDAPConstants.EDIT_MODE, UserStorageProvider.EditMode.READ_ONLY.toString());
+            appRealm.updateComponent(ldapFedProvider.getModel());
+        });
+    }
+
+
+    // KEYCLOAK-15139
+    @Test
+    public void testReadOnlyWithTOTPEnabled() {
+        // Set TOTP required
+        setTotpRequirementExecutionForRealm(AuthenticationExecutionModel.Requirement.REQUIRED);
+
+        // Authenticate as the LDAP user and assert it works
+        loginPage.open();
+        loginPage.login("johnkeycloak", "Password1");
+
+        assertTrue(totpPage.isCurrent());
+        assertFalse(totpPage.isCancelDisplayed());
+
+        // KEYCLOAK-11753 - Verify OTP label element present on "Configure OTP" required action form
+        driver.findElement(By.id("userLabel"));
+
+        totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()));
+
+        Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+        Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+
+        // Revert TOTP
+        setTotpRequirementExecutionForRealm(AuthenticationExecutionModel.Requirement.CONDITIONAL);
+        UserResource user = ApiUtil.findUserByUsernameId(testRealm(), "johnkeycloak");
+        String totpCredentialId = user.credentials().stream()
+                .filter(credentialRep -> credentialRep.getType().equals(OTPCredentialModel.TYPE))
+                .findFirst().get().getId();
+        user.removeCredential(totpCredentialId);
+    }
+
+    private void setTotpRequirementExecutionForRealm(AuthenticationExecutionModel.Requirement requirement) {
+        adminClient.realm("test").flows().getExecutions("browser").
+                stream().filter(execution -> execution.getDisplayName().equals("Browser - Conditional OTP"))
+                .forEach(execution ->
+                {execution.setRequirement(requirement.name());
+                    adminClient.realm("test").flows().updateExecutions("browser", execution);});
+    }
+
+
+    protected void assertFederatedUserLink(UserRepresentation user) {
+        Assert.assertTrue(StorageId.isLocalStorage(user.getId()));
+        Assert.assertNotNull(user.getFederationLink());
+        Assert.assertEquals(user.getFederationLink(), ldapModelId);
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
index 57bdf8d9b2b6..2871a3206006 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
@@ -67,7 +67,6 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import org.apache.commons.lang3.RandomStringUtils;
 
@@ -163,14 +162,14 @@ public void testBrowserSecurityHeaders() {
         Client client = ClientBuilder.newClient();
         Response response = client.target(oauth.getLoginFormUrl()).request().get();
         Assert.assertThat(response.getStatus(), is(equalTo(200)));
-        for (Map.Entry entry : BrowserSecurityHeaders.defaultHeaders.entrySet()) {
-            String headerName = BrowserSecurityHeaders.headerAttributeMap.get(entry.getKey());
-            String headerValue = response.getHeaderString(headerName);
-            if (entry.getValue().isEmpty()) {
+        for (BrowserSecurityHeaders header : BrowserSecurityHeaders.values()) {
+            String headerValue = response.getHeaderString(header.getHeaderName());
+            String expectedValue = header.getDefaultValue();
+            if (expectedValue.isEmpty()) {
                 Assert.assertNull(headerValue);
             } else {
                 Assert.assertNotNull(headerValue);
-                Assert.assertThat(headerValue, is(equalTo(entry.getValue())));
+                Assert.assertThat(headerValue, is(equalTo(expectedValue)));
             }
         }
         response.close();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetCredentialsAlternativeFlowsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetCredentialsAlternativeFlowsTest.java
index dbb4b510469d..50d643c3b012 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetCredentialsAlternativeFlowsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/ResetCredentialsAlternativeFlowsTest.java
@@ -18,6 +18,7 @@
 
 package org.keycloak.testsuite.forms;
 
+import org.hamcrest.Matchers;
 import org.jboss.arquillian.graphene.page.Page;
 import org.junit.Assert;
 import org.junit.Before;
@@ -52,13 +53,17 @@
 import org.keycloak.testsuite.util.MailUtils;
 import org.keycloak.testsuite.util.URLUtils;
 import org.keycloak.testsuite.util.UserBuilder;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
 
 import javax.mail.internet.MimeMessage;
 import java.util.Arrays;
 import java.util.List;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
 import static org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer.REMOTE;
 
 /**
@@ -434,8 +439,8 @@ public void deviceNameOptionalForFirstOTPCredentialButRequiredForEachNextOne() {
             pageSource = driver.getPageSource();
 
             // Check if OTP credential with empty label was created successfully
-            final String emptyOtpLabelPresentInAuthenticatorTable = "(?s)";
-            Assert.assertTrue(Pattern.compile(emptyOtpLabelPresentInAuthenticatorTable).matcher(pageSource).find());
+            assertThat(driver.findElements(By.className("provider")).stream()
+                    .map(WebElement::getText).collect(Collectors.toList()), Matchers.hasItem(""));
             accountTotpPage.removeTotp();
 
             // Logout
@@ -473,11 +478,9 @@ public void deviceNameOptionalForFirstOTPCredentialButRequiredForEachNextOne() {
             accountTotpPage.open();
             Assert.assertTrue(accountTotpPage.isCurrent());
 
-            // Get the updated Account TOTP page source post OTP credential creation
-            pageSource = driver.getPageSource();
-
             // Check if OTP credential with empty label was created successfully
-            Assert.assertTrue(Pattern.compile(emptyOtpLabelPresentInAuthenticatorTable).matcher(pageSource).find());
+            assertThat(driver.findElements(By.className("provider")).stream()
+                    .map(WebElement::getText).collect(Collectors.toList()), Matchers.hasItem(""));;
 
             // Logout
             oauth.openLogout();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/javascript/JavascriptAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/javascript/JavascriptAdapterTest.java
index 28729ecb0474..2471a1bad48a 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/javascript/JavascriptAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/javascript/JavascriptAdapterTest.java
@@ -18,6 +18,8 @@
 import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.admin.ApiUtil;
 import org.keycloak.testsuite.arquillian.SuiteContext;
+import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
+import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
 import org.keycloak.testsuite.auth.page.account.Applications;
 import org.keycloak.testsuite.auth.page.login.OAuthGrant;
 import org.keycloak.testsuite.auth.page.login.UpdatePassword;
@@ -47,16 +49,11 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
-import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
-
-import static org.junit.Assume.assumeFalse;
+import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_HOST;
 import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlDoesntStartWith;
 import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
 import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
 import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
-import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_HOST;
 
 /**
  * @author mhajas
@@ -65,6 +62,7 @@
 public class JavascriptAdapterTest extends AbstractJavascriptTest {
 
     private String testAppUrl;
+    private String testAppWithInitInHeadUrl;
     protected JavascriptTestExecutor testExecutor;
     private static int TIME_SKEW_TOLERANCE = 3;
 
@@ -90,7 +88,9 @@ protected RealmRepresentation updateRealm(RealmBuilder builder) {
 
     @Before
     public void setDefaultEnvironment() {
-        testAppUrl = authServerContextRootPage.toString().replace(AUTH_SERVER_HOST, JS_APP_HOST) + JAVASCRIPT_URL + "/index.html";
+        String testAppRootUrl = authServerContextRootPage.toString().replace(AUTH_SERVER_HOST, JS_APP_HOST) + JAVASCRIPT_URL;
+        testAppUrl = testAppRootUrl + "/index.html";
+        testAppWithInitInHeadUrl = testAppRootUrl + "/init-in-head.html";
 
         jsDriverTestRealmLoginPage.setAuthRealm(REALM_NAME);
         oAuthGrantPage.setAuthRealm(REALM_NAME);
@@ -101,10 +101,8 @@ public void setDefaultEnvironment() {
         events.poll();
         jsDriver.manage().deleteAllCookies();
 
-        jsDriver.navigate().to(testAppUrl);
+        navigateToTestApp(testAppUrl);
 
-        waitUntilElement(outputArea).is().present();
-        assertCurrentUrlStartsWith(testAppUrl, jsDriver);
         testExecutor = JavascriptTestExecutor.create(jsDriver, jsDriverTestRealmLoginPage);
 
         jsDriver.manage().deleteAllCookies();
@@ -121,6 +119,14 @@ protected JSObjectBuilder defaultArguments() {
     }
 
     private void assertOnTestAppUrl(WebDriver jsDriver, Object output, WebElement events) {
+        assertOnTestAppUrl(jsDriver, output, events, testAppUrl);
+    }
+
+    private void assertOnTestAppWithInitInHeadUrl(WebDriver jsDriver, Object output, WebElement events) {
+        assertOnTestAppUrl(jsDriver, output, events, testAppWithInitInHeadUrl);
+    }
+
+    private void assertOnTestAppUrl(WebDriver jsDriver, Object output, WebElement events, String testAppUrl) {
         waitForPageToLoad();
         assertCurrentUrlStartsWith(testAppUrl, jsDriver);
     }
@@ -748,7 +754,24 @@ public void testAIAFromJavascriptAdapterCancelled() {
         });
     }
 
+    @Test
+    // KEYCLOAK-15158
+    public void testInitInHead() {
+        navigateToTestApp(testAppWithInitInHeadUrl);
+
+        testExecutor.validateOutputField(this::assertInitNotAuth)
+                .login(this::assertOnLoginPage)
+                .loginForm(testUser, this::assertOnTestAppWithInitInHeadUrl)
+                .validateOutputField(this::assertInitAuth);
+    }
+
     protected void assertAdapterIsLoggedIn(WebDriver driver1, Object output, WebElement events) {
         assertTrue(testExecutor.isLoggedIn());
     }
+
+    protected void navigateToTestApp(final String testAppUrl) {
+        jsDriver.navigate().to(testAppUrl);
+        waitUntilElement(outputArea).is().present();
+        assertCurrentUrlStartsWith(testAppUrl, jsDriver);
+    }
 }
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/BadRealmTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/BadRealmTest.java
new file mode 100644
index 000000000000..76b1f963b471
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/model/BadRealmTest.java
@@ -0,0 +1,42 @@
+package org.keycloak.testsuite.model;
+
+import org.junit.Test;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.AbstractKeycloakTest;
+import org.keycloak.testsuite.arquillian.annotation.ModelTest;
+import org.keycloak.utils.ReservedCharValidator;
+
+import java.util.List;
+
+import static org.junit.Assert.fail;
+
+public class BadRealmTest extends AbstractKeycloakTest {
+    private String name = "MyRealm";
+    private String id = "MyId";
+    private String script = "";
+
+    public void addTestRealms(List testRealms) {
+    }
+
+    @Test
+    @ModelTest
+    public void testBadRealmName(KeycloakSession session) {
+        RealmManager manager = new RealmManager(session);
+        try {
+            manager.createRealm(id, name + script);
+            fail();
+        } catch (ReservedCharValidator.ReservedCharException ex) {}
+    }
+
+    @Test
+    @ModelTest
+    public void testBadRealmId(KeycloakSession session) {
+        RealmManager manager = new RealmManager(session);
+        try {
+            manager.createRealm(id + script, name);
+            fail();
+        } catch (ReservedCharValidator.ReservedCharException ex) {}
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LoginStatusIframeEndpointTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LoginStatusIframeEndpointTest.java
index c9f7e464b63a..32a05d75c9b4 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LoginStatusIframeEndpointTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/LoginStatusIframeEndpointTest.java
@@ -130,9 +130,9 @@ public void checkIframe() throws IOException {
             assertTrue(s.contains("function getCookie()"));
 
             assertEquals("CP=\"This is not a P3P policy!\"", response.getFirstHeader("P3P").getValue());
-            assertNull(response.getFirstHeader(BrowserSecurityHeaders.X_FRAME_OPTIONS));
-            assertEquals("frame-src 'self'; object-src 'none';", response.getFirstHeader(BrowserSecurityHeaders.CONTENT_SECURITY_POLICY).getValue());
-            assertEquals("none", response.getFirstHeader(BrowserSecurityHeaders.X_ROBOTS_TAG).getValue());
+            assertNull(response.getFirstHeader(BrowserSecurityHeaders.X_FRAME_OPTIONS.getHeaderName()));
+            assertEquals("frame-src 'self'; object-src 'none';", response.getFirstHeader(BrowserSecurityHeaders.CONTENT_SECURITY_POLICY.getHeaderName()).getValue());
+            assertEquals("none", response.getFirstHeader(BrowserSecurityHeaders.X_ROBOTS_TAG.getHeaderName()).getValue());
 
             response.close();
 
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/SamlXMLAttacksTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/SamlXMLAttacksTest.java
new file mode 100644
index 000000000000..a7b50a120030
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/saml/SamlXMLAttacksTest.java
@@ -0,0 +1,67 @@
+package org.keycloak.testsuite.saml;
+
+import org.apache.http.NameValuePair;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.message.BasicNameValuePair;
+import org.junit.Test;
+import org.keycloak.saml.common.constants.GeneralConstants;
+import org.keycloak.saml.processing.web.util.PostBindingUtil;
+
+import java.io.UnsupportedEncodingException;
+import java.util.LinkedList;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
+import static org.keycloak.testsuite.util.Matchers.bodyHC;
+
+public class SamlXMLAttacksTest extends AbstractSamlTest {
+
+    @Test(timeout = 4000)
+    public void testXMLBombAttackResistance() throws Exception {
+
+        String bombDoctype = "" +
+                "" +
+                "" +
+                "" +
+                "" +
+                "" +
+                "" +
+                "" +
+                "" +
+                "" +
+                "" +
+                "]>";
+        
+        String samlAuthnRequest = "" +
+                "" + SAML_CLIENT_ID_SALES_POST + "&lol9;" +
+                "";
+
+        try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
+            HttpPost post = new HttpPost(getAuthServerSamlEndpoint(REALM_NAME));
+
+            List parameters = new LinkedList<>();
+            String encoded = PostBindingUtil.base64Encode(bombDoctype + samlAuthnRequest);
+            parameters.add(new BasicNameValuePair(GeneralConstants.SAML_REQUEST_KEY, encoded));
+
+            UrlEncodedFormEntity formEntity;
+            try {
+                formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
+            } catch (UnsupportedEncodingException e) {
+                throw new RuntimeException(e);
+            }
+
+            post.setEntity(formEntity);
+            
+            try (CloseableHttpResponse response = client.execute(post)) {
+                assertThat(response, bodyHC(containsString("Invalid Request")));
+            }
+        }
+    }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/url/DefaultHostnameTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/url/DefaultHostnameTest.java
index 00deb766723c..c60a0aafd4f1 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/url/DefaultHostnameTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/url/DefaultHostnameTest.java
@@ -255,7 +255,7 @@ private void assertAdminPage(String realm, String expectedFrontendUrl, String ex
             assertTrue(indexPage.contains("consoleBaseUrl = '" + new URI(expectedAdminUrl).getPath() +"/admin/" + realm + "/console/'"));
             assertTrue(indexPage.contains("resourceUrl = '" + new URI(expectedAdminUrl).getPath() +"/resources/"));
 
-            String cspHeader = response.getFirstHeader(BrowserSecurityHeaders.CONTENT_SECURITY_POLICY);
+            String cspHeader = response.getFirstHeader(BrowserSecurityHeaders.CONTENT_SECURITY_POLICY.getHeaderName());
 
             if (expectedFrontendUrl.equalsIgnoreCase(expectedAdminUrl)) {
                 assertEquals("frame-src 'self'; frame-ancestors 'self'; object-src 'none';", cspHeader);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json
index 6af989d114a6..5f331ea7448b 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json
@@ -151,6 +151,17 @@
             "secret": "password",
             "directAccessGrantsEnabled": true
         },
+        {
+            "clientId": "serialization-servlet",
+            "enabled": true,
+            "adminUrl": "/serialization-servlet",
+            "baseUrl": "/serialization-servlet",
+            "redirectUris": [
+                "/serialization-servlet/*"
+            ],
+            "secret": "password",
+            "directAccessGrantsEnabled": true
+        },
         {
             "clientId": "customer-portal-subsystem",
             "enabled": true,
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/serialization-servlet/WEB-INF/keycloak.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/serialization-servlet/WEB-INF/keycloak.json
new file mode 100644
index 000000000000..d861ea9e661a
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/serialization-servlet/WEB-INF/keycloak.json
@@ -0,0 +1,10 @@
+{
+    "realm": "demo",
+    "resource": "serialization-servlet",
+    "auth-server-url": "http://localhost:8180/auth",
+    "ssl-required" : "external",
+    "expose-token": true,
+    "credentials": {
+        "secret": "password"
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/serialization-servlet/WEB-INF/web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/serialization-servlet/WEB-INF/web.xml
new file mode 100644
index 000000000000..e9b6913cf3cf
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/serialization-servlet/WEB-INF/web.xml
@@ -0,0 +1,76 @@
+
+
+
+
+
+    serialization-servlet
+
+    
+        Servlet
+        org.keycloak.testsuite.adapter.servlet.SerializationServlet
+    
+    
+        Error Servlet
+        org.keycloak.testsuite.adapter.servlet.ErrorServlet
+    
+
+    
+        Servlet
+        /*
+    
+
+    
+        Error Servlet
+        /error.html
+    
+
+    
+        
+            Users
+            /*
+        
+        
+            user
+        
+    
+    
+        
+            Errors
+            /error.html
+        
+    
+
+    
+        KEYCLOAK
+        demo
+        
+            /error.html
+            /error.html
+        
+    
+
+    
+        admin
+    
+    
+        user
+    
+
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/jboss/pom.xml
index cfed2a3b8c19..b2ebfde10141 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/pom.xml
@@ -24,7 +24,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-tests-adapters
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     integration-arquillian-tests-adapters-jboss
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/pom.xml
index 788bd858dbd0..cd2450628fb8 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/pom.xml
@@ -24,7 +24,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-tests-adapters-jboss-relative
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     integration-arquillian-tests-adapters-relative-eap
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/pom.xml
index a5353b02e598..75ab2c4d74b3 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/pom.xml
@@ -24,7 +24,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-tests-adapters-jboss
-        11.0.0-SNAPSHOT
+        11.0.3
     
     
     pom
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/wildfly/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/wildfly/pom.xml
index 1b9f67df4d78..17ab2795015d 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/wildfly/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/wildfly/pom.xml
@@ -24,7 +24,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-tests-adapters-jboss-relative
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     integration-arquillian-tests-adapters-relative-wildfly
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/pom.xml
index eb69d87b8fea..c04e8638a019 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/pom.xml
@@ -24,7 +24,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-tests-adapters-jboss
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     integration-arquillian-tests-adapters-remote
diff --git a/testsuite/integration-arquillian/tests/other/adapters/karaf/fuse61/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/karaf/fuse61/pom.xml
index 9721466d3532..ff3337b97f96 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/karaf/fuse61/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/karaf/fuse61/pom.xml
@@ -24,7 +24,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-tests-adapters-karaf
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     integration-arquillian-tests-adapters-fuse61
diff --git a/testsuite/integration-arquillian/tests/other/adapters/karaf/fuse62/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/karaf/fuse62/pom.xml
index 79b9aebb1165..3d48db225e20 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/karaf/fuse62/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/karaf/fuse62/pom.xml
@@ -24,7 +24,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-tests-adapters-karaf
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     integration-arquillian-tests-adapters-fuse62
diff --git a/testsuite/integration-arquillian/tests/other/adapters/karaf/karaf3/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/karaf/karaf3/pom.xml
index 4f4c9cd77485..cb275536699a 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/karaf/karaf3/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/karaf/karaf3/pom.xml
@@ -24,7 +24,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-tests-adapters-karaf
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     integration-arquillian-tests-adapters-karaf3
diff --git a/testsuite/integration-arquillian/tests/other/adapters/karaf/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/karaf/pom.xml
index 62a19faf1982..260097307107 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/karaf/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/karaf/pom.xml
@@ -24,7 +24,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-tests-adapters
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     integration-arquillian-tests-adapters-karaf
diff --git a/testsuite/integration-arquillian/tests/other/adapters/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/pom.xml
index 6d1b60acfe20..ec948f4dd70a 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/pom.xml
@@ -24,7 +24,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-tests-other
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     integration-arquillian-tests-adapters
diff --git a/testsuite/integration-arquillian/tests/other/adapters/was/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/was/pom.xml
index bba3ac2d5c6a..e3e304ec79cf 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/was/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/was/pom.xml
@@ -24,7 +24,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-tests-adapters
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     integration-arquillian-tests-adapters-was
diff --git a/testsuite/integration-arquillian/tests/other/adapters/was/was8/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/was/was8/pom.xml
index fae14d32c0be..d7e09b2af14e 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/was/was8/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/was/was8/pom.xml
@@ -24,7 +24,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-tests-adapters-was
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     integration-arquillian-tests-adapters-was8
diff --git a/testsuite/integration-arquillian/tests/other/adapters/wls/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/wls/pom.xml
index a23025811937..dbe388550c86 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/wls/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/wls/pom.xml
@@ -24,7 +24,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-tests-adapters
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     integration-arquillian-tests-adapters-wls
diff --git a/testsuite/integration-arquillian/tests/other/adapters/wls/wls12/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/wls/wls12/pom.xml
index ac77dd3c8c0c..b016b8373c4c 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/wls/wls12/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/wls/wls12/pom.xml
@@ -24,7 +24,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-tests-adapters-wls
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     integration-arquillian-tests-adapters-wls12
diff --git a/testsuite/integration-arquillian/tests/other/base-ui/pom.xml b/testsuite/integration-arquillian/tests/other/base-ui/pom.xml
index 252ec2012c71..83dd48e32c58 100644
--- a/testsuite/integration-arquillian/tests/other/base-ui/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/base-ui/pom.xml
@@ -22,7 +22,7 @@
     
         integration-arquillian-tests-other
         org.keycloak.testsuite
-        11.0.0-SNAPSHOT
+        11.0.3
     
     4.0.0
 
diff --git a/testsuite/integration-arquillian/tests/other/clean-start/pom.xml b/testsuite/integration-arquillian/tests/other/clean-start/pom.xml
index 9c5c0d3f8018..d11ced98a2b3 100644
--- a/testsuite/integration-arquillian/tests/other/clean-start/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/clean-start/pom.xml
@@ -23,7 +23,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-tests-other
-        11.0.0-SNAPSHOT
+        11.0.3
     
     
     integration-arquillian-tests-smoke-clean-start
diff --git a/testsuite/integration-arquillian/tests/other/console/pom.xml b/testsuite/integration-arquillian/tests/other/console/pom.xml
index 20a5f19ae634..78f4c5bc7ad6 100644
--- a/testsuite/integration-arquillian/tests/other/console/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/console/pom.xml
@@ -24,7 +24,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-tests-other
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     integration-arquillian-tests-console
diff --git a/testsuite/integration-arquillian/tests/other/jpa-performance/pom.xml b/testsuite/integration-arquillian/tests/other/jpa-performance/pom.xml
index b1d59b288603..d93dbea81fd7 100644
--- a/testsuite/integration-arquillian/tests/other/jpa-performance/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/jpa-performance/pom.xml
@@ -24,7 +24,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-tests-other
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     integration-arquillian-tests-jpa-performance
diff --git a/testsuite/integration-arquillian/tests/other/mod_auth_mellon/pom.xml b/testsuite/integration-arquillian/tests/other/mod_auth_mellon/pom.xml
index e5075565984b..0982c62ea8c7 100644
--- a/testsuite/integration-arquillian/tests/other/mod_auth_mellon/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/mod_auth_mellon/pom.xml
@@ -24,7 +24,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-tests-other
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     integration-arquillian-tests-other-mod_auth_mellon
diff --git a/testsuite/integration-arquillian/tests/other/pom.xml b/testsuite/integration-arquillian/tests/other/pom.xml
index 356895a7718b..3b7b3b957773 100644
--- a/testsuite/integration-arquillian/tests/other/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/pom.xml
@@ -24,7 +24,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-tests
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     integration-arquillian-tests-other
diff --git a/testsuite/integration-arquillian/tests/other/server-config-migration/pom.xml b/testsuite/integration-arquillian/tests/other/server-config-migration/pom.xml
index 7eb6416c47b8..1dd2842f9368 100644
--- a/testsuite/integration-arquillian/tests/other/server-config-migration/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/server-config-migration/pom.xml
@@ -24,7 +24,7 @@
     
         org.keycloak.testsuite
         integration-arquillian-tests-other
-        11.0.0-SNAPSHOT
+        11.0.3
         ../pom.xml
     
     
diff --git a/testsuite/integration-arquillian/tests/other/springboot-tests/pom.xml b/testsuite/integration-arquillian/tests/other/springboot-tests/pom.xml
index fa46afa2fe1c..30feeb87aacf 100644
--- a/testsuite/integration-arquillian/tests/other/springboot-tests/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/springboot-tests/pom.xml
@@ -5,7 +5,7 @@
     
         integration-arquillian-tests-other
         org.keycloak.testsuite
-        11.0.0-SNAPSHOT
+        11.0.3
     
     4.0.0
 
diff --git a/testsuite/integration-arquillian/tests/other/sssd/pom.xml b/testsuite/integration-arquillian/tests/other/sssd/pom.xml
index 8e6f14379f4e..cb608a92a60c 100644
--- a/testsuite/integration-arquillian/tests/other/sssd/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/sssd/pom.xml
@@ -5,7 +5,7 @@
     
         integration-arquillian-tests-other
         org.keycloak.testsuite
-        11.0.0-SNAPSHOT
+        11.0.3
     
     4.0.0
 
diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml
index 156c6789b782..d1326666ad6a 100755
--- a/testsuite/integration-arquillian/tests/pom.xml
+++ b/testsuite/integration-arquillian/tests/pom.xml
@@ -24,7 +24,7 @@
     
         org.keycloak.testsuite
         integration-arquillian
-        11.0.0-SNAPSHOT
+        11.0.3
     
 
     pom
diff --git a/testsuite/integration-arquillian/util/pom.xml b/testsuite/integration-arquillian/util/pom.xml
index 5f901c7763b4..e070aa14b0d8 100644
--- a/testsuite/integration-arquillian/util/pom.xml
+++ b/testsuite/integration-arquillian/util/pom.xml
@@ -21,7 +21,7 @@
     
         org.keycloak.testsuite
         integration-arquillian
-        11.0.0-SNAPSHOT
+        11.0.3
     
     4.0.0
 
diff --git a/testsuite/performance/infinispan/pom.xml b/testsuite/performance/infinispan/pom.xml
index a5d72e0bf978..45a68657e6bf 100644
--- a/testsuite/performance/infinispan/pom.xml
+++ b/testsuite/performance/infinispan/pom.xml
@@ -21,7 +21,7 @@
     
         org.keycloak.testsuite
         performance
-        11.0.0-SNAPSHOT
+        11.0.3
         ../pom.xml
     
     4.0.0
diff --git a/testsuite/performance/keycloak/pom.xml b/testsuite/performance/keycloak/pom.xml
index 923483e9eebe..a056ec5a7449 100644
--- a/testsuite/performance/keycloak/pom.xml
+++ b/testsuite/performance/keycloak/pom.xml
@@ -21,7 +21,7 @@
     
         org.keycloak.testsuite
         performance
-        11.0.0-SNAPSHOT
+        11.0.3
         ../pom.xml
     
     4.0.0
diff --git a/testsuite/performance/load-balancer/wildfly-modcluster/pom.xml b/testsuite/performance/load-balancer/wildfly-modcluster/pom.xml
index bd46fb06c491..fc9c9af2f0be 100644
--- a/testsuite/performance/load-balancer/wildfly-modcluster/pom.xml
+++ b/testsuite/performance/load-balancer/wildfly-modcluster/pom.xml
@@ -21,7 +21,7 @@
     
         org.keycloak.testsuite
         performance
-        11.0.0-SNAPSHOT
+        11.0.3
         ../../pom.xml
     
     4.0.0
diff --git a/testsuite/performance/pom.xml b/testsuite/performance/pom.xml
index dec5944f3df2..68967e3ccc29 100644
--- a/testsuite/performance/pom.xml
+++ b/testsuite/performance/pom.xml
@@ -21,7 +21,7 @@
     
         keycloak-testsuite-pom
         org.keycloak
-        11.0.0-SNAPSHOT
+        11.0.3
         ../pom.xml
     
     4.0.0
diff --git a/testsuite/performance/tests/pom.xml b/testsuite/performance/tests/pom.xml
index cb6ab2d0fde0..f80c9f4952a9 100644
--- a/testsuite/performance/tests/pom.xml
+++ b/testsuite/performance/tests/pom.xml
@@ -21,7 +21,7 @@
     
         org.keycloak.testsuite
         performance
-        11.0.0-SNAPSHOT
+        11.0.3
         ../pom.xml
     
     4.0.0
diff --git a/testsuite/pom.xml b/testsuite/pom.xml
index 05a1cfacdf19..a7c4b788171a 100755
--- a/testsuite/pom.xml
+++ b/testsuite/pom.xml
@@ -21,7 +21,7 @@
     
         keycloak-parent
         org.keycloak
-        11.0.0-SNAPSHOT
+        11.0.3
         ../pom.xml
     
     4.0.0
diff --git a/testsuite/utils/pom.xml b/testsuite/utils/pom.xml
index f42f02ce9617..da8123983c66 100755
--- a/testsuite/utils/pom.xml
+++ b/testsuite/utils/pom.xml
@@ -21,7 +21,7 @@
     
         keycloak-testsuite-pom
         org.keycloak
-        11.0.0-SNAPSHOT
+        11.0.3
     
     4.0.0
 
diff --git a/themes/pom.xml b/themes/pom.xml
index 3adba182bd14..471814aa71d4 100755
--- a/themes/pom.xml
+++ b/themes/pom.xml
@@ -4,7 +4,7 @@
     
         keycloak-parent
         org.keycloak
-        11.0.0-SNAPSHOT
+        11.0.3
     
     4.0.0
 
diff --git a/themes/src/main/resources-product/theme/rh-sso/welcome/resources/favicon.ico b/themes/src/main/resources-product/theme/rh-sso/welcome/resources/favicon.ico
deleted file mode 100644
index 11c5cd26196c..000000000000
Binary files a/themes/src/main/resources-product/theme/rh-sso/welcome/resources/favicon.ico and /dev/null differ
diff --git a/themes/src/main/resources/theme/base/account/template.ftl b/themes/src/main/resources/theme/base/account/template.ftl
index b7f2be319a4b..6f08eefc5149 100644
--- a/themes/src/main/resources/theme/base/account/template.ftl
+++ b/themes/src/main/resources/theme/base/account/template.ftl
@@ -7,7 +7,7 @@
     
 
     ${msg("accountManagementTitle")}
-    
+    
     <#if properties.stylesCommon?has_content>
         <#list properties.stylesCommon?split(' ') as style>
             
diff --git a/themes/src/main/resources/theme/base/admin/index.ftl b/themes/src/main/resources/theme/base/admin/index.ftl
index 8281828adcbe..da1e6654d434 100755
--- a/themes/src/main/resources/theme/base/admin/index.ftl
+++ b/themes/src/main/resources/theme/base/admin/index.ftl
@@ -8,7 +8,7 @@
     
     
 
-    
+    
     <#if properties.stylesCommon?has_content>
     <#list properties.stylesCommon?split(' ') as style>
     
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index 12b6a6324dfe..5004f0172494 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -1174,6 +1174,12 @@ otp-token-period=OTP Token Period
 otp-token-period.tooltip=How many seconds should an OTP token be valid? Defaults to 30 seconds.
 otp-supported-applications=Supported Applications
 otp-supported-applications.tooltip=Applications that are known to work with the current OTP policy
+loa-level=Level of Authentication
+loa-level.tooltip=Sets the Level of Authentication to the specified value.
+loa-store-in-user-session=Store LOA in user session
+loa-store-in-user-session.tooltip=Additionally stores the LOA in the user session.
+loa-condition-level=Level of Authentication
+loa-condition-level.tooltip=If this level of authentication is reached the condition evaluates to false.
 table-of-password-policies=Table of Password Policies
 add-policy.placeholder=Add policy...
 policy-type=Policy Type
@@ -1723,6 +1729,9 @@ subjectdn-tooltip=A regular expression for validating Subject DN in the Client C
 pkce-code-challenge-method=Proof Key for Code Exchange Code Challenge Method
 pkce-code-challenge-method.tooltip=Choose which code challenge method for PKCE is used. If not specified, keycloak does not applies PKCE to a client unless the client sends an authorization request with appropriate code challenge and code exchange method.
 
+acr-loa-map=ACR To LOA Mapping
+acr-loa-map.tooltip=Define which ACR (Authentication Context Class Reference) value is mapped to which LOA (Level of Authentication). The ACR can be any value, whereas the LOA must be numeric.
+
 key-not-allowed-here=Key '{{character}}' is not allowed here.
 
 # KEYCLOAK-10927 Implement LDAPv3 Password Modify Extended Operation
diff --git a/themes/src/main/resources/theme/base/admin/messages/messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/messages_en.properties
index 6a870247dd0b..c9bf2ecb291f 100644
--- a/themes/src/main/resources/theme/base/admin/messages/messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/messages_en.properties
@@ -22,6 +22,13 @@ ldapErrorMissingGroupsPathGroup=Groups path group does not exist - please create
 
 clientRedirectURIsFragmentError=Redirect URIs must not contain an URI fragment
 clientRootURLFragmentError=Root URL must not contain an URL fragment
+clientRootURLIllegalSchemeError=Root URL uses an illegal scheme
+clientBaseURLIllegalSchemeError=Base URL uses an illegal scheme
+clientRedirectURIsIllegalSchemeError=A redirect URI uses an illegal scheme
+clientBaseURLInvalid=Base URL is not a valid URL
+clientRootURLInvalid=Root URL is not a valid URL
+clientRedirectURIsInvalid=A redirect URI is not a valid URI
+
 
 pairwiseMalformedClientRedirectURI=Client contained an invalid redirect URI.
 pairwiseClientRedirectURIsMissingHost=Client redirect URIs must contain a valid host component.
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
index b06ee78de1be..259bc2966b57 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
@@ -1290,6 +1290,11 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, flows, $ro
                 $scope.displayOnConsentScreen = false;
             }
         }
+        try {
+          $scope.acrLoaMap = JSON.parse($scope.client.attributes["acr.loa.map"] || "{}");
+        } catch (e) {
+          $scope.acrLoaMap = {};
+        }
     }
 
     if (!$scope.create) {
@@ -1411,6 +1416,24 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, flows, $ro
         $scope.clientEdit.attributes['pkce.code.challenge.method'] = $scope.pkceCodeChallengeMethod;
     };
 
+    $scope.$watch('newAcr', function() {
+            $scope.changed = isChanged();
+        }, true);
+    $scope.$watch('newLoa', function() {
+            $scope.changed = isChanged();
+        }, true);
+    $scope.deleteAcrLoaMapping = function(acr) {
+        delete $scope.acrLoaMap[acr];
+        $scope.changed = true;
+    }
+    $scope.addAcrLoaMapping = function() {
+        if ($scope.newLoa.match(/^[0-9]+$/)) {
+            $scope.acrLoaMap[$scope.newAcr] = $scope.newLoa;
+            $scope.newAcr = $scope.newLoa = "";
+            $scope.changed = true;
+        }
+    }
+
     $scope.$watch(function() {
         return $location.path();
     }, function() {
@@ -1427,6 +1450,9 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, flows, $ro
         if ($scope.newWebOrigin && $scope.newWebOrigin.length > 0) {
             return true;
         }
+        if ($scope.newAcr && $scope.newAcr.length > 0 && $scope.newLoa && $scope.newLoa.length > 0) {
+            return true;
+        }
         return false;
     }
 
@@ -1539,6 +1565,10 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, flows, $ro
             $scope.addWebOrigin();
         }
 
+        if ($scope.newAcr && $scope.newAcr.length > 0 && $scope.newLoa && $scope.newLoa.length > 0) {
+          $scope.addAcrLoaMapping();
+        }
+
         if ($scope.samlServerSignature == true) {
             $scope.clientEdit.attributes["saml.server.signature"] = "true";
         } else {
@@ -1618,6 +1648,8 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, flows, $ro
             $scope.clientEdit.attributes["display.on.consent.screen"] = "false";
         }
 
+        $scope.clientEdit.attributes["acr.loa.map"] = JSON.stringify($scope.acrLoaMap);
+
         $scope.clientEdit.protocol = $scope.protocol;
         $scope.clientEdit.attributes['saml.signature.algorithm'] = $scope.signatureAlgorithm;
         $scope.clientEdit.attributes['saml_name_id_format'] = $scope.nameIdFormat;
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
index c2130d2a1821..5068d1be206e 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
@@ -636,6 +636,27 @@
                 
                 {{:: 'pkce-code-challenge-method.tooltip' | translate}}
             
+
+            
+ +
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ {{:: 'acr-loa-map.tooltip' | translate}} +
diff --git a/themes/src/main/resources/theme/base/login/messages/messages_en.properties b/themes/src/main/resources/theme/base/login/messages/messages_en.properties index 7a51c2089d60..822a3dfc987c 100755 --- a/themes/src/main/resources/theme/base/login/messages/messages_en.properties +++ b/themes/src/main/resources/theme/base/login/messages/messages_en.properties @@ -261,6 +261,7 @@ unexpectedErrorHandlingRequestMessage=Unexpected error when handling authenticat invalidAccessCodeMessage=Invalid access code. sessionNotActiveMessage=Session not active. invalidCodeMessage=An error occurred, please login again through your application. +insufficientLevelOfAuthentication=The requested level of authentication has not been satisfied. identityProviderUnexpectedErrorMessage=Unexpected error when authenticating with identity provider identityProviderNotFoundMessage=Could not find an identity provider with the identifier. identityProviderLinkSuccess=You successfully verified your email. Please go back to your original browser and continue there with the login. diff --git a/themes/src/main/resources/theme/base/login/template.ftl b/themes/src/main/resources/theme/base/login/template.ftl index eb3ec0147689..c6cf20565ab5 100644 --- a/themes/src/main/resources/theme/base/login/template.ftl +++ b/themes/src/main/resources/theme/base/login/template.ftl @@ -13,7 +13,7 @@ ${msg("loginTitle",(realm.displayName!''))} - + <#if properties.stylesCommon?has_content> <#list properties.stylesCommon?split(' ') as style> diff --git a/themes/src/main/resources/theme/keycloak/account/theme.properties b/themes/src/main/resources/theme/keycloak/account/theme.properties index d0c205a0037a..e7f1147934e1 100644 --- a/themes/src/main/resources/theme/keycloak/account/theme.properties +++ b/themes/src/main/resources/theme/keycloak/account/theme.properties @@ -1,4 +1,5 @@ parent=base +import=common/keycloak styles=css/account.css stylesCommon=node_modules/patternfly/dist/css/patternfly.min.css node_modules/patternfly/dist/css/patternfly-additions.min.css diff --git a/themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css b/themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css index 681b3fc0451c..8c00d98b34aa 100755 --- a/themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css +++ b/themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css @@ -406,6 +406,10 @@ table.kc-authz-table-expanded { font-size: 14px; } +.input-map input.form-control { + width: 50%; +} + /* Deactivation styles for user-group membership tree models */ div[tree-model] li .deactivate { diff --git a/themes/src/main/resources/theme/keycloak/admin/theme.properties b/themes/src/main/resources/theme/keycloak/admin/theme.properties index 07ab7a5281a3..c1a677f5057f 100755 --- a/themes/src/main/resources/theme/keycloak/admin/theme.properties +++ b/themes/src/main/resources/theme/keycloak/admin/theme.properties @@ -1,3 +1,5 @@ parent=base +import=common/keycloak + styles=css/styles.css -stylesCommon=node_modules/patternfly/dist/css/patternfly.min.css node_modules/patternfly/dist/css/patternfly-additions.min.css node_modules/select2/select2.css lib/angular/treeview/css/angular.treeview.css node_modules/text-security/text-security.css \ No newline at end of file +stylesCommon=node_modules/patternfly/dist/css/patternfly.min.css node_modules/patternfly/dist/css/patternfly-additions.min.css node_modules/select2/select2.css lib/angular/treeview/css/angular.treeview.css node_modules/text-security/text-security.css diff --git a/themes/src/main/resources/theme/keycloak/login/theme.properties b/themes/src/main/resources/theme/keycloak/login/theme.properties index 417eb5c12258..6e5bdcf17f44 100644 --- a/themes/src/main/resources/theme/keycloak/login/theme.properties +++ b/themes/src/main/resources/theme/keycloak/login/theme.properties @@ -1,4 +1,5 @@ parent=base +import=common/keycloak styles=css/login.css stylesCommon=node_modules/patternfly/dist/css/patternfly.min.css node_modules/patternfly/dist/css/patternfly-additions.min.css lib/zocial/zocial.css diff --git a/themes/src/main/resources/theme/keycloak/welcome/index.ftl b/themes/src/main/resources/theme/keycloak/welcome/index.ftl index 8fd197888fc5..eba148bd31a9 100755 --- a/themes/src/main/resources/theme/keycloak/welcome/index.ftl +++ b/themes/src/main/resources/theme/keycloak/welcome/index.ftl @@ -29,7 +29,7 @@ - + <#if properties.stylesCommon?has_content> <#list properties.stylesCommon?split(' ') as style> diff --git a/themes/src/main/resources/theme/keycloak/welcome/theme.properties b/themes/src/main/resources/theme/keycloak/welcome/theme.properties index 7e48d4098477..c8bc26974862 100644 --- a/themes/src/main/resources/theme/keycloak/welcome/theme.properties +++ b/themes/src/main/resources/theme/keycloak/welcome/theme.properties @@ -1,4 +1,6 @@ styles=css/welcome.css +import=common/keycloak + stylesCommon=node_modules/patternfly/dist/css/patternfly.css node_modules/patternfly/dist/css/patternfly-additions.css documentationUrl=https://www.keycloak.org/documentation.html diff --git a/util/embedded-ldap/pom.xml b/util/embedded-ldap/pom.xml index 85deefd127fa..10724ed9d37e 100644 --- a/util/embedded-ldap/pom.xml +++ b/util/embedded-ldap/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../../pom.xml 4.0.0 diff --git a/util/pom.xml b/util/pom.xml index 047f47f05695..cd1aba5492ac 100644 --- a/util/pom.xml +++ b/util/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 ../pom.xml diff --git a/wildfly/adduser/pom.xml b/wildfly/adduser/pom.xml index c8d4347bec40..676a7833a6f8 100755 --- a/wildfly/adduser/pom.xml +++ b/wildfly/adduser/pom.xml @@ -21,7 +21,7 @@ org.keycloak keycloak-wildfly-parent - 11.0.0-SNAPSHOT + 11.0.3 keycloak-wildfly-adduser diff --git a/wildfly/extensions/pom.xml b/wildfly/extensions/pom.xml index dbd7ef4c2b8b..6a15208e84ce 100755 --- a/wildfly/extensions/pom.xml +++ b/wildfly/extensions/pom.xml @@ -21,7 +21,7 @@ org.keycloak keycloak-wildfly-parent - 11.0.0-SNAPSHOT + 11.0.3 keycloak-wildfly-extensions diff --git a/wildfly/pom.xml b/wildfly/pom.xml index a7ad81831de9..f9f73e775b01 100755 --- a/wildfly/pom.xml +++ b/wildfly/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 11.0.0-SNAPSHOT + 11.0.3 Keycloak WildFly Integration diff --git a/wildfly/server-subsystem/pom.xml b/wildfly/server-subsystem/pom.xml index 078f42b46da1..237b0a315b6a 100755 --- a/wildfly/server-subsystem/pom.xml +++ b/wildfly/server-subsystem/pom.xml @@ -21,7 +21,7 @@ org.keycloak keycloak-wildfly-parent - 11.0.0-SNAPSHOT + 11.0.3 keycloak-wildfly-server-subsystem diff --git a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml index 5fb32ea190aa..f7b199c983d7 100755 --- a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml +++ b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-datasources.xml @@ -36,6 +36,9 @@ sa sa + + 100 + diff --git a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-undertow.xml b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-undertow.xml index faebb13f4ee6..4ef3bcc67755 100644 --- a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-undertow.xml +++ b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-undertow.xml @@ -28,8 +28,8 @@ - - + +