diff --git a/.gitattributes b/.gitattributes
index 2b70adf8d81a..166e7330d90f 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -18,3 +18,5 @@
*.eot binary
*.otf binary
*.woff binary
+# See https://github.com/approvals/ApprovalTests.Java#approved-file-artifacts (used in golden testing for help output of quarkus based dist)
+*.approved.* binary
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 23130e60b255..681b2a513145 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -414,7 +414,7 @@ jobs:
- name: Run Quarkus Tests in Docker
run: |
- mvn clean install -nsu -B -f quarkus/tests/pom.xml -Dkc.quarkus.tests.dist=docker -Dtest=StartDevCommandTest | misc/log/trimmer.sh
+ mvn clean install -nsu -B -f quarkus/tests/pom.xml -Dkc.quarkus.tests.dist=docker | misc/log/trimmer.sh
TEST_RESULT=${PIPESTATUS[0]}
exit $TEST_RESULT
@@ -425,6 +425,13 @@ jobs:
find . -path '*/target/surefire-reports/*.xml' | zip -q reports-quarkus-tests.zip -@
exit $TEST_RESULT
+ - name: Run Quarkus Storage Tests
+ run: |
+ mvn clean install -nsu -B -f quarkus/tests/pom.xml -Ptest-database -Dtest=PostgreSQLStartDatabaseTest | misc/log/trimmer.sh
+ TEST_RESULT=${PIPESTATUS[0]}
+ find . -path '*/target/surefire-reports/*.xml' | zip -q reports-quarkus-tests.zip -@
+ exit $TEST_RESULT
+
- name: Quarkus test reports
uses: actions/upload-artifact@v2
if: failure()
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index f61d9d749f60..bb5e6483141e 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -11,6 +11,10 @@ on:
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
+ paths-ignore:
+ - 'testsuite/**'
+ - 'examples/**'
+ - 'quarkus/tests/**'
schedule:
- cron: '0 9 * * 2'
diff --git a/.github/workflows/operator-ci.yml b/.github/workflows/operator-ci.yml
new file mode 100644
index 000000000000..554f82c3549a
--- /dev/null
+++ b/.github/workflows/operator-ci.yml
@@ -0,0 +1,39 @@
+name: Keycloak Operator CI
+
+on: [push, pull_request]
+
+env:
+ JDK_VERSION: 11
+
+concurrency:
+ # Only run once for latest commit per ref and cancel other (previous) runs.
+ group: ci-operator-keycloak-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ build:
+ name: Build
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Update maven settings
+ run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/
+ - uses: actions/setup-java@v1
+ with:
+ java-version: ${{ env.JDK_VERSION }}
+ - name: Cache Maven packages
+ id: cache
+ uses: actions/cache@v2
+ with:
+ path: |
+ ~/.m2/repository
+ key: cache-1-${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
+ restore-keys: cache-1-${{ runner.os }}-m2
+
+ - name: Create the Keycloak distribution
+ run: |
+ mvn clean install -DskipTests -DskipExamples -DskipTestsuite
+
+ - name: Build the Keycloak Operator
+ run: |
+ mvn clean package -nsu -B -e -pl operator -Doperator -Dquarkus.container-image.build=true -Dquarkus.kubernetes.deployment-target=minikube
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8174af6a3284..4045178cfb51 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -20,7 +20,7 @@ Here's a quick checklist for a good PR, more details below:
3. One feature/change per PR
4. One commit per PR
5. PR rebased on main (`git rebase`, not `git pull`)
-5. Commit message includes link to issue ([linking a pull request to an issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue))
+5. [Good descriptive commit message, with link to issue](#commit-messages-and-issue-linking)
6. No changes to code not directly related to your PR
7. Includes functional/integration test
8. Includes documentation
@@ -92,4 +92,43 @@ for more details.
The above helps us review your PR and also makes it easier for us to maintain the repository. It is also required by
our automatic merging process.
+Please, also provide a good description [commit message, with a link to the issue](#commit-messages-and-issue-linking).
We also require that the commit message includes a link to the issue ([linking a pull request to an issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue)).
+
+### Commit messages and issue linking
+
+The format for a commit message should look like:
+
+```
+A brief descriptive summary
+
+Optionally, more details around how it was implemented
+
+Closes #1234
+```
+
+The very last part of the commit message should be a link to the GitHub issue, when done correctly GitHub will automatically link the issue with the PR. There are 3 alternatives provided by GitHub here:
+
+* Closes: Issues in the same repository
+* Fixes: Issues in a different repository (this shouldn't be used, as issues should be created in the correct repository instead)
+* Resolves: When multiple issues are resolved (this should be avoided)
+
+Although, GitHub allows alternatives (close, closed, fix, fixed), please only use the above formats.
+
+Creating multi line commit messages with `git` can be done with:
+
+```
+git commit -m "Summary" -m "Optional description" -m "Closes #1234"
+```
+
+Alternatively, `shift + enter` can be used to add line breaks:
+
+```
+$ git commit -m "Summary
+>
+> Optional description
+>
+> Closes #1234"
+```
+
+For more information linking PRs to issues refer to the [GitHub Documentation](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue).
\ No newline at end of file
diff --git a/README.md b/README.md
index 250ab9cb2690..3b6d4f51fbd1 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ This repository contains the source code for the Keycloak Server, Java adapters
## Reporting Security Vulnerabilities
-If you've found a security vulnerability, please look at the [instructions on how to properly report it](SECURITY.md)
+If you've found a security vulnerability, please look at the [instructions on how to properly report it](https://github.com/keycloak/keycloak/security/policy)
## Reporting an issue
diff --git a/SECURITY.md b/SECURITY.md
deleted file mode 100644
index 5f1a4103e621..000000000000
--- a/SECURITY.md
+++ /dev/null
@@ -1,30 +0,0 @@
-# Security Policy
-
-The Keycloak team takes security very seriously, and aim to resolve issues as quickly as possible. Building secure
-software is a continuous process, and can always be improved. As such we welcome reports on potential security
-vulnerabilities, as well as suggestions around hardening the software and our process.
-
-## Reporting a suspected vulnerability
-
-It is important that suspected vulnerabilities are disclosed in a responsible way, and are not publicly disclosed until
-after they have been analysed and a fix is available.
-
-To report a security vulnerability, send an email to keycloak-security@googlegroups.com.
-
-If you would like to work with us on a fix for the security vulnerability, please include your GitHub username
-in the above email, and we will provide you access to a temporary private fork where we can collaborate on a fix
-without it being disclosed publicly.
-
-Do *not* open a public issue, send a pull request, or disclose any information about the suspected vulnerability publicly.
-If you discover any publicly disclosed security vulnerabilities, please notify us *immediately* through
-keycloak-security@googlegroups.com.
-
-## Supported Versions
-
-Depending on the severity of a vulnerability the issue may be fixed in the current `major.minor` release of Keycloak, or
-for lower severity vulnerabilities or hardening in the following `major.minor` release. Refer to
-`https://www.keycloak.org/downloads` to find the latest release.
-
-If you are unable to regularly upgrade Keycloak we encourage you to consider
-[Red Hat Single Sign-On](https://access.redhat.com/products/red-hat-single-sign-on), which offers
-[long term support](https://access.redhat.com/support/policy/updates/jboss_notes#p_sso) of specific versions of Keycloak.
\ No newline at end of file
diff --git a/adapters/oidc/adapter-core/pom.xml b/adapters/oidc/adapter-core/pom.xml
index 7696bc7fce5f..4356de31a20c 100755
--- a/adapters/oidc/adapter-core/pom.xml
+++ b/adapters/oidc/adapter-core/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/oidc/as7-eap6/as7-adapter-spi/pom.xml b/adapters/oidc/as7-eap6/as7-adapter-spi/pom.xml
index 15ded6904f01..2b3b6d316d3c 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../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 7cd8686d6760..43f79fa899eb 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../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 673af8376397..09f9d7e1743d 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../pom.xml
diff --git a/adapters/oidc/as7-eap6/pom.xml b/adapters/oidc/as7-eap6/pom.xml
index 354fc23b5f36..1a22ba6b7e89 100755
--- a/adapters/oidc/as7-eap6/pom.xml
+++ b/adapters/oidc/as7-eap6/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../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 c9ee3a485392..b710c67f5b0d 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../pom.xml
4.0.0
diff --git a/adapters/oidc/fuse7/jetty94/pom.xml b/adapters/oidc/fuse7/jetty94/pom.xml
index 4a0633512b0f..ba7814a74d09 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../pom.xml
4.0.0
diff --git a/adapters/oidc/fuse7/pom.xml b/adapters/oidc/fuse7/pom.xml
index 7ccd2208c593..a4f9bfea41f0 100644
--- a/adapters/oidc/fuse7/pom.xml
+++ b/adapters/oidc/fuse7/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/oidc/fuse7/tomcat8/pom.xml b/adapters/oidc/fuse7/tomcat8/pom.xml
index c6103ff8eb59..bff573b60aa1 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../pom.xml
4.0.0
diff --git a/adapters/oidc/fuse7/undertow/pom.xml b/adapters/oidc/fuse7/undertow/pom.xml
index 009c41d50b20..f85f424bd613 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../pom.xml
4.0.0
diff --git a/adapters/oidc/installed/pom.xml b/adapters/oidc/installed/pom.xml
index cbb2257d78fa..67c879264991 100755
--- a/adapters/oidc/installed/pom.xml
+++ b/adapters/oidc/installed/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KcinitDriver.java b/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KcinitDriver.java
deleted file mode 100644
index d26436560234..000000000000
--- a/adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KcinitDriver.java
+++ /dev/null
@@ -1,694 +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.adapters.installed;
-
-import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
-import org.keycloak.OAuth2Constants;
-import org.keycloak.adapters.KeycloakDeployment;
-import org.keycloak.adapters.KeycloakDeploymentBuilder;
-import org.keycloak.adapters.ServerRequest;
-import org.keycloak.common.util.Base64;
-import org.keycloak.common.util.Time;
-import org.keycloak.jose.jwe.*;
-import org.keycloak.representations.AccessTokenResponse;
-import org.keycloak.representations.adapters.config.AdapterConfig;
-import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
-import org.keycloak.util.JsonSerialization;
-
-import javax.crypto.SecretKey;
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.PBEKeySpec;
-import javax.crypto.spec.SecretKeySpec;
-import javax.ws.rs.client.Client;
-import javax.ws.rs.client.Entity;
-import javax.ws.rs.client.WebTarget;
-import javax.ws.rs.core.Form;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import java.io.*;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Paths;
-import java.security.NoSuchAlgorithmException;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.KeySpec;
-import java.util.*;
-
-/**
- * All kcinit commands that take input ask for
- *
- * 1. . kcinit
- * - setup and export KC_SESSION_KEY env var if not set.
- * - checks to see if master token valid, refresh is possible, exit if token valid
- * - performs command line login
- * - stores master token for master client
- * 2. app.sh is a wrapper for app cli.
- * - token=`kcinit token app`
- * - checks to see if token for app client has been fetched, refresh if valid, output token to sys.out if exists
- * - if no token, login. Prompts go to stderr.
- * - pass token as cmd line param to app or as environment variable.
- *
- * 3. kcinit password {password}
- * - outputs password key that is used for encryption.
- * - can be used in .bashrc as export KC_SESSSION_KEY=`kcinit password {password}` or just set it in .bat file
- *
- *
- * @author Bill Burke
- * @version $Revision: 1 $
- */
-public class KcinitDriver {
-
- public static final String KC_SESSION_KEY = "KC_SESSION_KEY";
- public static final String KC_LOGIN_CONFIG_PATH = "KC_LOGIN_CONFIG_PATH";
- protected Map config;
- protected boolean debug = true;
-
- protected static byte[] salt = new byte[]{-4, 88, 66, -101, 78, -94, 21, 105};
-
- String[] args = null;
-
- protected boolean forceLogin;
- protected boolean browserLogin;
-
- public void mainCmd(String[] args) throws Exception {
-
- this.args = args;
-
-
- if (args.length == 0) {
- printHelp();
- return;
- }
-
- if (args[0].equalsIgnoreCase("token")) {
- //System.err.println("executing token");
- token();
- } else if (args[0].equalsIgnoreCase("login")) {
- login();
- } else if (args[0].equalsIgnoreCase("logout")) {
- logout();
- } else if (args[0].equalsIgnoreCase("env")) {
- System.out.println(System.getenv().toString());
- } else if (args[0].equalsIgnoreCase("install")) {
- install();
- } else if (args[0].equalsIgnoreCase("uninstall")) {
- uninstall();
- } else if (args[0].equalsIgnoreCase("password")) {
- passwordKey();
- } else {
- KeycloakInstalled.console().writer().println("Unknown command: " + args[0]);
- KeycloakInstalled.console().writer().println();
- printHelp();
- }
- }
-
- public String getHome() {
- String home = System.getenv("HOME");
- if (home == null) {
- home = System.getProperty("HOME");
- if (home == null) {
- home = Paths.get("").toAbsolutePath().normalize().toString();
- }
- }
- return home;
- }
-
- public void passwordKey() {
- if (args.length < 2) {
- printHelp();
- System.exit(1);
- }
- String password = args[1];
- try {
- String encodedKey = generateEncryptionKey(password);
- System.out.printf(encodedKey);
- } catch (Exception e) {
- e.printStackTrace();
- System.exit(1);
- }
- }
-
- protected String generateEncryptionKey(String password) throws NoSuchAlgorithmException, InvalidKeySpecException {
- SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
- KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 100, 128);
- SecretKey tmp = factory.generateSecret(spec);
- byte[] aeskey = tmp.getEncoded();
- return Base64.encodeBytes(aeskey);
- }
-
- public JWE createJWE() {
- String key = getEncryptionKey();
- if (key == null) {
- throw new RuntimeException(KC_SESSION_KEY + " env var not set");
- }
- byte[] aesKey = null;
- try {
- aesKey = Base64.decode(key.getBytes(StandardCharsets.UTF_8));
- } catch (IOException e) {
- throw new RuntimeException("invalid " + KC_SESSION_KEY + "env var");
- }
-
- JWE jwe = new JWE();
- final SecretKey aesSecret = new SecretKeySpec(aesKey, "AES");
- jwe.getKeyStorage()
- .setDecryptionKey(aesSecret);
- return jwe;
- }
-
- protected String encryptionKey;
-
- protected String getEncryptionKey() {
- if (encryptionKey != null) return encryptionKey;
- return System.getenv(KC_SESSION_KEY);
- }
-
- public String encrypt(String payload) {
- JWE jwe = createJWE();
- JWEHeader jweHeader = new JWEHeader(JWEConstants.A128KW, JWEConstants.A128CBC_HS256, null);
- jwe.header(jweHeader).content(payload.getBytes(StandardCharsets.UTF_8));
- try {
- return jwe.encodeJwe();
- } catch (JWEException e) {
- throw new RuntimeException("cannot encrypt payload", e);
- }
- }
-
- public String decrypt(String encoded) {
- JWE jwe = createJWE();
- try {
- jwe.verifyAndDecodeJwe(encoded);
- byte[] content = jwe.getContent();
- if (content == null) return null;
- return new String(content, StandardCharsets.UTF_8);
- } catch (Exception ex) {
- throw new RuntimeException("cannot decrypt payload", ex);
-
- }
-
- }
-
- public static String getenv(String name, String defaultValue) {
- String val = System.getenv(name);
- return val == null ? defaultValue : val;
- }
-
- public File getConfigDirectory() {
- return Paths.get(getHome(), getenv(KC_LOGIN_CONFIG_PATH, ".keycloak"), "kcinit").toFile();
- }
-
-
- public File getConfigFile() {
- return Paths.get(getHome(), getenv(KC_LOGIN_CONFIG_PATH, ".keycloak"), "kcinit", "config.json").toFile();
- }
-
- public File getTokenFilePath(String client) {
- return Paths.get(getHome(), getenv(KC_LOGIN_CONFIG_PATH, ".keycloak"), "kcinit", "tokens", client).toFile();
- }
-
- public File getTokenDirectory() {
- return Paths.get(getHome(), getenv(KC_LOGIN_CONFIG_PATH, ".keycloak"), "kcinit", "tokens").toFile();
- }
-
- protected boolean encrypted = false;
-
- protected void checkEnv() {
- File configFile = getConfigFile();
- if (!configFile.exists()) {
- KeycloakInstalled.console().writer().println("You have not configured kcinit. Please run 'kcinit install' to configure.");
- System.exit(1);
- }
- byte[] data = new byte[0];
- try {
- data = readFileRaw(configFile);
- } catch (IOException e) {
-
- }
- if (data == null) {
- KeycloakInstalled.console().writer().println("Config file unreadable. Please run 'kcinit install' to configure.");
- System.exit(1);
-
- }
- String encodedJwe = new String(data, StandardCharsets.UTF_8);
-
- if (encodedJwe.contains("realm")) {
- encrypted = false;
- return;
- } else {
- encrypted = true;
- }
-
- if (System.getenv(KC_SESSION_KEY) == null) {
- promptLocalPassword();
- }
- }
-
- protected void promptLocalPassword() {
- String password = KeycloakInstalled.console().passwordPrompt("Enter password to unlock kcinit config files: ");
- try {
- encryptionKey = generateEncryptionKey(password);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
-
- protected String readFile(File fp) {
- try {
- byte[] data = readFileRaw(fp);
- if (data == null) return null;
- String file = new String(data, StandardCharsets.UTF_8);
- if (!encrypted) {
- return file;
- }
- String decrypted = decrypt(file);
- if (decrypted == null)
- throw new RuntimeException("Unable to decrypt file. Did you set your local password correctly?");
- return decrypted;
- } catch (IOException e) {
- throw new RuntimeException("failed to decrypt file: " + fp.getAbsolutePath() + " Did you set your local password correctly?", e);
- }
-
-
- }
-
- protected byte[] readFileRaw(File fp) throws IOException {
- if (!fp.exists()) return null;
- try (FileInputStream fis = new FileInputStream(fp)) {
- byte[] data = new byte[(int) fp.length()];
- fis.read(data);
- return data;
- }
- }
-
- protected void writeFile(File fp, String payload) {
- try {
- String data = payload;
- if (encrypted) data = encrypt(payload);
- FileOutputStream fos = new FileOutputStream(fp);
- fos.write(data.getBytes(StandardCharsets.UTF_8));
- fos.flush();
- fos.close();
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
-
- public void install() {
- if (getEncryptionKey() == null) {
- if (KeycloakInstalled.console().confirm("Do you want to protect tokens stored locally with a password? (y/n): ")) {
- String password = "p";
- String confirm = "c";
- do {
- password = KeycloakInstalled.console().passwordPrompt("Enter local password: ");
- confirm = KeycloakInstalled.console().passwordPrompt("Confirm local password: ");
- if (!password.equals(confirm)) {
- KeycloakInstalled.console().writer().println();
- KeycloakInstalled.console().writer().println("Confirmation does not match. Try again.");
- KeycloakInstalled.console().writer().println();
- }
- } while (!password.equals(confirm));
- try {
- this.encrypted = true;
- this.encryptionKey = generateEncryptionKey(password);
- } catch (Exception e) {
- e.printStackTrace();
- System.exit(1);
- }
- }
- } else {
- if (!KeycloakInstalled.console().confirm("KC_SESSION_KEY env var already set. Do you want to use this as your local encryption key? (y/n): ")) {
- KeycloakInstalled.console().writer().println("Unset KC_SESSION_KEY env var and run again");
- System.exit(1);
- }
- this.encrypted = true;
- this.encryptionKey = getEncryptionKey();
- }
- String server = KeycloakInstalled.console().readLine("Authentication server URL [http://localhost:8080/auth]: ").trim();
- String realm = KeycloakInstalled.console().readLine("Name of realm [master]: ").trim();
- String client = KeycloakInstalled.console().readLine("CLI client id [kcinit]: ").trim();
- String secret = KeycloakInstalled.console().readLine("CLI client secret [none]: ").trim();
- if (server.equals("")) {
- server = "http://localhost:8080/auth";
- }
- if (realm.equals("")) {
- realm = "master";
- }
- if (client.equals("")) {
- client = "kcinit";
- }
- File configDir = getTokenDirectory();
- configDir.mkdirs();
-
- File configFile = getConfigFile();
- Map props = new HashMap<>();
- props.put("server", server);
- props.put("realm", realm);
- props.put("client", client);
- props.put("secret", secret);
-
- try {
- String json = JsonSerialization.writeValueAsString(props);
- writeFile(configFile, json);
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- KeycloakInstalled.console().writer().println();
- KeycloakInstalled.console().writer().println("Installation complete!");
- KeycloakInstalled.console().writer().println();
- }
-
-
- public void printHelp() {
- KeycloakInstalled.console().writer().println("Commands:");
- KeycloakInstalled.console().writer().println(" login [-f] -f forces login");
- KeycloakInstalled.console().writer().println(" logout");
- KeycloakInstalled.console().writer().println(" token [client] - print access token of desired client. Defaults to default master client. Will print either 'error', 'not-allowed', or 'login-required' on error.");
- KeycloakInstalled.console().writer().println(" install - Install this utility. Will store in $HOME/.keycloak/kcinit unless " + KC_LOGIN_CONFIG_PATH + " env var is set");
- System.exit(1);
- }
-
-
- public AdapterConfig getConfig() {
- File configFile = getConfigFile();
- if (!configFile.exists()) {
- KeycloakInstalled.console().writer().println("You have not configured kcinit. Please run 'kcinit install' to configure.");
- System.exit(1);
- return null;
- }
-
- AdapterConfig config = new AdapterConfig();
- config.setAuthServerUrl(getConfigProperties().get("server"));
- config.setRealm(getConfigProperties().get("realm"));
- config.setResource(getConfigProperties().get("client"));
- config.setSslRequired("external");
- String secret = getConfigProperties().get("secret");
- if (secret != null && !secret.trim().equals("")) {
- Map creds = new HashMap<>();
- creds.put("secret", secret);
- config.setCredentials(creds);
- } else {
- config.setPublicClient(true);
- }
- return config;
- }
-
- private Map getConfigProperties() {
- if (this.config != null) return this.config;
- if (!getConfigFile().exists()) {
- KeycloakInstalled.console().writer().println();
- KeycloakInstalled.console().writer().println(("Config file does not exist. Run kcinit install to set it up."));
- System.exit(1);
- }
- String json = readFile(getConfigFile());
- try {
- Map map = JsonSerialization.readValue(json, Map.class);
- config = (Map) map;
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- return this.config;
- }
-
- public String readToken(String client) throws Exception {
- String json = getTokenResponse(client);
- if (json == null) return null;
-
-
- if (json != null) {
- try {
- AccessTokenResponse tokenResponse = JsonSerialization.readValue(json, AccessTokenResponse.class);
- if (Time.currentTime() < tokenResponse.getExpiresIn()) {
- return tokenResponse.getToken();
- }
- AdapterConfig config = getConfig();
- KeycloakInstalled installed = new KeycloakInstalled(KeycloakDeploymentBuilder.build(config));
- installed.refreshToken(tokenResponse.getRefreshToken());
- processResponse(installed, client);
- return tokenResponse.getToken();
- } catch (Exception e) {
- File tokenFile = getTokenFilePath(client);
- if (tokenFile.exists()) {
- tokenFile.delete();
- }
-
- return null;
- }
- }
- return null;
-
- }
-
- public String readRefreshToken(String client) throws Exception {
- String json = getTokenResponse(client);
- if (json == null) return null;
-
-
- if (json != null) {
- try {
- AccessTokenResponse tokenResponse = JsonSerialization.readValue(json, AccessTokenResponse.class);
- return tokenResponse.getRefreshToken();
- } catch (Exception e) {
- if (debug) {
- e.printStackTrace();
- }
- File tokenFile = getTokenFilePath(client);
- if (tokenFile.exists()) {
- tokenFile.delete();
- }
-
- return null;
- }
- }
- return null;
-
- }
-
-
- private String getTokenResponse(String client) {
- File tokenFile = getTokenFilePath(client);
- try {
- return readFile(tokenFile);
- } catch (Exception e) {
- if (debug) {
- System.err.println("Failed to read encrypted file");
- e.printStackTrace();
- }
- if (tokenFile.exists()) tokenFile.delete();
- return null;
- }
- }
-
-
- public void token() throws Exception {
- KeycloakInstalled.console().stderrOutput();
-
- checkEnv();
- String masterClient = getMasterClient();
- String client = masterClient;
- if (args.length > 1) {
- client = args[1];
- }
- //System.err.println("readToken: " + client);
- String token = readToken(client);
- if (token != null) {
- System.out.print(token);
- return;
- }
- if (token == null && client.equals(masterClient)) {
- //System.err.println("not logged in, logging in.");
- doConsoleLogin();
- token = readToken(client);
- if (token != null) {
- System.out.print(token);
- return;
- }
-
- }
- String masterToken = readToken(masterClient);
- if (masterToken == null) {
- //System.err.println("not logged in, logging in.");
- doConsoleLogin();
- masterToken = readToken(masterClient);
- if (masterToken == null) {
- System.err.println("Login failed. Cannot retrieve token");
- System.exit(1);
- }
- }
-
- //System.err.println("exchange: " + client);
- Client httpClient = getHttpClient();
-
- WebTarget exchangeUrl = httpClient.target(getServer())
- .path("/realms")
- .path(getRealm())
- .path("protocol/openid-connect/token");
-
- Form form = new Form()
- .param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
- .param(OAuth2Constants.CLIENT_ID, masterClient)
- .param(OAuth2Constants.SUBJECT_TOKEN, masterToken)
- .param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.ACCESS_TOKEN_TYPE)
- .param(OAuth2Constants.REQUESTED_TOKEN_TYPE, OAuth2Constants.REFRESH_TOKEN_TYPE)
- .param(OAuth2Constants.AUDIENCE, client);
- if (getMasterClientSecret() != null) {
- form.param(OAuth2Constants.CLIENT_SECRET, getMasterClientSecret());
- }
- Response response = exchangeUrl.request().post(Entity.form(
- form
- ));
-
- if (response.getStatus() == 401 || response.getStatus() == 403) {
- response.close();
- System.err.println("Not allowed to exchange for client token");
- System.exit(1);
- }
-
- if (response.getStatus() != 200) {
- if (response.getMediaType() != null && response.getMediaType().equals(MediaType.APPLICATION_JSON_TYPE)) {
- try {
- String json = response.readEntity(String.class);
- OAuth2ErrorRepresentation error = JsonSerialization.readValue(json, OAuth2ErrorRepresentation.class);
- System.err.println("Failed to exchange token: " + error.getError() + ". " + error.getErrorDescription());
- System.exit(1);
- } catch (Exception ignore) {
- ignore.printStackTrace();
-
- }
- }
-
- response.close();
- System.err.println("Unknown error exchanging for client token: " + response.getStatus());
- System.exit(1);
- }
-
- String json = response.readEntity(String.class);
- response.close();
- AccessTokenResponse tokenResponse = JsonSerialization.readValue(json, AccessTokenResponse.class);
- if (tokenResponse.getToken() != null) {
- getTokenDirectory().mkdirs();
- tokenResponse.setExpiresIn(Time.currentTime() + tokenResponse.getExpiresIn());
- tokenResponse.setIdToken(null);
- json = JsonSerialization.writeValueAsString(tokenResponse);
- writeFile(getTokenFilePath(client), json);
- System.out.printf(tokenResponse.getToken());
- } else {
- System.err.println("Error processing token");
- System.exit(1);
- }
- }
-
- protected String getMasterClientSecret() {
- return getProperty("secret");
- }
-
- protected String getServer() {
- return getProperty("server");
- }
-
- protected String getRealm() {
- return getProperty("realm");
- }
-
- public String getProperty(String name) {
- return getConfigProperties().get(name);
- }
-
- protected boolean forceLogin() {
- return args.length > 0 && args[0].equals("-f");
-
- }
-
- public Client getHttpClient() {
- return new ResteasyClientBuilder().disableTrustManager().build();
- }
-
- public void login() throws Exception {
- checkEnv();
- this.args = Arrays.copyOfRange(this.args, 1, this.args.length);
- for (String arg : args) {
- if (arg.equals("-f") || arg.equals("-force")) {
- forceLogin = true;
- this.args = Arrays.copyOfRange(this.args, 1, this.args.length);
- } else if (arg.equals("-browser") || arg.equals("-b")) {
- browserLogin = true;
- this.args = Arrays.copyOfRange(this.args, 1, this.args.length);
- } else {
- System.err.println("Illegal argument: " + arg);
- printHelp();
- System.exit(1);
- }
- }
-
- String masterClient = getMasterClient();
- if (!forceLogin && readToken(masterClient) != null) {
- KeycloakInstalled.console().writer().println("Already logged in. `kcinit -f` to force relogin");
- return;
- }
- doConsoleLogin();
- KeycloakInstalled.console().writer().println("Login successful!");
- }
-
- public void doConsoleLogin() throws Exception {
- String masterClient = getMasterClient();
- AdapterConfig config = getConfig();
- KeycloakInstalled installed = new KeycloakInstalled(KeycloakDeploymentBuilder.build(config));
- //System.err.println("calling loginCommandLine");
- if (!installed.loginCommandLine()) {
- System.exit(1);
- }
- processResponse(installed, masterClient);
- }
-
- private String getMasterClient() {
- return getProperty("client");
- }
-
- private void processResponse(KeycloakInstalled installed, String client) throws IOException {
- AccessTokenResponse tokenResponse = installed.getTokenResponse();
- tokenResponse.setExpiresIn(Time.currentTime() + tokenResponse.getExpiresIn());
- tokenResponse.setIdToken(null);
- String json = JsonSerialization.writeValueAsString(tokenResponse);
- getTokenDirectory().mkdirs();
- writeFile(getTokenFilePath(client), json);
- }
-
- public void logout() throws Exception {
- String token = readRefreshToken(getMasterClient());
- if (token != null) {
- try {
- KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getConfig());
- ServerRequest.invokeLogout(deployment, token);
- } catch (Exception e) {
- if (debug) {
- e.printStackTrace();
- }
- }
-
- }
- if (getTokenDirectory().exists()) {
- for (File fp : getTokenDirectory().listFiles()) fp.delete();
- }
- }
- public void uninstall() throws Exception {
- File configFile = getConfigFile();
- if (configFile.exists()) configFile.delete();
- if (getTokenDirectory().exists()) {
- for (File fp : getTokenDirectory().listFiles()) fp.delete();
- }
- }
-}
diff --git a/adapters/oidc/jaxrs-oauth-client/pom.xml b/adapters/oidc/jaxrs-oauth-client/pom.xml
index 7a07c8c217de..9febbdc56947 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/oidc/jetty/jetty-core/pom.xml b/adapters/oidc/jetty/jetty-core/pom.xml
index fad8bbc57c71..220f810051ec 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../../pom.xml
4.0.0
diff --git a/adapters/oidc/jetty/jetty9.2/pom.xml b/adapters/oidc/jetty/jetty9.2/pom.xml
index cdcf5a084a02..a5a34adacd9e 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../../pom.xml
4.0.0
diff --git a/adapters/oidc/jetty/jetty9.3/pom.xml b/adapters/oidc/jetty/jetty9.3/pom.xml
index 077a2b5936e7..bd1912548fee 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../../pom.xml
4.0.0
diff --git a/adapters/oidc/jetty/jetty9.4/pom.xml b/adapters/oidc/jetty/jetty9.4/pom.xml
index 16b235ed04f3..2479c58e8b0f 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../../pom.xml
4.0.0
diff --git a/adapters/oidc/jetty/pom.xml b/adapters/oidc/jetty/pom.xml
index bbac2c1a8c17..f17cccac54d7 100755
--- a/adapters/oidc/jetty/pom.xml
+++ b/adapters/oidc/jetty/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
Keycloak Jetty Integration
diff --git a/adapters/oidc/js/.gitignore b/adapters/oidc/js/.gitignore
new file mode 100644
index 000000000000..6b7940261b52
--- /dev/null
+++ b/adapters/oidc/js/.gitignore
@@ -0,0 +1,2 @@
+node
+node_modules
diff --git a/adapters/oidc/js/package-lock.json b/adapters/oidc/js/package-lock.json
new file mode 100644
index 000000000000..e0ac963a3e56
--- /dev/null
+++ b/adapters/oidc/js/package-lock.json
@@ -0,0 +1,1237 @@
+{
+ "name": "keycloak-js-adapter",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "keycloak-js-adapter",
+ "dependencies": {
+ "base64-js": "^1.5.1",
+ "es6-promise": "^4.2.8",
+ "js-sha256": "^0.9.0"
+ },
+ "devDependencies": {
+ "@rollup/plugin-commonjs": "^21.0.1",
+ "@rollup/plugin-inject": "^4.0.3",
+ "@rollup/plugin-node-resolve": "^13.1.1",
+ "@rollup/plugin-typescript": "^8.3.0",
+ "@types/node": "^17.0.5",
+ "rollup": "^2.62.0",
+ "rollup-plugin-terser": "^7.0.2",
+ "typescript": "^4.5.4"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz",
+ "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/highlight": "^7.16.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.15.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz",
+ "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz",
+ "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.15.7",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@rollup/plugin-commonjs": {
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-21.0.1.tgz",
+ "integrity": "sha512-EA+g22lbNJ8p5kuZJUYyhhDK7WgJckW5g4pNN7n4mAFUM96VuwUnNT3xr2Db2iCZPI1pJPbGyfT5mS9T1dHfMg==",
+ "dev": true,
+ "dependencies": {
+ "@rollup/pluginutils": "^3.1.0",
+ "commondir": "^1.0.1",
+ "estree-walker": "^2.0.1",
+ "glob": "^7.1.6",
+ "is-reference": "^1.2.1",
+ "magic-string": "^0.25.7",
+ "resolve": "^1.17.0"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^2.38.3"
+ }
+ },
+ "node_modules/@rollup/plugin-commonjs/node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dev": true
+ },
+ "node_modules/@rollup/plugin-inject": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-inject/-/plugin-inject-4.0.3.tgz",
+ "integrity": "sha512-lzMXmj0LZjd67MI+M8H9dk/oCxR0TYqYAdZ6ZOejWQLSUtud+FUPu4NCMAO8KyWWAalFo8ean7yFHCMvCNsCZw==",
+ "dev": true,
+ "dependencies": {
+ "@rollup/pluginutils": "^3.1.0",
+ "estree-walker": "^2.0.1",
+ "magic-string": "^0.25.7"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0 || ^2.0.0"
+ }
+ },
+ "node_modules/@rollup/plugin-inject/node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dev": true
+ },
+ "node_modules/@rollup/plugin-node-resolve": {
+ "version": "13.1.1",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.1.1.tgz",
+ "integrity": "sha512-6QKtRevXLrmEig9UiMYt2fSvee9TyltGRfw+qSs6xjUnxwjOzTOqy+/Lpxsgjb8mJn1EQNbCDAvt89O4uzL5kw==",
+ "dev": true,
+ "dependencies": {
+ "@rollup/pluginutils": "^3.1.0",
+ "@types/resolve": "1.17.1",
+ "builtin-modules": "^3.1.0",
+ "deepmerge": "^4.2.2",
+ "is-module": "^1.0.0",
+ "resolve": "^1.19.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^2.42.0"
+ }
+ },
+ "node_modules/@rollup/plugin-typescript": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.3.0.tgz",
+ "integrity": "sha512-I5FpSvLbtAdwJ+naznv+B4sjXZUcIvLLceYpITAn7wAP8W0wqc5noLdGIp9HGVntNhRWXctwPYrSSFQxtl0FPA==",
+ "dev": true,
+ "dependencies": {
+ "@rollup/pluginutils": "^3.1.0",
+ "resolve": "^1.17.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^2.14.0",
+ "tslib": "*",
+ "typescript": ">=3.7.0"
+ }
+ },
+ "node_modules/@rollup/pluginutils": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
+ "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "0.0.39",
+ "estree-walker": "^1.0.1",
+ "picomatch": "^2.2.2"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "0.0.39",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
+ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
+ "dev": true
+ },
+ "node_modules/@types/node": {
+ "version": "17.0.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.5.tgz",
+ "integrity": "sha512-w3mrvNXLeDYV1GKTZorGJQivK6XLCoGwpnyJFbJVK/aTBQUxOCaa/GlFAAN3OTDFcb7h5tiFG+YXCO2By+riZw==",
+ "dev": true
+ },
+ "node_modules/@types/resolve": {
+ "version": "1.17.1",
+ "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
+ "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "dev": true
+ },
+ "node_modules/builtin-modules": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz",
+ "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "dev": true
+ },
+ "node_modules/commondir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
+ "dev": true
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "node_modules/deepmerge": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
+ "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/es6-promise": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
+ "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
+ "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
+ "dev": true
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "node_modules/glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "node_modules/is-core-module": {
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz",
+ "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==",
+ "dev": true,
+ "dependencies": {
+ "has": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-module": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
+ "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=",
+ "dev": true
+ },
+ "node_modules/is-reference": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
+ "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "*"
+ }
+ },
+ "node_modules/jest-worker": {
+ "version": "26.6.2",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz",
+ "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^7.0.0"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ }
+ },
+ "node_modules/jest-worker/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jest-worker/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/js-sha256": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz",
+ "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA=="
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
+ "node_modules/magic-string": {
+ "version": "0.25.7",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
+ "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==",
+ "dev": true,
+ "dependencies": {
+ "sourcemap-codec": "^1.4.4"
+ }
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true
+ },
+ "node_modules/minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
+ "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dev": true,
+ "dependencies": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.20.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
+ "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.2.0",
+ "path-parse": "^1.0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "2.62.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.62.0.tgz",
+ "integrity": "sha512-cJEQq2gwB0GWMD3rYImefQTSjrPYaC6s4J9pYqnstVLJ1CHa/aZNVkD4Epuvg4iLeMA4KRiq7UM7awKK6j7jcw==",
+ "dev": true,
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/rollup-plugin-terser": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz",
+ "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.10.4",
+ "jest-worker": "^26.2.1",
+ "serialize-javascript": "^4.0.0",
+ "terser": "^5.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^2.0.0"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/serialize-javascript": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
+ "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
+ "dev": true,
+ "dependencies": {
+ "randombytes": "^2.1.0"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
+ "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/source-map-support": {
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "dev": true,
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/source-map-support/node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/sourcemap-codec": {
+ "version": "1.4.8",
+ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
+ "dev": true
+ },
+ "node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/terser": {
+ "version": "5.10.0",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
+ "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
+ "dev": true,
+ "dependencies": {
+ "commander": "^2.20.0",
+ "source-map": "~0.7.2",
+ "source-map-support": "~0.5.20"
+ },
+ "bin": {
+ "terser": "bin/terser"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "acorn": "^8.5.0"
+ },
+ "peerDependenciesMeta": {
+ "acorn": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
+ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
+ "dev": true,
+ "peer": true
+ },
+ "node_modules/typescript": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz",
+ "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ }
+ },
+ "dependencies": {
+ "@babel/code-frame": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz",
+ "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "^7.16.0"
+ }
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.15.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz",
+ "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==",
+ "dev": true
+ },
+ "@babel/highlight": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz",
+ "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.15.7",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "@rollup/plugin-commonjs": {
+ "version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-21.0.1.tgz",
+ "integrity": "sha512-EA+g22lbNJ8p5kuZJUYyhhDK7WgJckW5g4pNN7n4mAFUM96VuwUnNT3xr2Db2iCZPI1pJPbGyfT5mS9T1dHfMg==",
+ "dev": true,
+ "requires": {
+ "@rollup/pluginutils": "^3.1.0",
+ "commondir": "^1.0.1",
+ "estree-walker": "^2.0.1",
+ "glob": "^7.1.6",
+ "is-reference": "^1.2.1",
+ "magic-string": "^0.25.7",
+ "resolve": "^1.17.0"
+ },
+ "dependencies": {
+ "estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dev": true
+ }
+ }
+ },
+ "@rollup/plugin-inject": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-inject/-/plugin-inject-4.0.3.tgz",
+ "integrity": "sha512-lzMXmj0LZjd67MI+M8H9dk/oCxR0TYqYAdZ6ZOejWQLSUtud+FUPu4NCMAO8KyWWAalFo8ean7yFHCMvCNsCZw==",
+ "dev": true,
+ "requires": {
+ "@rollup/pluginutils": "^3.1.0",
+ "estree-walker": "^2.0.1",
+ "magic-string": "^0.25.7"
+ },
+ "dependencies": {
+ "estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dev": true
+ }
+ }
+ },
+ "@rollup/plugin-node-resolve": {
+ "version": "13.1.1",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.1.1.tgz",
+ "integrity": "sha512-6QKtRevXLrmEig9UiMYt2fSvee9TyltGRfw+qSs6xjUnxwjOzTOqy+/Lpxsgjb8mJn1EQNbCDAvt89O4uzL5kw==",
+ "dev": true,
+ "requires": {
+ "@rollup/pluginutils": "^3.1.0",
+ "@types/resolve": "1.17.1",
+ "builtin-modules": "^3.1.0",
+ "deepmerge": "^4.2.2",
+ "is-module": "^1.0.0",
+ "resolve": "^1.19.0"
+ }
+ },
+ "@rollup/plugin-typescript": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.3.0.tgz",
+ "integrity": "sha512-I5FpSvLbtAdwJ+naznv+B4sjXZUcIvLLceYpITAn7wAP8W0wqc5noLdGIp9HGVntNhRWXctwPYrSSFQxtl0FPA==",
+ "dev": true,
+ "requires": {
+ "@rollup/pluginutils": "^3.1.0",
+ "resolve": "^1.17.0"
+ }
+ },
+ "@rollup/pluginutils": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
+ "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
+ "dev": true,
+ "requires": {
+ "@types/estree": "0.0.39",
+ "estree-walker": "^1.0.1",
+ "picomatch": "^2.2.2"
+ }
+ },
+ "@types/estree": {
+ "version": "0.0.39",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
+ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
+ "dev": true
+ },
+ "@types/node": {
+ "version": "17.0.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.5.tgz",
+ "integrity": "sha512-w3mrvNXLeDYV1GKTZorGJQivK6XLCoGwpnyJFbJVK/aTBQUxOCaa/GlFAAN3OTDFcb7h5tiFG+YXCO2By+riZw==",
+ "dev": true
+ },
+ "@types/resolve": {
+ "version": "1.17.1",
+ "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
+ "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==",
+ "dev": true,
+ "requires": {
+ "@types/node": "*"
+ }
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "dev": true
+ },
+ "builtin-modules": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz",
+ "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==",
+ "dev": true
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "dev": true
+ },
+ "commondir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
+ "dev": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "deepmerge": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
+ "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
+ "dev": true
+ },
+ "es6-promise": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
+ "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true
+ },
+ "estree-walker": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
+ "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
+ "dev": true
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "requires": {
+ "function-bind": "^1.1.1"
+ }
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "is-core-module": {
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz",
+ "integrity": "sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==",
+ "dev": true,
+ "requires": {
+ "has": "^1.0.3"
+ }
+ },
+ "is-module": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
+ "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=",
+ "dev": true
+ },
+ "is-reference": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
+ "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
+ "dev": true,
+ "requires": {
+ "@types/estree": "*"
+ }
+ },
+ "jest-worker": {
+ "version": "26.6.2",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz",
+ "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==",
+ "dev": true,
+ "requires": {
+ "@types/node": "*",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^7.0.0"
+ },
+ "dependencies": {
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
+ "js-sha256": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz",
+ "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA=="
+ },
+ "js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
+ "magic-string": {
+ "version": "0.25.7",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
+ "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==",
+ "dev": true,
+ "requires": {
+ "sourcemap-codec": "^1.4.4"
+ }
+ },
+ "merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true
+ },
+ "path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
+ },
+ "picomatch": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
+ "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
+ "dev": true
+ },
+ "randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "resolve": {
+ "version": "1.20.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
+ "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
+ "dev": true,
+ "requires": {
+ "is-core-module": "^2.2.0",
+ "path-parse": "^1.0.6"
+ }
+ },
+ "rollup": {
+ "version": "2.62.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.62.0.tgz",
+ "integrity": "sha512-cJEQq2gwB0GWMD3rYImefQTSjrPYaC6s4J9pYqnstVLJ1CHa/aZNVkD4Epuvg4iLeMA4KRiq7UM7awKK6j7jcw==",
+ "dev": true,
+ "requires": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "rollup-plugin-terser": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz",
+ "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.10.4",
+ "jest-worker": "^26.2.1",
+ "serialize-javascript": "^4.0.0",
+ "terser": "^5.0.0"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true
+ },
+ "serialize-javascript": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
+ "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
+ "dev": true,
+ "requires": {
+ "randombytes": "^2.1.0"
+ }
+ },
+ "source-map": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
+ "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
+ "dev": true
+ },
+ "source-map-support": {
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "dev": true,
+ "requires": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "sourcemap-codec": {
+ "version": "1.4.8",
+ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ },
+ "terser": {
+ "version": "5.10.0",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
+ "integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
+ "dev": true,
+ "requires": {
+ "commander": "^2.20.0",
+ "source-map": "~0.7.2",
+ "source-map-support": "~0.5.20"
+ }
+ },
+ "tslib": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
+ "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
+ "dev": true,
+ "peer": true
+ },
+ "typescript": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz",
+ "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==",
+ "dev": true
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ }
+ }
+}
diff --git a/adapters/oidc/js/package.json b/adapters/oidc/js/package.json
new file mode 100644
index 000000000000..b58febff3a4a
--- /dev/null
+++ b/adapters/oidc/js/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "keycloak-js-adapter",
+ "private": true,
+ "scripts": {
+ "build": "rollup --config --configPlugin typescript"
+ },
+ "devDependencies": {
+ "@rollup/plugin-commonjs": "^21.0.1",
+ "@rollup/plugin-inject": "^4.0.3",
+ "@rollup/plugin-node-resolve": "^13.1.1",
+ "@rollup/plugin-typescript": "^8.3.0",
+ "@types/node": "^17.0.5",
+ "rollup": "^2.62.0",
+ "rollup-plugin-terser": "^7.0.2",
+ "typescript": "^4.5.4"
+ },
+ "dependencies": {
+ "base64-js": "^1.5.1",
+ "es6-promise": "^4.2.8",
+ "js-sha256": "^0.9.0"
+ }
+}
diff --git a/adapters/oidc/js/pom.xml b/adapters/oidc/js/pom.xml
index d41452f31bf2..c9cd3245cba5 100755
--- a/adapters/oidc/js/pom.xml
+++ b/adapters/oidc/js/pom.xml
@@ -16,75 +16,50 @@
~ limitations under the License.
-->
-
+
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
- ../../../pom.xml
+ 17.0.0-SNAPSHOT
+ ../../../pom.xml
4.0.0
keycloak-js-adapter
Keycloak JS Integration
-
-
-
-
-
-
- com.samaxes.maven
- minify-maven-plugin
-
- CLOSURE
- ECMASCRIPT5
- true
-
-
-
- min-js
- compile
-
- utf-8
- ${basedir}/src/main/resources
- .
-
- keycloak.js
-
-
- ${project.build.directory}/classes
- .
- keycloak.js
-
-
- minify
-
-
-
- min-authz-js
- compile
-
- utf-8
- ${basedir}/src/main/resources
- .
-
- keycloak-authz.js
-
-
- ${project.build.directory}/classes
- .
- keycloak-authz.js
-
-
- minify
-
-
-
-
+
+
+ com.github.eirslett
+ frontend-maven-plugin
+
+
+ install node and npm
+
+ install-node-and-npm
+
+
+
+ npm install
+
+ npm
+
+
+
+ npm run build
+
+ npm
+
+
+ run build
+
+
+
+
+ ${node.version}
+
+
-
diff --git a/adapters/oidc/js/rollup.config.ts b/adapters/oidc/js/rollup.config.ts
new file mode 100644
index 000000000000..6734c89a09ef
--- /dev/null
+++ b/adapters/oidc/js/rollup.config.ts
@@ -0,0 +1,81 @@
+import commonjs from "@rollup/plugin-commonjs";
+import inject from "@rollup/plugin-inject";
+import { nodeResolve } from "@rollup/plugin-node-resolve";
+import path from "node:path";
+import type { OutputOptions, RollupOptions } from "rollup";
+import { defineConfig } from "rollup";
+import { terser } from "rollup-plugin-terser";
+
+interface DefineOptionsArgs {
+ file: string;
+ name: string;
+ amdId: string;
+}
+
+function defineOptions({
+ file,
+ name,
+ amdId,
+}: DefineOptionsArgs): RollupOptions[] {
+ const sourceDir = "src/main/js";
+ const targetDir = "target/classes";
+ const commonOptions: RollupOptions = {
+ input: path.join(sourceDir, `${file}.js`),
+ plugins: [commonjs(), nodeResolve()],
+ };
+
+ const umdOutput: OutputOptions = {
+ format: "umd",
+ name,
+ amd: { id: amdId },
+ };
+
+ return [
+ // Modern ES module variant, with externalized dependencies.
+ {
+ ...commonOptions,
+ output: [
+ {
+ file: path.join(targetDir, `${file}.mjs`),
+ },
+ ],
+ external: ["base64-js", "js-sha256"],
+ },
+ // Legacy Universal Module Definition, or “UMD”, with inlined dependencies.
+ {
+ ...commonOptions,
+ output: [
+ {
+ ...umdOutput,
+ file: path.join(targetDir, `${file}.js`),
+ },
+ {
+ ...umdOutput,
+ file: path.join(targetDir, `${file}.min.js`),
+ sourcemap: true,
+ sourcemapExcludeSources: true,
+ plugins: [terser()],
+ },
+ ],
+ plugins: [
+ ...commonOptions.plugins,
+ inject({
+ Promise: ["es6-promise/dist/es6-promise.min.js", "Promise"],
+ }),
+ ],
+ },
+ ];
+}
+
+export default defineConfig([
+ ...defineOptions({
+ file: "keycloak",
+ name: "Keycloak",
+ amdId: "keycloak",
+ }),
+ ...defineOptions({
+ file: "keycloak-authz",
+ name: "KeycloakAuthorization",
+ amdId: "keycloak-authorization",
+ }),
+]);
diff --git a/adapters/oidc/js/src/main/js/keycloak-authz.js b/adapters/oidc/js/src/main/js/keycloak-authz.js
new file mode 100644
index 000000000000..9d83e4bc1210
--- /dev/null
+++ b/adapters/oidc/js/src/main/js/keycloak-authz.js
@@ -0,0 +1,221 @@
+/*
+ * 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.
+ *
+ */
+
+var KeycloakAuthorization = function (keycloak, options) {
+ var _instance = this;
+ this.rpt = null;
+
+ var resolve = function () {};
+ var reject = function () {};
+
+ // detects if browser supports promises
+ if (typeof Promise !== "undefined" && Promise.toString().indexOf("[native code]") !== -1) {
+ this.ready = new Promise(function (res, rej) {
+ resolve = res;
+ reject = rej;
+ });
+ }
+
+ this.init = function () {
+ var request = new XMLHttpRequest();
+
+ request.open('GET', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/.well-known/uma2-configuration');
+ request.onreadystatechange = function () {
+ if (request.readyState == 4) {
+ if (request.status == 200) {
+ _instance.config = JSON.parse(request.responseText);
+ resolve();
+ } else {
+ console.error('Could not obtain configuration from server.');
+ reject();
+ }
+ }
+ }
+
+ request.send(null);
+ };
+
+ /**
+ * This method enables client applications to better integrate with resource servers protected by a Keycloak
+ * policy enforcer using UMA protocol.
+ *
+ * The authorization request must be provided with a ticket.
+ */
+ this.authorize = function (authorizationRequest) {
+ this.then = function (onGrant, onDeny, onError) {
+ if (authorizationRequest && authorizationRequest.ticket) {
+ var request = new XMLHttpRequest();
+
+ request.open('POST', _instance.config.token_endpoint, true);
+ request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
+ request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token);
+
+ request.onreadystatechange = function () {
+ if (request.readyState == 4) {
+ var status = request.status;
+
+ if (status >= 200 && status < 300) {
+ var rpt = JSON.parse(request.responseText).access_token;
+ _instance.rpt = rpt;
+ onGrant(rpt);
+ } else if (status == 403) {
+ if (onDeny) {
+ onDeny();
+ } else {
+ console.error('Authorization request was denied by the server.');
+ }
+ } else {
+ if (onError) {
+ onError();
+ } else {
+ console.error('Could not obtain authorization data from server.');
+ }
+ }
+ }
+ };
+
+ var params = "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket&client_id=" + keycloak.clientId + "&ticket=" + authorizationRequest.ticket;
+
+ if (authorizationRequest.submitRequest != undefined) {
+ params += "&submit_request=" + authorizationRequest.submitRequest;
+ }
+
+ var metadata = authorizationRequest.metadata;
+
+ if (metadata) {
+ if (metadata.responseIncludeResourceName) {
+ params += "&response_include_resource_name=" + metadata.responseIncludeResourceName;
+ }
+ if (metadata.responsePermissionsLimit) {
+ params += "&response_permissions_limit=" + metadata.responsePermissionsLimit;
+ }
+ }
+
+ if (_instance.rpt && (authorizationRequest.incrementalAuthorization == undefined || authorizationRequest.incrementalAuthorization)) {
+ params += "&rpt=" + _instance.rpt;
+ }
+
+ request.send(params);
+ }
+ };
+
+ return this;
+ };
+
+ /**
+ * Obtains all entitlements from a Keycloak Server based on a given resourceServerId.
+ */
+ this.entitlement = function (resourceServerId, authorizationRequest) {
+ this.then = function (onGrant, onDeny, onError) {
+ var request = new XMLHttpRequest();
+
+ request.open('POST', _instance.config.token_endpoint, true);
+ request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
+ request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token);
+
+ request.onreadystatechange = function () {
+ if (request.readyState == 4) {
+ var status = request.status;
+
+ if (status >= 200 && status < 300) {
+ var rpt = JSON.parse(request.responseText).access_token;
+ _instance.rpt = rpt;
+ onGrant(rpt);
+ } else if (status == 403) {
+ if (onDeny) {
+ onDeny();
+ } else {
+ console.error('Authorization request was denied by the server.');
+ }
+ } else {
+ if (onError) {
+ onError();
+ } else {
+ console.error('Could not obtain authorization data from server.');
+ }
+ }
+ }
+ };
+
+ if (!authorizationRequest) {
+ authorizationRequest = {};
+ }
+
+ var params = "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket&client_id=" + keycloak.clientId;
+
+ if (authorizationRequest.claimToken) {
+ params += "&claim_token=" + authorizationRequest.claimToken;
+
+ if (authorizationRequest.claimTokenFormat) {
+ params += "&claim_token_format=" + authorizationRequest.claimTokenFormat;
+ }
+ }
+
+ params += "&audience=" + resourceServerId;
+
+ var permissions = authorizationRequest.permissions;
+
+ if (!permissions) {
+ permissions = [];
+ }
+
+ for (var i = 0; i < permissions.length; i++) {
+ var resource = permissions[i];
+ var permission = resource.id;
+
+ if (resource.scopes && resource.scopes.length > 0) {
+ permission += "#";
+ for (j = 0; j < resource.scopes.length; j++) {
+ var scope = resource.scopes[j];
+ if (permission.indexOf('#') != permission.length - 1) {
+ permission += ",";
+ }
+ permission += scope;
+ }
+ }
+
+ params += "&permission=" + permission;
+ }
+
+ var metadata = authorizationRequest.metadata;
+
+ if (metadata) {
+ if (metadata.responseIncludeResourceName) {
+ params += "&response_include_resource_name=" + metadata.responseIncludeResourceName;
+ }
+ if (metadata.responsePermissionsLimit) {
+ params += "&response_permissions_limit=" + metadata.responsePermissionsLimit;
+ }
+ }
+
+ if (_instance.rpt) {
+ params += "&rpt=" + _instance.rpt;
+ }
+
+ request.send(params);
+ };
+
+ return this;
+ };
+
+ this.init(this);
+
+ return this;
+};
+
+export default KeycloakAuthorization;
diff --git a/adapters/oidc/js/src/main/js/keycloak.js b/adapters/oidc/js/src/main/js/keycloak.js
new file mode 100755
index 000000000000..db95534becd8
--- /dev/null
+++ b/adapters/oidc/js/src/main/js/keycloak.js
@@ -0,0 +1,1721 @@
+/*
+ * 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.
+ */
+import base64 from 'base64-js';
+import { sha256 } from 'js-sha256';
+
+if (typeof Promise === 'undefined') {
+ throw Error('Keycloak requires an environment that supports Promises. Make sure that you include the appropriate polyfill.');
+}
+
+var loggedPromiseDeprecation = false;
+
+function logPromiseDeprecation() {
+ if (!loggedPromiseDeprecation) {
+ loggedPromiseDeprecation = true;
+ console.warn('[KEYCLOAK] Usage of legacy style promise methods such as `.error()` and `.success()` has been deprecated and support will be removed in future versions. Use standard style promise methods such as `.then() and `.catch()` instead.');
+ }
+}
+
+function Keycloak (config) {
+ if (!(this instanceof Keycloak)) {
+ return new Keycloak(config);
+ }
+
+ var kc = this;
+ var adapter;
+ var refreshQueue = [];
+ var callbackStorage;
+
+ var loginIframe = {
+ enable: true,
+ callbackList: [],
+ interval: 5
+ };
+
+ var scripts = document.getElementsByTagName('script');
+ for (var i = 0; i < scripts.length; i++) {
+ if ((scripts[i].src.indexOf('keycloak.js') !== -1 || scripts[i].src.indexOf('keycloak.min.js') !== -1) && scripts[i].src.indexOf('version=') !== -1) {
+ kc.iframeVersion = scripts[i].src.substring(scripts[i].src.indexOf('version=') + 8).split('&')[0];
+ }
+ }
+
+ var useNonce = true;
+ var logInfo = createLogger(console.info);
+ var logWarn = createLogger(console.warn);
+
+ kc.init = function (initOptions) {
+ kc.authenticated = false;
+
+ callbackStorage = createCallbackStorage();
+ var adapters = ['default', 'cordova', 'cordova-native'];
+
+ if (initOptions && adapters.indexOf(initOptions.adapter) > -1) {
+ adapter = loadAdapter(initOptions.adapter);
+ } else if (initOptions && typeof initOptions.adapter === "object") {
+ adapter = initOptions.adapter;
+ } else {
+ if (window.Cordova || window.cordova) {
+ adapter = loadAdapter('cordova');
+ } else {
+ adapter = loadAdapter();
+ }
+ }
+
+ if (initOptions) {
+ if (typeof initOptions.useNonce !== 'undefined') {
+ useNonce = initOptions.useNonce;
+ }
+
+ if (typeof initOptions.checkLoginIframe !== 'undefined') {
+ loginIframe.enable = initOptions.checkLoginIframe;
+ }
+
+ if (initOptions.checkLoginIframeInterval) {
+ loginIframe.interval = initOptions.checkLoginIframeInterval;
+ }
+
+ if (initOptions.onLoad === 'login-required') {
+ kc.loginRequired = true;
+ }
+
+ if (initOptions.responseMode) {
+ if (initOptions.responseMode === 'query' || initOptions.responseMode === 'fragment') {
+ kc.responseMode = initOptions.responseMode;
+ } else {
+ throw 'Invalid value for responseMode';
+ }
+ }
+
+ if (initOptions.flow) {
+ switch (initOptions.flow) {
+ case 'standard':
+ kc.responseType = 'code';
+ break;
+ case 'implicit':
+ kc.responseType = 'id_token token';
+ break;
+ case 'hybrid':
+ kc.responseType = 'code id_token token';
+ break;
+ default:
+ throw 'Invalid value for flow';
+ }
+ kc.flow = initOptions.flow;
+ }
+
+ if (initOptions.timeSkew != null) {
+ kc.timeSkew = initOptions.timeSkew;
+ }
+
+ if(initOptions.redirectUri) {
+ kc.redirectUri = initOptions.redirectUri;
+ }
+
+ if (initOptions.silentCheckSsoRedirectUri) {
+ kc.silentCheckSsoRedirectUri = initOptions.silentCheckSsoRedirectUri;
+ }
+
+ if (typeof initOptions.silentCheckSsoFallback === 'boolean') {
+ kc.silentCheckSsoFallback = initOptions.silentCheckSsoFallback;
+ } else {
+ kc.silentCheckSsoFallback = true;
+ }
+
+ if (initOptions.pkceMethod) {
+ if (initOptions.pkceMethod !== "S256") {
+ throw 'Invalid value for pkceMethod';
+ }
+ kc.pkceMethod = initOptions.pkceMethod;
+ }
+
+ if (typeof initOptions.enableLogging === 'boolean') {
+ kc.enableLogging = initOptions.enableLogging;
+ } else {
+ kc.enableLogging = false;
+ }
+
+ if (typeof initOptions.scope === 'string') {
+ kc.scope = initOptions.scope;
+ }
+
+ if (typeof initOptions.messageReceiveTimeout === 'number' && initOptions.messageReceiveTimeout > 0) {
+ kc.messageReceiveTimeout = initOptions.messageReceiveTimeout;
+ } else {
+ kc.messageReceiveTimeout = 10000;
+ }
+ }
+
+ if (!kc.responseMode) {
+ kc.responseMode = 'fragment';
+ }
+ if (!kc.responseType) {
+ kc.responseType = 'code';
+ kc.flow = 'standard';
+ }
+
+ var promise = createPromise();
+
+ var initPromise = createPromise();
+ initPromise.promise.then(function() {
+ kc.onReady && kc.onReady(kc.authenticated);
+ promise.setSuccess(kc.authenticated);
+ }).catch(function(error) {
+ promise.setError(error);
+ });
+
+ var configPromise = loadConfig(config);
+
+ function onLoad() {
+ var doLogin = function(prompt) {
+ if (!prompt) {
+ options.prompt = 'none';
+ }
+
+ kc.login(options).then(function () {
+ initPromise.setSuccess();
+ }).catch(function (error) {
+ initPromise.setError(error);
+ });
+ }
+
+ var checkSsoSilently = function() {
+ var ifrm = document.createElement("iframe");
+ var src = kc.createLoginUrl({prompt: 'none', redirectUri: kc.silentCheckSsoRedirectUri});
+ ifrm.setAttribute("src", src);
+ ifrm.setAttribute("title", "keycloak-silent-check-sso");
+ ifrm.style.display = "none";
+ document.body.appendChild(ifrm);
+
+ var messageCallback = function(event) {
+ if (event.origin !== window.location.origin || ifrm.contentWindow !== event.source) {
+ return;
+ }
+
+ var oauth = parseCallback(event.data);
+ processCallback(oauth, initPromise);
+
+ document.body.removeChild(ifrm);
+ window.removeEventListener("message", messageCallback);
+ };
+
+ window.addEventListener("message", messageCallback);
+ };
+
+ var options = {};
+ switch (initOptions.onLoad) {
+ case 'check-sso':
+ if (loginIframe.enable) {
+ setupCheckLoginIframe().then(function() {
+ checkLoginIframe().then(function (unchanged) {
+ if (!unchanged) {
+ kc.silentCheckSsoRedirectUri ? checkSsoSilently() : doLogin(false);
+ } else {
+ initPromise.setSuccess();
+ }
+ }).catch(function (error) {
+ initPromise.setError(error);
+ });
+ });
+ } else {
+ kc.silentCheckSsoRedirectUri ? checkSsoSilently() : doLogin(false);
+ }
+ break;
+ case 'login-required':
+ doLogin(true);
+ break;
+ default:
+ throw 'Invalid value for onLoad';
+ }
+ }
+
+ function processInit() {
+ var callback = parseCallback(window.location.href);
+
+ if (callback) {
+ window.history.replaceState(window.history.state, null, callback.newUrl);
+ }
+
+ if (callback && callback.valid) {
+ return setupCheckLoginIframe().then(function() {
+ processCallback(callback, initPromise);
+ }).catch(function (error) {
+ initPromise.setError(error);
+ });
+ } else if (initOptions) {
+ if (initOptions.token && initOptions.refreshToken) {
+ setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken);
+
+ if (loginIframe.enable) {
+ setupCheckLoginIframe().then(function() {
+ checkLoginIframe().then(function (unchanged) {
+ if (unchanged) {
+ kc.onAuthSuccess && kc.onAuthSuccess();
+ initPromise.setSuccess();
+ scheduleCheckIframe();
+ } else {
+ initPromise.setSuccess();
+ }
+ }).catch(function (error) {
+ initPromise.setError(error);
+ });
+ });
+ } else {
+ kc.updateToken(-1).then(function() {
+ kc.onAuthSuccess && kc.onAuthSuccess();
+ initPromise.setSuccess();
+ }).catch(function(error) {
+ kc.onAuthError && kc.onAuthError();
+ if (initOptions.onLoad) {
+ onLoad();
+ } else {
+ initPromise.setError(error);
+ }
+ });
+ }
+ } else if (initOptions.onLoad) {
+ onLoad();
+ } else {
+ initPromise.setSuccess();
+ }
+ } else {
+ initPromise.setSuccess();
+ }
+ }
+
+ 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 () {
+ domReady()
+ .then(check3pCookiesSupported)
+ .then(processInit)
+ .catch(function (error) {
+ promise.setError(error);
+ });
+ });
+ configPromise.catch(function (error) {
+ promise.setError(error);
+ });
+
+ return promise.promise;
+ }
+
+ kc.login = function (options) {
+ return adapter.login(options);
+ }
+
+ function generateRandomData(len) {
+ // use web crypto APIs if possible
+ var array = null;
+ var crypto = window.crypto || window.msCrypto;
+ if (crypto && crypto.getRandomValues && window.Uint8Array) {
+ array = new Uint8Array(len);
+ crypto.getRandomValues(array);
+ return array;
+ }
+
+ // fallback to Math random
+ array = new Array(len);
+ for (var j = 0; j < array.length; j++) {
+ array[j] = Math.floor(256 * Math.random());
+ }
+ return array;
+ }
+
+ function generateCodeVerifier(len) {
+ return generateRandomString(len, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789');
+ }
+
+ function generateRandomString(len, alphabet){
+ var randomData = generateRandomData(len);
+ var chars = new Array(len);
+ for (var i = 0; i < len; i++) {
+ chars[i] = alphabet.charCodeAt(randomData[i] % alphabet.length);
+ }
+ return String.fromCharCode.apply(null, chars);
+ }
+
+ function generatePkceChallenge(pkceMethod, codeVerifier) {
+ switch (pkceMethod) {
+ // The use of the "plain" method is considered insecure and therefore not supported.
+ case "S256":
+ // hash codeVerifier, then encode as url-safe base64 without padding
+ var hashBytes = new Uint8Array(sha256.arrayBuffer(codeVerifier));
+ var encodedHash = base64.fromByteArray(hashBytes)
+ .replace(/\+/g, '-')
+ .replace(/\//g, '_')
+ .replace(/\=/g, '');
+ return encodedHash;
+ default:
+ throw 'Invalid value for pkceMethod';
+ }
+ }
+
+ kc.createLoginUrl = function(options) {
+ var state = createUUID();
+ var nonce = createUUID();
+
+ var redirectUri = adapter.redirectUri(options);
+
+ var callbackState = {
+ state: state,
+ nonce: nonce,
+ redirectUri: encodeURIComponent(redirectUri)
+ };
+
+ if (options && options.prompt) {
+ callbackState.prompt = options.prompt;
+ }
+
+ var baseUrl;
+ if (options && options.action == 'register') {
+ baseUrl = kc.endpoints.register();
+ } else {
+ baseUrl = kc.endpoints.authorize();
+ }
+
+ var scope = options && options.scope || kc.scope;
+ if (!scope) {
+ // if scope is not set, default to "openid"
+ scope = "openid";
+ } else if (scope.indexOf("openid") === -1) {
+ // if openid scope is missing, prefix the given scopes with it
+ scope = "openid " + scope;
+ }
+
+ var url = baseUrl
+ + '?client_id=' + encodeURIComponent(kc.clientId)
+ + '&redirect_uri=' + encodeURIComponent(redirectUri)
+ + '&state=' + encodeURIComponent(state)
+ + '&response_mode=' + encodeURIComponent(kc.responseMode)
+ + '&response_type=' + encodeURIComponent(kc.responseType)
+ + '&scope=' + encodeURIComponent(scope);
+ if (useNonce) {
+ url = url + '&nonce=' + encodeURIComponent(nonce);
+ }
+
+ if (options && options.prompt) {
+ url += '&prompt=' + encodeURIComponent(options.prompt);
+ }
+
+ if (options && options.maxAge) {
+ url += '&max_age=' + encodeURIComponent(options.maxAge);
+ }
+
+ if (options && options.loginHint) {
+ url += '&login_hint=' + encodeURIComponent(options.loginHint);
+ }
+
+ if (options && options.idpHint) {
+ url += '&kc_idp_hint=' + encodeURIComponent(options.idpHint);
+ }
+
+ if (options && options.action && options.action != 'register') {
+ url += '&kc_action=' + encodeURIComponent(options.action);
+ }
+
+ if (options && options.locale) {
+ url += '&ui_locales=' + encodeURIComponent(options.locale);
+ }
+
+ if (kc.pkceMethod) {
+ var codeVerifier = generateCodeVerifier(96);
+ callbackState.pkceCodeVerifier = codeVerifier;
+ var pkceChallenge = generatePkceChallenge(kc.pkceMethod, codeVerifier);
+ url += '&code_challenge=' + pkceChallenge;
+ url += '&code_challenge_method=' + kc.pkceMethod;
+ }
+
+ callbackStorage.add(callbackState);
+
+ return url;
+ }
+
+ kc.logout = function(options) {
+ return adapter.logout(options);
+ }
+
+ kc.createLogoutUrl = function(options) {
+ var url = kc.endpoints.logout()
+ + '?redirect_uri=' + encodeURIComponent(adapter.redirectUri(options, false));
+
+ return url;
+ }
+
+ kc.register = function (options) {
+ return adapter.register(options);
+ }
+
+ kc.createRegisterUrl = function(options) {
+ if (!options) {
+ options = {};
+ }
+ options.action = 'register';
+ return kc.createLoginUrl(options);
+ }
+
+ kc.createAccountUrl = function(options) {
+ var realm = getRealmUrl();
+ var url = undefined;
+ if (typeof realm !== 'undefined') {
+ url = realm
+ + '/account'
+ + '?referrer=' + encodeURIComponent(kc.clientId)
+ + '&referrer_uri=' + encodeURIComponent(adapter.redirectUri(options));
+ }
+ return url;
+ }
+
+ kc.accountManagement = function() {
+ return adapter.accountManagement();
+ }
+
+ kc.hasRealmRole = function (role) {
+ var access = kc.realmAccess;
+ return !!access && access.roles.indexOf(role) >= 0;
+ }
+
+ kc.hasResourceRole = function(role, resource) {
+ if (!kc.resourceAccess) {
+ return false;
+ }
+
+ var access = kc.resourceAccess[resource || kc.clientId];
+ return !!access && access.roles.indexOf(role) >= 0;
+ }
+
+ kc.loadUserProfile = function() {
+ var url = getRealmUrl() + '/account';
+ var req = new XMLHttpRequest();
+ req.open('GET', url, true);
+ req.setRequestHeader('Accept', 'application/json');
+ req.setRequestHeader('Authorization', 'bearer ' + kc.token);
+
+ var promise = createPromise();
+
+ req.onreadystatechange = function () {
+ if (req.readyState == 4) {
+ if (req.status == 200) {
+ kc.profile = JSON.parse(req.responseText);
+ promise.setSuccess(kc.profile);
+ } else {
+ promise.setError();
+ }
+ }
+ }
+
+ req.send();
+
+ return promise.promise;
+ }
+
+ kc.loadUserInfo = function() {
+ var url = kc.endpoints.userinfo();
+ var req = new XMLHttpRequest();
+ req.open('GET', url, true);
+ req.setRequestHeader('Accept', 'application/json');
+ req.setRequestHeader('Authorization', 'bearer ' + kc.token);
+
+ var promise = createPromise();
+
+ req.onreadystatechange = function () {
+ if (req.readyState == 4) {
+ if (req.status == 200) {
+ kc.userInfo = JSON.parse(req.responseText);
+ promise.setSuccess(kc.userInfo);
+ } else {
+ promise.setError();
+ }
+ }
+ }
+
+ req.send();
+
+ return promise.promise;
+ }
+
+ kc.isTokenExpired = function(minValidity) {
+ if (!kc.tokenParsed || (!kc.refreshToken && kc.flow != 'implicit' )) {
+ throw 'Not authenticated';
+ }
+
+ if (kc.timeSkew == null) {
+ logInfo('[KEYCLOAK] Unable to determine if token is expired as timeskew is not set');
+ return true;
+ }
+
+ var expiresIn = kc.tokenParsed['exp'] - Math.ceil(new Date().getTime() / 1000) + kc.timeSkew;
+ if (minValidity) {
+ if (isNaN(minValidity)) {
+ throw 'Invalid minValidity';
+ }
+ expiresIn -= minValidity;
+ }
+ return expiresIn < 0;
+ }
+
+ kc.updateToken = function(minValidity) {
+ var promise = createPromise();
+
+ if (!kc.refreshToken) {
+ promise.setError();
+ return promise.promise;
+ }
+
+ minValidity = minValidity || 5;
+
+ var exec = function() {
+ var refreshToken = false;
+ if (minValidity == -1) {
+ refreshToken = true;
+ logInfo('[KEYCLOAK] Refreshing token: forced refresh');
+ } else if (!kc.tokenParsed || kc.isTokenExpired(minValidity)) {
+ refreshToken = true;
+ logInfo('[KEYCLOAK] Refreshing token: token expired');
+ }
+
+ if (!refreshToken) {
+ promise.setSuccess(false);
+ } else {
+ var params = 'grant_type=refresh_token&' + 'refresh_token=' + kc.refreshToken;
+ var url = kc.endpoints.token();
+
+ refreshQueue.push(promise);
+
+ if (refreshQueue.length == 1) {
+ var req = new XMLHttpRequest();
+ req.open('POST', url, true);
+ req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
+ req.withCredentials = true;
+
+ params += '&client_id=' + encodeURIComponent(kc.clientId);
+
+ var timeLocal = new Date().getTime();
+
+ req.onreadystatechange = function () {
+ if (req.readyState == 4) {
+ if (req.status == 200) {
+ logInfo('[KEYCLOAK] Token refreshed');
+
+ timeLocal = (timeLocal + new Date().getTime()) / 2;
+
+ var tokenResponse = JSON.parse(req.responseText);
+
+ setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], timeLocal);
+
+ kc.onAuthRefreshSuccess && kc.onAuthRefreshSuccess();
+ for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) {
+ p.setSuccess(true);
+ }
+ } else {
+ logWarn('[KEYCLOAK] Failed to refresh token');
+
+ if (req.status == 400) {
+ kc.clearToken();
+ }
+
+ kc.onAuthRefreshError && kc.onAuthRefreshError();
+ for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) {
+ p.setError(true);
+ }
+ }
+ }
+ };
+
+ req.send(params);
+ }
+ }
+ }
+
+ if (loginIframe.enable) {
+ var iframePromise = checkLoginIframe();
+ iframePromise.then(function() {
+ exec();
+ }).catch(function(error) {
+ promise.setError(error);
+ });
+ } else {
+ exec();
+ }
+
+ return promise.promise;
+ }
+
+ kc.clearToken = function() {
+ if (kc.token) {
+ setToken(null, null, null);
+ kc.onAuthLogout && kc.onAuthLogout();
+ if (kc.loginRequired) {
+ kc.login();
+ }
+ }
+ }
+
+ function getRealmUrl() {
+ if (typeof kc.authServerUrl !== 'undefined') {
+ if (kc.authServerUrl.charAt(kc.authServerUrl.length - 1) == '/') {
+ return kc.authServerUrl + 'realms/' + encodeURIComponent(kc.realm);
+ } else {
+ return kc.authServerUrl + '/realms/' + encodeURIComponent(kc.realm);
+ }
+ } else {
+ return undefined;
+ }
+ }
+
+ function getOrigin() {
+ if (!window.location.origin) {
+ return window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: '');
+ } else {
+ return window.location.origin;
+ }
+ }
+
+ function processCallback(oauth, promise) {
+ var code = oauth.code;
+ var error = oauth.error;
+ var prompt = oauth.prompt;
+
+ var timeLocal = new Date().getTime();
+
+ if (oauth['kc_action_status']) {
+ kc.onActionUpdate && kc.onActionUpdate(oauth['kc_action_status']);
+ }
+
+ if (error) {
+ if (prompt != 'none') {
+ var errorData = { error: error, error_description: oauth.error_description };
+ kc.onAuthError && kc.onAuthError(errorData);
+ promise && promise.setError(errorData);
+ } else {
+ promise && promise.setSuccess();
+ }
+ return;
+ } else if ((kc.flow != 'standard') && (oauth.access_token || oauth.id_token)) {
+ authSuccess(oauth.access_token, null, oauth.id_token, true);
+ }
+
+ if ((kc.flow != 'implicit') && code) {
+ var params = 'code=' + code + '&grant_type=authorization_code';
+ var url = kc.endpoints.token();
+
+ var req = new XMLHttpRequest();
+ req.open('POST', url, true);
+ req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
+
+ params += '&client_id=' + encodeURIComponent(kc.clientId);
+ params += '&redirect_uri=' + oauth.redirectUri;
+
+ if (oauth.pkceCodeVerifier) {
+ params += '&code_verifier=' + oauth.pkceCodeVerifier;
+ }
+
+ req.withCredentials = true;
+
+ req.onreadystatechange = function() {
+ if (req.readyState == 4) {
+ if (req.status == 200) {
+
+ var tokenResponse = JSON.parse(req.responseText);
+ authSuccess(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], kc.flow === 'standard');
+ scheduleCheckIframe();
+ } else {
+ kc.onAuthError && kc.onAuthError();
+ promise && promise.setError();
+ }
+ }
+ };
+
+ req.send(params);
+ }
+
+ function authSuccess(accessToken, refreshToken, idToken, fulfillPromise) {
+ timeLocal = (timeLocal + new Date().getTime()) / 2;
+
+ setToken(accessToken, refreshToken, idToken, timeLocal);
+
+ if (useNonce && ((kc.tokenParsed && kc.tokenParsed.nonce != oauth.storedNonce) ||
+ (kc.refreshTokenParsed && kc.refreshTokenParsed.nonce != oauth.storedNonce) ||
+ (kc.idTokenParsed && kc.idTokenParsed.nonce != oauth.storedNonce))) {
+
+ logInfo('[KEYCLOAK] Invalid nonce, clearing token');
+ kc.clearToken();
+ promise && promise.setError();
+ } else {
+ if (fulfillPromise) {
+ kc.onAuthSuccess && kc.onAuthSuccess();
+ promise && promise.setSuccess();
+ }
+ }
+ }
+
+ }
+
+ function loadConfig(url) {
+ var promise = createPromise();
+ var configUrl;
+
+ if (!config) {
+ configUrl = 'keycloak.json';
+ } else if (typeof config === 'string') {
+ configUrl = config;
+ }
+
+ function setupOidcEndoints(oidcConfiguration) {
+ if (! oidcConfiguration) {
+ kc.endpoints = {
+ authorize: function() {
+ return getRealmUrl() + '/protocol/openid-connect/auth';
+ },
+ token: function() {
+ return getRealmUrl() + '/protocol/openid-connect/token';
+ },
+ logout: function() {
+ return getRealmUrl() + '/protocol/openid-connect/logout';
+ },
+ checkSessionIframe: function() {
+ var src = getRealmUrl() + '/protocol/openid-connect/login-status-iframe.html';
+ if (kc.iframeVersion) {
+ src = src + '?version=' + kc.iframeVersion;
+ }
+ return src;
+ },
+ thirdPartyCookiesIframe: function() {
+ var src = getRealmUrl() + '/protocol/openid-connect/3p-cookies/step1.html';
+ if (kc.iframeVersion) {
+ src = src + '?version=' + kc.iframeVersion;
+ }
+ return src;
+ },
+ register: function() {
+ return getRealmUrl() + '/protocol/openid-connect/registrations';
+ },
+ userinfo: function() {
+ return getRealmUrl() + '/protocol/openid-connect/userinfo';
+ }
+ };
+ } else {
+ kc.endpoints = {
+ authorize: function() {
+ return oidcConfiguration.authorization_endpoint;
+ },
+ token: function() {
+ return oidcConfiguration.token_endpoint;
+ },
+ logout: function() {
+ if (!oidcConfiguration.end_session_endpoint) {
+ throw "Not supported by the OIDC server";
+ }
+ return oidcConfiguration.end_session_endpoint;
+ },
+ checkSessionIframe: function() {
+ if (!oidcConfiguration.check_session_iframe) {
+ throw "Not supported by the OIDC server";
+ }
+ return oidcConfiguration.check_session_iframe;
+ },
+ register: function() {
+ throw 'Redirection to "Register user" page not supported in standard OIDC mode';
+ },
+ userinfo: function() {
+ if (!oidcConfiguration.userinfo_endpoint) {
+ throw "Not supported by the OIDC server";
+ }
+ return oidcConfiguration.userinfo_endpoint;
+ }
+ }
+ }
+ }
+
+ if (configUrl) {
+ var req = new XMLHttpRequest();
+ req.open('GET', configUrl, true);
+ req.setRequestHeader('Accept', 'application/json');
+
+ req.onreadystatechange = function () {
+ if (req.readyState == 4) {
+ if (req.status == 200 || fileLoaded(req)) {
+ var config = JSON.parse(req.responseText);
+
+ kc.authServerUrl = config['auth-server-url'];
+ kc.realm = config['realm'];
+ kc.clientId = config['resource'];
+ setupOidcEndoints(null);
+ promise.setSuccess();
+ } else {
+ promise.setError();
+ }
+ }
+ };
+
+ req.send();
+ } else {
+ if (!config.clientId) {
+ throw 'clientId missing';
+ }
+
+ kc.clientId = config.clientId;
+
+ var oidcProvider = config['oidcProvider'];
+ if (!oidcProvider) {
+ if (!config['url']) {
+ var scripts = document.getElementsByTagName('script');
+ for (var i = 0; i < scripts.length; i++) {
+ if (scripts[i].src.match(/.*keycloak\.js/)) {
+ config.url = scripts[i].src.substr(0, scripts[i].src.indexOf('/js/keycloak.js'));
+ break;
+ }
+ }
+ }
+ if (!config.realm) {
+ throw 'realm missing';
+ }
+
+ kc.authServerUrl = config.url;
+ kc.realm = config.realm;
+ setupOidcEndoints(null);
+ promise.setSuccess();
+ } else {
+ if (typeof oidcProvider === 'string') {
+ var oidcProviderConfigUrl;
+ if (oidcProvider.charAt(oidcProvider.length - 1) == '/') {
+ oidcProviderConfigUrl = oidcProvider + '.well-known/openid-configuration';
+ } else {
+ oidcProviderConfigUrl = oidcProvider + '/.well-known/openid-configuration';
+ }
+ var req = new XMLHttpRequest();
+ req.open('GET', oidcProviderConfigUrl, true);
+ req.setRequestHeader('Accept', 'application/json');
+
+ req.onreadystatechange = function () {
+ if (req.readyState == 4) {
+ if (req.status == 200 || fileLoaded(req)) {
+ var oidcProviderConfig = JSON.parse(req.responseText);
+ setupOidcEndoints(oidcProviderConfig);
+ promise.setSuccess();
+ } else {
+ promise.setError();
+ }
+ }
+ };
+
+ req.send();
+ } else {
+ setupOidcEndoints(oidcProvider);
+ promise.setSuccess();
+ }
+ }
+ }
+
+ return promise.promise;
+ }
+
+ function fileLoaded(xhr) {
+ return xhr.status == 0 && xhr.responseText && xhr.responseURL.startsWith('file:');
+ }
+
+ function setToken(token, refreshToken, idToken, timeLocal) {
+ if (kc.tokenTimeoutHandle) {
+ clearTimeout(kc.tokenTimeoutHandle);
+ kc.tokenTimeoutHandle = null;
+ }
+
+ if (refreshToken) {
+ kc.refreshToken = refreshToken;
+ kc.refreshTokenParsed = decodeToken(refreshToken);
+ } else {
+ delete kc.refreshToken;
+ delete kc.refreshTokenParsed;
+ }
+
+ if (idToken) {
+ kc.idToken = idToken;
+ kc.idTokenParsed = decodeToken(idToken);
+ } else {
+ delete kc.idToken;
+ delete kc.idTokenParsed;
+ }
+
+ if (token) {
+ kc.token = token;
+ kc.tokenParsed = decodeToken(token);
+ kc.sessionId = kc.tokenParsed.session_state;
+ kc.authenticated = true;
+ kc.subject = kc.tokenParsed.sub;
+ kc.realmAccess = kc.tokenParsed.realm_access;
+ kc.resourceAccess = kc.tokenParsed.resource_access;
+
+ if (timeLocal) {
+ kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
+ }
+
+ if (kc.timeSkew != null) {
+ logInfo('[KEYCLOAK] Estimated time difference between browser and server is ' + kc.timeSkew + ' seconds');
+
+ if (kc.onTokenExpired) {
+ var expiresIn = (kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew) * 1000;
+ logInfo('[KEYCLOAK] Token expires in ' + Math.round(expiresIn / 1000) + ' s');
+ if (expiresIn <= 0) {
+ kc.onTokenExpired();
+ } else {
+ kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn);
+ }
+ }
+ }
+ } else {
+ delete kc.token;
+ delete kc.tokenParsed;
+ delete kc.subject;
+ delete kc.realmAccess;
+ delete kc.resourceAccess;
+
+ kc.authenticated = false;
+ }
+ }
+
+ function decodeToken(str) {
+ str = str.split('.')[1];
+
+ str = str.replace(/-/g, '+');
+ str = str.replace(/_/g, '/');
+ switch (str.length % 4) {
+ case 0:
+ break;
+ case 2:
+ str += '==';
+ break;
+ case 3:
+ str += '=';
+ break;
+ default:
+ throw 'Invalid token';
+ }
+
+ str = decodeURIComponent(escape(atob(str)));
+
+ str = JSON.parse(str);
+ return str;
+ }
+
+ function createUUID() {
+ var hexDigits = '0123456789abcdef';
+ var s = generateRandomString(36, hexDigits).split("");
+ s[14] = '4';
+ s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
+ s[8] = s[13] = s[18] = s[23] = '-';
+ var uuid = s.join('');
+ return uuid;
+ }
+
+ function parseCallback(url) {
+ var oauth = parseCallbackUrl(url);
+ if (!oauth) {
+ return;
+ }
+
+ var oauthState = callbackStorage.get(oauth.state);
+
+ if (oauthState) {
+ oauth.valid = true;
+ oauth.redirectUri = oauthState.redirectUri;
+ oauth.storedNonce = oauthState.nonce;
+ oauth.prompt = oauthState.prompt;
+ oauth.pkceCodeVerifier = oauthState.pkceCodeVerifier;
+ }
+
+ return oauth;
+ }
+
+ function parseCallbackUrl(url) {
+ var supportedParams;
+ switch (kc.flow) {
+ case 'standard':
+ supportedParams = ['code', 'state', 'session_state', 'kc_action_status'];
+ break;
+ case 'implicit':
+ supportedParams = ['access_token', 'token_type', 'id_token', 'state', 'session_state', 'expires_in', 'kc_action_status'];
+ break;
+ case 'hybrid':
+ supportedParams = ['access_token', 'token_type', 'id_token', 'code', 'state', 'session_state', 'expires_in', 'kc_action_status'];
+ break;
+ }
+
+ supportedParams.push('error');
+ supportedParams.push('error_description');
+ supportedParams.push('error_uri');
+
+ var queryIndex = url.indexOf('?');
+ var fragmentIndex = url.indexOf('#');
+
+ var newUrl;
+ var parsed;
+
+ if (kc.responseMode === 'query' && queryIndex !== -1) {
+ newUrl = url.substring(0, queryIndex);
+ parsed = parseCallbackParams(url.substring(queryIndex + 1, fragmentIndex !== -1 ? fragmentIndex : url.length), supportedParams);
+ if (parsed.paramsString !== '') {
+ newUrl += '?' + parsed.paramsString;
+ }
+ if (fragmentIndex !== -1) {
+ newUrl += url.substring(fragmentIndex);
+ }
+ } else if (kc.responseMode === 'fragment' && fragmentIndex !== -1) {
+ newUrl = url.substring(0, fragmentIndex);
+ parsed = parseCallbackParams(url.substring(fragmentIndex + 1), supportedParams);
+ if (parsed.paramsString !== '') {
+ newUrl += '#' + parsed.paramsString;
+ }
+ }
+
+ if (parsed && parsed.oauthParams) {
+ if (kc.flow === 'standard' || kc.flow === 'hybrid') {
+ if ((parsed.oauthParams.code || parsed.oauthParams.error) && parsed.oauthParams.state) {
+ parsed.oauthParams.newUrl = newUrl;
+ return parsed.oauthParams;
+ }
+ } else if (kc.flow === 'implicit') {
+ if ((parsed.oauthParams.access_token || parsed.oauthParams.error) && parsed.oauthParams.state) {
+ parsed.oauthParams.newUrl = newUrl;
+ return parsed.oauthParams;
+ }
+ }
+ }
+ }
+
+ function parseCallbackParams(paramsString, supportedParams) {
+ var p = paramsString.split('&');
+ var result = {
+ paramsString: '',
+ oauthParams: {}
+ }
+ for (var i = 0; i < p.length; i++) {
+ var split = p[i].indexOf("=");
+ var key = p[i].slice(0, split);
+ if (supportedParams.indexOf(key) !== -1) {
+ result.oauthParams[key] = p[i].slice(split + 1);
+ } else {
+ if (result.paramsString !== '') {
+ result.paramsString += '&';
+ }
+ result.paramsString += p[i];
+ }
+ }
+ return result;
+ }
+
+ function createPromise() {
+ // Need to create a native Promise which also preserves the
+ // interface of the custom promise type previously used by the API
+ var p = {
+ setSuccess: function(result) {
+ p.resolve(result);
+ },
+
+ setError: function(result) {
+ p.reject(result);
+ }
+ };
+ 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;
+ }
+
+ // Function to extend existing native Promise with timeout
+ function applyTimeoutToPromise(promise, timeout, errorMessage) {
+ var timeoutHandle = null;
+ var timeoutPromise = new Promise(function (resolve, reject) {
+ timeoutHandle = setTimeout(function () {
+ reject({ "error": errorMessage || "Promise is not settled within timeout of " + timeout + "ms" });
+ }, timeout);
+ });
+
+ return Promise.race([promise, timeoutPromise]).finally(function () {
+ clearTimeout(timeoutHandle);
+ });
+ }
+
+ function setupCheckLoginIframe() {
+ var promise = createPromise();
+
+ if (!loginIframe.enable) {
+ promise.setSuccess();
+ return promise.promise;
+ }
+
+ if (loginIframe.iframe) {
+ promise.setSuccess();
+ return promise.promise;
+ }
+
+ var iframe = document.createElement('iframe');
+ loginIframe.iframe = iframe;
+
+ iframe.onload = function() {
+ var authUrl = kc.endpoints.authorize();
+ if (authUrl.charAt(0) === '/') {
+ loginIframe.iframeOrigin = getOrigin();
+ } else {
+ loginIframe.iframeOrigin = authUrl.substring(0, authUrl.indexOf('/', 8));
+ }
+ promise.setSuccess();
+ }
+
+ var src = kc.endpoints.checkSessionIframe();
+ iframe.setAttribute('src', src );
+ iframe.setAttribute('title', 'keycloak-session-iframe' );
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+
+ var messageCallback = function(event) {
+ if ((event.origin !== loginIframe.iframeOrigin) || (loginIframe.iframe.contentWindow !== event.source)) {
+ return;
+ }
+
+ if (!(event.data == 'unchanged' || event.data == 'changed' || event.data == 'error')) {
+ return;
+ }
+
+
+ if (event.data != 'unchanged') {
+ kc.clearToken();
+ }
+
+ var callbacks = loginIframe.callbackList.splice(0, loginIframe.callbackList.length);
+
+ for (var i = callbacks.length - 1; i >= 0; --i) {
+ var promise = callbacks[i];
+ if (event.data == 'error') {
+ promise.setError();
+ } else {
+ promise.setSuccess(event.data == 'unchanged');
+ }
+ }
+ };
+
+ window.addEventListener('message', messageCallback, false);
+
+ return promise.promise;
+ }
+
+ function scheduleCheckIframe() {
+ if (loginIframe.enable) {
+ if (kc.token) {
+ setTimeout(function() {
+ checkLoginIframe().then(function(unchanged) {
+ if (unchanged) {
+ scheduleCheckIframe();
+ }
+ });
+ }, loginIframe.interval * 1000);
+ }
+ }
+ }
+
+ function checkLoginIframe() {
+ var promise = createPromise();
+
+ if (loginIframe.iframe && loginIframe.iframeOrigin ) {
+ var msg = kc.clientId + ' ' + (kc.sessionId ? kc.sessionId : '');
+ loginIframe.callbackList.push(promise);
+ var origin = loginIframe.iframeOrigin;
+ if (loginIframe.callbackList.length == 1) {
+ loginIframe.iframe.contentWindow.postMessage(msg, origin);
+ }
+ } else {
+ promise.setSuccess();
+ }
+
+ return promise.promise;
+ }
+
+ function check3pCookiesSupported() {
+ var promise = createPromise();
+
+ if (loginIframe.enable || kc.silentCheckSsoRedirectUri) {
+ var iframe = document.createElement('iframe');
+ iframe.setAttribute('src', kc.endpoints.thirdPartyCookiesIframe());
+ iframe.setAttribute('title', 'keycloak-3p-check-iframe' );
+ iframe.style.display = 'none';
+ document.body.appendChild(iframe);
+
+ var messageCallback = function(event) {
+ if (iframe.contentWindow !== event.source) {
+ return;
+ }
+
+ if (event.data !== "supported" && event.data !== "unsupported") {
+ return;
+ } else if (event.data === "unsupported") {
+ loginIframe.enable = false;
+ if (kc.silentCheckSsoFallback) {
+ kc.silentCheckSsoRedirectUri = false;
+ }
+ logWarn("[KEYCLOAK] 3rd party cookies aren't supported by this browser. checkLoginIframe and " +
+ "silent check-sso are not available.")
+ }
+
+ document.body.removeChild(iframe);
+ window.removeEventListener("message", messageCallback);
+ promise.setSuccess();
+ };
+
+ window.addEventListener('message', messageCallback, false);
+ } else {
+ promise.setSuccess();
+ }
+
+ return applyTimeoutToPromise(promise.promise, kc.messageReceiveTimeout, "Timeout when waiting for 3rd party check iframe message.");
+ }
+
+ function loadAdapter(type) {
+ if (!type || type == 'default') {
+ return {
+ login: function(options) {
+ window.location.replace(kc.createLoginUrl(options));
+ return createPromise().promise;
+ },
+
+ logout: function(options) {
+ window.location.replace(kc.createLogoutUrl(options));
+ return createPromise().promise;
+ },
+
+ register: function(options) {
+ window.location.replace(kc.createRegisterUrl(options));
+ return createPromise().promise;
+ },
+
+ accountManagement : function() {
+ var accountUrl = kc.createAccountUrl();
+ if (typeof accountUrl !== 'undefined') {
+ window.location.href = accountUrl;
+ } else {
+ throw "Not supported by the OIDC server";
+ }
+ return createPromise().promise;
+ },
+
+ redirectUri: function(options, encodeHash) {
+ if (arguments.length == 1) {
+ encodeHash = true;
+ }
+
+ if (options && options.redirectUri) {
+ return options.redirectUri;
+ } else if (kc.redirectUri) {
+ return kc.redirectUri;
+ } else {
+ return location.href;
+ }
+ }
+ };
+ }
+
+ if (type == 'cordova') {
+ loginIframe.enable = false;
+ var cordovaOpenWindowWrapper = function(loginUrl, target, options) {
+ if (window.cordova && window.cordova.InAppBrowser) {
+ // Use inappbrowser for IOS and Android if available
+ return window.cordova.InAppBrowser.open(loginUrl, target, options);
+ } else {
+ return window.open(loginUrl, target, options);
+ }
+ };
+
+ var shallowCloneCordovaOptions = function (userOptions) {
+ if (userOptions && userOptions.cordovaOptions) {
+ return Object.keys(userOptions.cordovaOptions).reduce(function (options, optionName) {
+ options[optionName] = userOptions.cordovaOptions[optionName];
+ return options;
+ }, {});
+ } else {
+ return {};
+ }
+ };
+
+ var formatCordovaOptions = function (cordovaOptions) {
+ return Object.keys(cordovaOptions).reduce(function (options, optionName) {
+ options.push(optionName+"="+cordovaOptions[optionName]);
+ return options;
+ }, []).join(",");
+ };
+
+ var createCordovaOptions = function (userOptions) {
+ var cordovaOptions = shallowCloneCordovaOptions(userOptions);
+ cordovaOptions.location = 'no';
+ if (userOptions && userOptions.prompt == 'none') {
+ cordovaOptions.hidden = 'yes';
+ }
+ return formatCordovaOptions(cordovaOptions);
+ };
+
+ return {
+ login: function(options) {
+ var promise = createPromise();
+
+ var cordovaOptions = createCordovaOptions(options);
+ var loginUrl = kc.createLoginUrl(options);
+ var ref = cordovaOpenWindowWrapper(loginUrl, '_blank', cordovaOptions);
+ var completed = false;
+
+ var closed = false;
+ var closeBrowser = function() {
+ closed = true;
+ ref.close();
+ };
+
+ ref.addEventListener('loadstart', function(event) {
+ if (event.url.indexOf('http://localhost') == 0) {
+ var callback = parseCallback(event.url);
+ processCallback(callback, promise);
+ closeBrowser();
+ completed = true;
+ }
+ });
+
+ ref.addEventListener('loaderror', function(event) {
+ if (!completed) {
+ if (event.url.indexOf('http://localhost') == 0) {
+ var callback = parseCallback(event.url);
+ processCallback(callback, promise);
+ closeBrowser();
+ completed = true;
+ } else {
+ promise.setError();
+ closeBrowser();
+ }
+ }
+ });
+
+ ref.addEventListener('exit', function(event) {
+ if (!closed) {
+ promise.setError({
+ reason: "closed_by_user"
+ });
+ }
+ });
+
+ return promise.promise;
+ },
+
+ logout: function(options) {
+ var promise = createPromise();
+
+ var logoutUrl = kc.createLogoutUrl(options);
+ var ref = cordovaOpenWindowWrapper(logoutUrl, '_blank', 'location=no,hidden=yes,clearcache=yes');
+
+ var error;
+
+ ref.addEventListener('loadstart', function(event) {
+ if (event.url.indexOf('http://localhost') == 0) {
+ ref.close();
+ }
+ });
+
+ ref.addEventListener('loaderror', function(event) {
+ if (event.url.indexOf('http://localhost') == 0) {
+ ref.close();
+ } else {
+ error = true;
+ ref.close();
+ }
+ });
+
+ ref.addEventListener('exit', function(event) {
+ if (error) {
+ promise.setError();
+ } else {
+ kc.clearToken();
+ promise.setSuccess();
+ }
+ });
+
+ return promise.promise;
+ },
+
+ register : function(options) {
+ var promise = createPromise();
+ var registerUrl = kc.createRegisterUrl();
+ var cordovaOptions = createCordovaOptions(options);
+ var ref = cordovaOpenWindowWrapper(registerUrl, '_blank', cordovaOptions);
+ ref.addEventListener('loadstart', function(event) {
+ if (event.url.indexOf('http://localhost') == 0) {
+ ref.close();
+ var oauth = parseCallback(event.url);
+ processCallback(oauth, promise);
+ }
+ });
+ return promise.promise;
+ },
+
+ accountManagement : function() {
+ var accountUrl = kc.createAccountUrl();
+ if (typeof accountUrl !== 'undefined') {
+ var ref = cordovaOpenWindowWrapper(accountUrl, '_blank', 'location=no');
+ ref.addEventListener('loadstart', function(event) {
+ if (event.url.indexOf('http://localhost') == 0) {
+ ref.close();
+ }
+ });
+ } else {
+ throw "Not supported by the OIDC server";
+ }
+ },
+
+ redirectUri: function(options) {
+ return 'http://localhost';
+ }
+ }
+ }
+
+ if (type == 'cordova-native') {
+ loginIframe.enable = false;
+
+ return {
+ login: function(options) {
+ var promise = createPromise();
+ var loginUrl = kc.createLoginUrl(options);
+
+ universalLinks.subscribe('keycloak', function(event) {
+ universalLinks.unsubscribe('keycloak');
+ window.cordova.plugins.browsertab.close();
+ var oauth = parseCallback(event.url);
+ processCallback(oauth, promise);
+ });
+
+ window.cordova.plugins.browsertab.openUrl(loginUrl);
+ return promise.promise;
+ },
+
+ logout: function(options) {
+ var promise = createPromise();
+ var logoutUrl = kc.createLogoutUrl(options);
+
+ universalLinks.subscribe('keycloak', function(event) {
+ universalLinks.unsubscribe('keycloak');
+ window.cordova.plugins.browsertab.close();
+ kc.clearToken();
+ promise.setSuccess();
+ });
+
+ window.cordova.plugins.browsertab.openUrl(logoutUrl);
+ return promise.promise;
+ },
+
+ register : function(options) {
+ var promise = createPromise();
+ var registerUrl = kc.createRegisterUrl(options);
+ universalLinks.subscribe('keycloak' , function(event) {
+ universalLinks.unsubscribe('keycloak');
+ window.cordova.plugins.browsertab.close();
+ var oauth = parseCallback(event.url);
+ processCallback(oauth, promise);
+ });
+ window.cordova.plugins.browsertab.openUrl(registerUrl);
+ return promise.promise;
+
+ },
+
+ accountManagement : function() {
+ var accountUrl = kc.createAccountUrl();
+ if (typeof accountUrl !== 'undefined') {
+ window.cordova.plugins.browsertab.openUrl(accountUrl);
+ } else {
+ throw "Not supported by the OIDC server";
+ }
+ },
+
+ redirectUri: function(options) {
+ if (options && options.redirectUri) {
+ return options.redirectUri;
+ } else if (kc.redirectUri) {
+ return kc.redirectUri;
+ } else {
+ return "http://localhost";
+ }
+ }
+ }
+ }
+
+ throw 'invalid adapter type: ' + type;
+ }
+
+ var LocalStorage = function() {
+ if (!(this instanceof LocalStorage)) {
+ return new LocalStorage();
+ }
+
+ localStorage.setItem('kc-test', 'test');
+ localStorage.removeItem('kc-test');
+
+ var cs = this;
+
+ function clearExpired() {
+ var time = new Date().getTime();
+ for (var i = 0; i < localStorage.length; i++) {
+ var key = localStorage.key(i);
+ if (key && key.indexOf('kc-callback-') == 0) {
+ var value = localStorage.getItem(key);
+ if (value) {
+ try {
+ var expires = JSON.parse(value).expires;
+ if (!expires || expires < time) {
+ localStorage.removeItem(key);
+ }
+ } catch (err) {
+ localStorage.removeItem(key);
+ }
+ }
+ }
+ }
+ }
+
+ cs.get = function(state) {
+ if (!state) {
+ return;
+ }
+
+ var key = 'kc-callback-' + state;
+ var value = localStorage.getItem(key);
+ if (value) {
+ localStorage.removeItem(key);
+ value = JSON.parse(value);
+ }
+
+ clearExpired();
+ return value;
+ };
+
+ cs.add = function(state) {
+ clearExpired();
+
+ var key = 'kc-callback-' + state.state;
+ state.expires = new Date().getTime() + (60 * 60 * 1000);
+ localStorage.setItem(key, JSON.stringify(state));
+ };
+ };
+
+ var CookieStorage = function() {
+ if (!(this instanceof CookieStorage)) {
+ return new CookieStorage();
+ }
+
+ var cs = this;
+
+ cs.get = function(state) {
+ if (!state) {
+ return;
+ }
+
+ var value = getCookie('kc-callback-' + state);
+ setCookie('kc-callback-' + state, '', cookieExpiration(-100));
+ if (value) {
+ return JSON.parse(value);
+ }
+ };
+
+ cs.add = function(state) {
+ setCookie('kc-callback-' + state.state, JSON.stringify(state), cookieExpiration(60));
+ };
+
+ cs.removeItem = function(key) {
+ setCookie(key, '', cookieExpiration(-100));
+ };
+
+ var cookieExpiration = function (minutes) {
+ var exp = new Date();
+ exp.setTime(exp.getTime() + (minutes*60*1000));
+ return exp;
+ };
+
+ var getCookie = function (key) {
+ var name = key + '=';
+ var ca = document.cookie.split(';');
+ for (var i = 0; i < ca.length; i++) {
+ var c = ca[i];
+ while (c.charAt(0) == ' ') {
+ c = c.substring(1);
+ }
+ if (c.indexOf(name) == 0) {
+ return c.substring(name.length, c.length);
+ }
+ }
+ return '';
+ };
+
+ var setCookie = function (key, value, expirationDate) {
+ var cookie = key + '=' + value + '; '
+ + 'expires=' + expirationDate.toUTCString() + '; ';
+ document.cookie = cookie;
+ }
+ };
+
+ function createCallbackStorage() {
+ try {
+ return new LocalStorage();
+ } catch (err) {
+ }
+
+ return new CookieStorage();
+ }
+
+ function createLogger(fn) {
+ return function() {
+ if (kc.enableLogging) {
+ fn.apply(console, Array.prototype.slice.call(arguments));
+ }
+ };
+ }
+}
+
+export default Keycloak;
diff --git a/adapters/oidc/js/src/main/resources/keycloak-authz.js b/adapters/oidc/js/src/main/resources/keycloak-authz.js
deleted file mode 100644
index df60747641fe..000000000000
--- a/adapters/oidc/js/src/main/resources/keycloak-authz.js
+++ /dev/null
@@ -1,232 +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.
- *
- */
-
-(function( window, undefined ) {
-
- var KeycloakAuthorization = function (keycloak, options) {
- var _instance = this;
- this.rpt = null;
-
- var resolve = function () {};
- var reject = function () {};
-
- // detects if browser supports promises
- if (typeof Promise !== "undefined" && Promise.toString().indexOf("[native code]") !== -1) {
- this.ready = new Promise(function (res, rej) {
- resolve = res;
- reject = rej;
- });
- }
-
- this.init = function () {
- var request = new XMLHttpRequest();
-
- request.open('GET', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/.well-known/uma2-configuration');
- request.onreadystatechange = function () {
- if (request.readyState == 4) {
- if (request.status == 200) {
- _instance.config = JSON.parse(request.responseText);
- resolve();
- } else {
- console.error('Could not obtain configuration from server.');
- reject();
- }
- }
- }
-
- request.send(null);
- };
-
- /**
- * This method enables client applications to better integrate with resource servers protected by a Keycloak
- * policy enforcer using UMA protocol.
- *
- * The authorization request must be provided with a ticket.
- */
- this.authorize = function (authorizationRequest) {
- this.then = function (onGrant, onDeny, onError) {
- if (authorizationRequest && authorizationRequest.ticket) {
- var request = new XMLHttpRequest();
-
- request.open('POST', _instance.config.token_endpoint, true);
- request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
- request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token);
-
- request.onreadystatechange = function () {
- if (request.readyState == 4) {
- var status = request.status;
-
- if (status >= 200 && status < 300) {
- var rpt = JSON.parse(request.responseText).access_token;
- _instance.rpt = rpt;
- onGrant(rpt);
- } else if (status == 403) {
- if (onDeny) {
- onDeny();
- } else {
- console.error('Authorization request was denied by the server.');
- }
- } else {
- if (onError) {
- onError();
- } else {
- console.error('Could not obtain authorization data from server.');
- }
- }
- }
- };
-
- var params = "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket&client_id=" + keycloak.clientId + "&ticket=" + authorizationRequest.ticket;
-
- if (authorizationRequest.submitRequest != undefined) {
- params += "&submit_request=" + authorizationRequest.submitRequest;
- }
-
- var metadata = authorizationRequest.metadata;
-
- if (metadata) {
- if (metadata.responseIncludeResourceName) {
- params += "&response_include_resource_name=" + metadata.responseIncludeResourceName;
- }
- if (metadata.responsePermissionsLimit) {
- params += "&response_permissions_limit=" + metadata.responsePermissionsLimit;
- }
- }
-
- if (_instance.rpt && (authorizationRequest.incrementalAuthorization == undefined || authorizationRequest.incrementalAuthorization)) {
- params += "&rpt=" + _instance.rpt;
- }
-
- request.send(params);
- }
- };
-
- return this;
- };
-
- /**
- * Obtains all entitlements from a Keycloak Server based on a given resourceServerId.
- */
- this.entitlement = function (resourceServerId, authorizationRequest) {
- this.then = function (onGrant, onDeny, onError) {
- var request = new XMLHttpRequest();
-
- request.open('POST', _instance.config.token_endpoint, true);
- request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
- request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token);
-
- request.onreadystatechange = function () {
- if (request.readyState == 4) {
- var status = request.status;
-
- if (status >= 200 && status < 300) {
- var rpt = JSON.parse(request.responseText).access_token;
- _instance.rpt = rpt;
- onGrant(rpt);
- } else if (status == 403) {
- if (onDeny) {
- onDeny();
- } else {
- console.error('Authorization request was denied by the server.');
- }
- } else {
- if (onError) {
- onError();
- } else {
- console.error('Could not obtain authorization data from server.');
- }
- }
- }
- };
-
- if (!authorizationRequest) {
- authorizationRequest = {};
- }
-
- var params = "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket&client_id=" + keycloak.clientId;
-
- if (authorizationRequest.claimToken) {
- params += "&claim_token=" + authorizationRequest.claimToken;
-
- if (authorizationRequest.claimTokenFormat) {
- params += "&claim_token_format=" + authorizationRequest.claimTokenFormat;
- }
- }
-
- params += "&audience=" + resourceServerId;
-
- var permissions = authorizationRequest.permissions;
-
- if (!permissions) {
- permissions = [];
- }
-
- for (i = 0; i < permissions.length; i++) {
- var resource = permissions[i];
- var permission = resource.id;
-
- if (resource.scopes && resource.scopes.length > 0) {
- permission += "#";
- for (j = 0; j < resource.scopes.length; j++) {
- var scope = resource.scopes[j];
- if (permission.indexOf('#') != permission.length - 1) {
- permission += ",";
- }
- permission += scope;
- }
- }
-
- params += "&permission=" + permission;
- }
-
- var metadata = authorizationRequest.metadata;
-
- if (metadata) {
- if (metadata.responseIncludeResourceName) {
- params += "&response_include_resource_name=" + metadata.responseIncludeResourceName;
- }
- if (metadata.responsePermissionsLimit) {
- params += "&response_permissions_limit=" + metadata.responsePermissionsLimit;
- }
- }
-
- if (_instance.rpt) {
- params += "&rpt=" + _instance.rpt;
- }
-
- request.send(params);
- };
-
- return this;
- };
-
- this.init(this);
-
- return this;
- };
-
- if ( typeof module === "object" && module && typeof module.exports === "object" ) {
- module.exports = KeycloakAuthorization;
- } else {
- window.KeycloakAuthorization = KeycloakAuthorization;
-
- if ( typeof define === "function" && define.amd ) {
- define( "keycloak-authorization", [], function () { return KeycloakAuthorization; } );
- }
- }
-})( window );
\ No newline at end of file
diff --git a/adapters/oidc/js/src/main/resources/keycloak.js b/adapters/oidc/js/src/main/resources/keycloak.js
deleted file mode 100755
index 936a3c3c3620..000000000000
--- a/adapters/oidc/js/src/main/resources/keycloak.js
+++ /dev/null
@@ -1,1766 +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.
- */
-
-(function(root, factory) {
- if ( typeof exports === 'object' ) {
- if ( typeof module === 'object' ) {
- module.exports = factory( require("js-sha256"), require("base64-js") );
- } else {
- exports["keycloak"] = factory( require("js-sha256"), require("base64-js") );
- }
- } else {
- /**
- * [js-sha256]{@link https://github.com/emn178/js-sha256}
- *
- * @version 0.9.0
- * @author Chen, Yi-Cyuan [emn178@gmail.com]
- * @copyright Chen, Yi-Cyuan 2014-2017
- * @license MIT
- */
- !function () { "use strict"; function t(t, i) { i ? (d[0] = d[16] = d[1] = d[2] = d[3] = d[4] = d[5] = d[6] = d[7] = d[8] = d[9] = d[10] = d[11] = d[12] = d[13] = d[14] = d[15] = 0, this.blocks = d) : this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], t ? (this.h0 = 3238371032, this.h1 = 914150663, this.h2 = 812702999, this.h3 = 4144912697, this.h4 = 4290775857, this.h5 = 1750603025, this.h6 = 1694076839, this.h7 = 3204075428) : (this.h0 = 1779033703, this.h1 = 3144134277, this.h2 = 1013904242, this.h3 = 2773480762, this.h4 = 1359893119, this.h5 = 2600822924, this.h6 = 528734635, this.h7 = 1541459225), this.block = this.start = this.bytes = this.hBytes = 0, this.finalized = this.hashed = !1, this.first = !0, this.is224 = t } function i(i, r, s) { var e, n = typeof i; if ("string" === n) { var o, a = [], u = i.length, c = 0; for (e = 0; e < u; ++e)(o = i.charCodeAt(e)) < 128 ? a[c++] = o : o < 2048 ? (a[c++] = 192 | o >> 6, a[c++] = 128 | 63 & o) : o < 55296 || o >= 57344 ? (a[c++] = 224 | o >> 12, a[c++] = 128 | o >> 6 & 63, a[c++] = 128 | 63 & o) : (o = 65536 + ((1023 & o) << 10 | 1023 & i.charCodeAt(++e)), a[c++] = 240 | o >> 18, a[c++] = 128 | o >> 12 & 63, a[c++] = 128 | o >> 6 & 63, a[c++] = 128 | 63 & o); i = a } else { if ("object" !== n) throw new Error(h); if (null === i) throw new Error(h); if (f && i.constructor === ArrayBuffer) i = new Uint8Array(i); else if (!(Array.isArray(i) || f && ArrayBuffer.isView(i))) throw new Error(h) } i.length > 64 && (i = new t(r, !0).update(i).array()); var y = [], p = []; for (e = 0; e < 64; ++e) { var l = i[e] || 0; y[e] = 92 ^ l, p[e] = 54 ^ l } t.call(this, r, s), this.update(p), this.oKeyPad = y, this.inner = !0, this.sharedMemory = s } var h = "input is invalid type", r = "object" == typeof window, s = r ? window : {}; s.JS_SHA256_NO_WINDOW && (r = !1); var e = !r && "object" == typeof self, n = !s.JS_SHA256_NO_NODE_JS && "object" == typeof process && process.versions && process.versions.node; n ? s = global : e && (s = self); var o = !s.JS_SHA256_NO_COMMON_JS && "object" == typeof module && module.exports, a = "function" == typeof define && define.amd, f = !s.JS_SHA256_NO_ARRAY_BUFFER && "undefined" != typeof ArrayBuffer, u = "0123456789abcdef".split(""), c = [-2147483648, 8388608, 32768, 128], y = [24, 16, 8, 0], p = [1116352408, 1899447441, 3049323471, 3921009573, 961987163, 1508970993, 2453635748, 2870763221, 3624381080, 310598401, 607225278, 1426881987, 1925078388, 2162078206, 2614888103, 3248222580, 3835390401, 4022224774, 264347078, 604807628, 770255983, 1249150122, 1555081692, 1996064986, 2554220882, 2821834349, 2952996808, 3210313671, 3336571891, 3584528711, 113926993, 338241895, 666307205, 773529912, 1294757372, 1396182291, 1695183700, 1986661051, 2177026350, 2456956037, 2730485921, 2820302411, 3259730800, 3345764771, 3516065817, 3600352804, 4094571909, 275423344, 430227734, 506948616, 659060556, 883997877, 958139571, 1322822218, 1537002063, 1747873779, 1955562222, 2024104815, 2227730452, 2361852424, 2428436474, 2756734187, 3204031479, 3329325298], l = ["hex", "array", "digest", "arrayBuffer"], d = []; !s.JS_SHA256_NO_NODE_JS && Array.isArray || (Array.isArray = function (t) { return "[object Array]" === Object.prototype.toString.call(t) }), !f || !s.JS_SHA256_NO_ARRAY_BUFFER_IS_VIEW && ArrayBuffer.isView || (ArrayBuffer.isView = function (t) { return "object" == typeof t && t.buffer && t.buffer.constructor === ArrayBuffer }); var A = function (i, h) { return function (r) { return new t(h, !0).update(r)[i]() } }, w = function (i) { var h = A("hex", i); n && (h = b(h, i)), h.create = function () { return new t(i) }, h.update = function (t) { return h.create().update(t) }; for (var r = 0; r < l.length; ++r) { var s = l[r]; h[s] = A(s, i) } return h }, b = function (t, i) { var r = eval("require('crypto')"), s = eval("require('buffer').Buffer"), e = i ? "sha224" : "sha256", n = function (i) { if ("string" == typeof i) return r.createHash(e).update(i, "utf8").digest("hex"); if (null === i || void 0 === i) throw new Error(h); return i.constructor === ArrayBuffer && (i = new Uint8Array(i)), Array.isArray(i) || ArrayBuffer.isView(i) || i.constructor === s ? r.createHash(e).update(new s(i)).digest("hex") : t(i) }; return n }, v = function (t, h) { return function (r, s) { return new i(r, h, !0).update(s)[t]() } }, _ = function (t) { var h = v("hex", t); h.create = function (h) { return new i(h, t) }, h.update = function (t, i) { return h.create(t).update(i) }; for (var r = 0; r < l.length; ++r) { var s = l[r]; h[s] = v(s, t) } return h }; t.prototype.update = function (t) { if (!this.finalized) { var i, r = typeof t; if ("string" !== r) { if ("object" !== r) throw new Error(h); if (null === t) throw new Error(h); if (f && t.constructor === ArrayBuffer) t = new Uint8Array(t); else if (!(Array.isArray(t) || f && ArrayBuffer.isView(t))) throw new Error(h); i = !0 } for (var s, e, n = 0, o = t.length, a = this.blocks; n < o;) { if (this.hashed && (this.hashed = !1, a[0] = this.block, a[16] = a[1] = a[2] = a[3] = a[4] = a[5] = a[6] = a[7] = a[8] = a[9] = a[10] = a[11] = a[12] = a[13] = a[14] = a[15] = 0), i) for (e = this.start; n < o && e < 64; ++n)a[e >> 2] |= t[n] << y[3 & e++]; else for (e = this.start; n < o && e < 64; ++n)(s = t.charCodeAt(n)) < 128 ? a[e >> 2] |= s << y[3 & e++] : s < 2048 ? (a[e >> 2] |= (192 | s >> 6) << y[3 & e++], a[e >> 2] |= (128 | 63 & s) << y[3 & e++]) : s < 55296 || s >= 57344 ? (a[e >> 2] |= (224 | s >> 12) << y[3 & e++], a[e >> 2] |= (128 | s >> 6 & 63) << y[3 & e++], a[e >> 2] |= (128 | 63 & s) << y[3 & e++]) : (s = 65536 + ((1023 & s) << 10 | 1023 & t.charCodeAt(++n)), a[e >> 2] |= (240 | s >> 18) << y[3 & e++], a[e >> 2] |= (128 | s >> 12 & 63) << y[3 & e++], a[e >> 2] |= (128 | s >> 6 & 63) << y[3 & e++], a[e >> 2] |= (128 | 63 & s) << y[3 & e++]); this.lastByteIndex = e, this.bytes += e - this.start, e >= 64 ? (this.block = a[16], this.start = e - 64, this.hash(), this.hashed = !0) : this.start = e } return this.bytes > 4294967295 && (this.hBytes += this.bytes / 4294967296 << 0, this.bytes = this.bytes % 4294967296), this } }, t.prototype.finalize = function () { if (!this.finalized) { this.finalized = !0; var t = this.blocks, i = this.lastByteIndex; t[16] = this.block, t[i >> 2] |= c[3 & i], this.block = t[16], i >= 56 && (this.hashed || this.hash(), t[0] = this.block, t[16] = t[1] = t[2] = t[3] = t[4] = t[5] = t[6] = t[7] = t[8] = t[9] = t[10] = t[11] = t[12] = t[13] = t[14] = t[15] = 0), t[14] = this.hBytes << 3 | this.bytes >>> 29, t[15] = this.bytes << 3, this.hash() } }, t.prototype.hash = function () { var t, i, h, r, s, e, n, o, a, f = this.h0, u = this.h1, c = this.h2, y = this.h3, l = this.h4, d = this.h5, A = this.h6, w = this.h7, b = this.blocks; for (t = 16; t < 64; ++t)i = ((s = b[t - 15]) >>> 7 | s << 25) ^ (s >>> 18 | s << 14) ^ s >>> 3, h = ((s = b[t - 2]) >>> 17 | s << 15) ^ (s >>> 19 | s << 13) ^ s >>> 10, b[t] = b[t - 16] + i + b[t - 7] + h << 0; for (a = u & c, t = 0; t < 64; t += 4)this.first ? (this.is224 ? (e = 300032, w = (s = b[0] - 1413257819) - 150054599 << 0, y = s + 24177077 << 0) : (e = 704751109, w = (s = b[0] - 210244248) - 1521486534 << 0, y = s + 143694565 << 0), this.first = !1) : (i = (f >>> 2 | f << 30) ^ (f >>> 13 | f << 19) ^ (f >>> 22 | f << 10), r = (e = f & u) ^ f & c ^ a, w = y + (s = w + (h = (l >>> 6 | l << 26) ^ (l >>> 11 | l << 21) ^ (l >>> 25 | l << 7)) + (l & d ^ ~l & A) + p[t] + b[t]) << 0, y = s + (i + r) << 0), i = (y >>> 2 | y << 30) ^ (y >>> 13 | y << 19) ^ (y >>> 22 | y << 10), r = (n = y & f) ^ y & u ^ e, A = c + (s = A + (h = (w >>> 6 | w << 26) ^ (w >>> 11 | w << 21) ^ (w >>> 25 | w << 7)) + (w & l ^ ~w & d) + p[t + 1] + b[t + 1]) << 0, i = ((c = s + (i + r) << 0) >>> 2 | c << 30) ^ (c >>> 13 | c << 19) ^ (c >>> 22 | c << 10), r = (o = c & y) ^ c & f ^ n, d = u + (s = d + (h = (A >>> 6 | A << 26) ^ (A >>> 11 | A << 21) ^ (A >>> 25 | A << 7)) + (A & w ^ ~A & l) + p[t + 2] + b[t + 2]) << 0, i = ((u = s + (i + r) << 0) >>> 2 | u << 30) ^ (u >>> 13 | u << 19) ^ (u >>> 22 | u << 10), r = (a = u & c) ^ u & y ^ o, l = f + (s = l + (h = (d >>> 6 | d << 26) ^ (d >>> 11 | d << 21) ^ (d >>> 25 | d << 7)) + (d & A ^ ~d & w) + p[t + 3] + b[t + 3]) << 0, f = s + (i + r) << 0; this.h0 = this.h0 + f << 0, this.h1 = this.h1 + u << 0, this.h2 = this.h2 + c << 0, this.h3 = this.h3 + y << 0, this.h4 = this.h4 + l << 0, this.h5 = this.h5 + d << 0, this.h6 = this.h6 + A << 0, this.h7 = this.h7 + w << 0 }, t.prototype.hex = function () { this.finalize(); var t = this.h0, i = this.h1, h = this.h2, r = this.h3, s = this.h4, e = this.h5, n = this.h6, o = this.h7, a = u[t >> 28 & 15] + u[t >> 24 & 15] + u[t >> 20 & 15] + u[t >> 16 & 15] + u[t >> 12 & 15] + u[t >> 8 & 15] + u[t >> 4 & 15] + u[15 & t] + u[i >> 28 & 15] + u[i >> 24 & 15] + u[i >> 20 & 15] + u[i >> 16 & 15] + u[i >> 12 & 15] + u[i >> 8 & 15] + u[i >> 4 & 15] + u[15 & i] + u[h >> 28 & 15] + u[h >> 24 & 15] + u[h >> 20 & 15] + u[h >> 16 & 15] + u[h >> 12 & 15] + u[h >> 8 & 15] + u[h >> 4 & 15] + u[15 & h] + u[r >> 28 & 15] + u[r >> 24 & 15] + u[r >> 20 & 15] + u[r >> 16 & 15] + u[r >> 12 & 15] + u[r >> 8 & 15] + u[r >> 4 & 15] + u[15 & r] + u[s >> 28 & 15] + u[s >> 24 & 15] + u[s >> 20 & 15] + u[s >> 16 & 15] + u[s >> 12 & 15] + u[s >> 8 & 15] + u[s >> 4 & 15] + u[15 & s] + u[e >> 28 & 15] + u[e >> 24 & 15] + u[e >> 20 & 15] + u[e >> 16 & 15] + u[e >> 12 & 15] + u[e >> 8 & 15] + u[e >> 4 & 15] + u[15 & e] + u[n >> 28 & 15] + u[n >> 24 & 15] + u[n >> 20 & 15] + u[n >> 16 & 15] + u[n >> 12 & 15] + u[n >> 8 & 15] + u[n >> 4 & 15] + u[15 & n]; return this.is224 || (a += u[o >> 28 & 15] + u[o >> 24 & 15] + u[o >> 20 & 15] + u[o >> 16 & 15] + u[o >> 12 & 15] + u[o >> 8 & 15] + u[o >> 4 & 15] + u[15 & o]), a }, t.prototype.toString = t.prototype.hex, t.prototype.digest = function () { this.finalize(); var t = this.h0, i = this.h1, h = this.h2, r = this.h3, s = this.h4, e = this.h5, n = this.h6, o = this.h7, a = [t >> 24 & 255, t >> 16 & 255, t >> 8 & 255, 255 & t, i >> 24 & 255, i >> 16 & 255, i >> 8 & 255, 255 & i, h >> 24 & 255, h >> 16 & 255, h >> 8 & 255, 255 & h, r >> 24 & 255, r >> 16 & 255, r >> 8 & 255, 255 & r, s >> 24 & 255, s >> 16 & 255, s >> 8 & 255, 255 & s, e >> 24 & 255, e >> 16 & 255, e >> 8 & 255, 255 & e, n >> 24 & 255, n >> 16 & 255, n >> 8 & 255, 255 & n]; return this.is224 || a.push(o >> 24 & 255, o >> 16 & 255, o >> 8 & 255, 255 & o), a }, t.prototype.array = t.prototype.digest, t.prototype.arrayBuffer = function () { this.finalize(); var t = new ArrayBuffer(this.is224 ? 28 : 32), i = new DataView(t); return i.setUint32(0, this.h0), i.setUint32(4, this.h1), i.setUint32(8, this.h2), i.setUint32(12, this.h3), i.setUint32(16, this.h4), i.setUint32(20, this.h5), i.setUint32(24, this.h6), this.is224 || i.setUint32(28, this.h7), t }, i.prototype = new t, i.prototype.finalize = function () { if (t.prototype.finalize.call(this), this.inner) { this.inner = !1; var i = this.array(); t.call(this, this.is224, this.sharedMemory), this.update(this.oKeyPad), this.update(i), t.prototype.finalize.call(this) } }; var B = w(); B.sha256 = B, B.sha224 = w(!0), B.sha256.hmac = _(), B.sha224.hmac = _(!0), o ? module.exports = B : (s.sha256 = B.sha256, s.sha224 = B.sha224, a && define(function () { return B })) }();
-
- /**
- * [base64-js]{@link https://github.com/beatgammit/base64-js}
- *
- * @version v1.3.0
- * @author Kirill, Fomichev
- * @copyright Kirill, Fomichev 2014
- * @license MIT
- */
- (function (r) { if (typeof exports === "object" && typeof module !== "undefined") { module.exports = r() } else if (typeof define === "function" && define.amd) { define([], r) } else { var e; if (typeof window !== "undefined") { e = window } else if (typeof global !== "undefined") { e = global } else if (typeof self !== "undefined") { e = self } else { e = this } e.base64js = r() } })(function () { var r, e, n; return function () { function r(e, n, t) { function o(f, i) { if (!n[f]) { if (!e[f]) { var u = "function" == typeof require && require; if (!i && u) return u(f, !0); if (a) return a(f, !0); var v = new Error("Cannot find module '" + f + "'"); throw v.code = "MODULE_NOT_FOUND", v } var d = n[f] = { exports: {} }; e[f][0].call(d.exports, function (r) { var n = e[f][1][r]; return o(n || r) }, d, d.exports, r, e, n, t) } return n[f].exports } for (var a = "function" == typeof require && require, f = 0; f < t.length; f++)o(t[f]); return o } return r }()({ "/": [function (r, e, n) { "use strict"; n.byteLength = d; n.toByteArray = h; n.fromByteArray = p; var t = []; var o = []; var a = typeof Uint8Array !== "undefined" ? Uint8Array : Array; var f = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; for (var i = 0, u = f.length; i < u; ++i) { t[i] = f[i]; o[f.charCodeAt(i)] = i } o["-".charCodeAt(0)] = 62; o["_".charCodeAt(0)] = 63; function v(r) { var e = r.length; if (e % 4 > 0) { throw new Error("Invalid string. Length must be a multiple of 4") } var n = r.indexOf("="); if (n === -1) n = e; var t = n === e ? 0 : 4 - n % 4; return [n, t] } function d(r) { var e = v(r); var n = e[0]; var t = e[1]; return (n + t) * 3 / 4 - t } function c(r, e, n) { return (e + n) * 3 / 4 - n } function h(r) { var e; var n = v(r); var t = n[0]; var f = n[1]; var i = new a(c(r, t, f)); var u = 0; var d = f > 0 ? t - 4 : t; for (var h = 0; h < d; h += 4) { e = o[r.charCodeAt(h)] << 18 | o[r.charCodeAt(h + 1)] << 12 | o[r.charCodeAt(h + 2)] << 6 | o[r.charCodeAt(h + 3)]; i[u++] = e >> 16 & 255; i[u++] = e >> 8 & 255; i[u++] = e & 255 } if (f === 2) { e = o[r.charCodeAt(h)] << 2 | o[r.charCodeAt(h + 1)] >> 4; i[u++] = e & 255 } if (f === 1) { e = o[r.charCodeAt(h)] << 10 | o[r.charCodeAt(h + 1)] << 4 | o[r.charCodeAt(h + 2)] >> 2; i[u++] = e >> 8 & 255; i[u++] = e & 255 } return i } function s(r) { return t[r >> 18 & 63] + t[r >> 12 & 63] + t[r >> 6 & 63] + t[r & 63] } function l(r, e, n) { var t; var o = []; for (var a = e; a < n; a += 3) { t = (r[a] << 16 & 16711680) + (r[a + 1] << 8 & 65280) + (r[a + 2] & 255); o.push(s(t)) } return o.join("") } function p(r) { var e; var n = r.length; var o = n % 3; var a = []; var f = 16383; for (var i = 0, u = n - o; i < u; i += f) { a.push(l(r, i, i + f > u ? u : i + f)) } if (o === 1) { e = r[n - 1]; a.push(t[e >> 2] + t[e << 4 & 63] + "==") } else if (o === 2) { e = (r[n - 2] << 8) + r[n - 1]; a.push(t[e >> 10] + t[e >> 4 & 63] + t[e << 2 & 63] + "=") } return a.join("") } }, {}] }, {}, [])("/") });
-
- /**
- * [promise-polyfill]{@link https://github.com/taylorhakes/promise-polyfill}
- *
- * @version v8.1.3
- * @author Hakes, Taylor
- * @copyright Hakes, Taylor 2014
- * @license MIT
- */
- !function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n():"function"==typeof define&&define.amd?define(n):n()}(0,function(){"use strict";function e(e){var n=this.constructor;return this.then(function(t){return n.resolve(e()).then(function(){return t})},function(t){return n.resolve(e()).then(function(){return n.reject(t)})})}function n(e){return!(!e||"undefined"==typeof e.length)}function t(){}function o(e){if(!(this instanceof o))throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=undefined,this._deferreds=[],c(e,this)}function r(e,n){for(;3===e._state;)e=e._value;0!==e._state?(e._handled=!0,o._immediateFn(function(){var t=1===e._state?n.onFulfilled:n.onRejected;if(null!==t){var o;try{o=t(e._value)}catch(r){return void f(n.promise,r)}i(n.promise,o)}else(1===e._state?i:f)(n.promise,e._value)})):e._deferreds.push(n)}function i(e,n){try{if(n===e)throw new TypeError("A promise cannot be resolved with itself.");if(n&&("object"==typeof n||"function"==typeof n)){var t=n.then;if(n instanceof o)return e._state=3,e._value=n,void u(e);if("function"==typeof t)return void c(function(e,n){return function(){e.apply(n,arguments)}}(t,n),e)}e._state=1,e._value=n,u(e)}catch(r){f(e,r)}}function f(e,n){e._state=2,e._value=n,u(e)}function u(e){2===e._state&&0===e._deferreds.length&&o._immediateFn(function(){e._handled||o._unhandledRejectionFn(e._value)});for(var n=0,t=e._deferreds.length;t>n;n++)r(e,e._deferreds[n]);e._deferreds=null}function c(e,n){var t=!1;try{e(function(e){t||(t=!0,i(n,e))},function(e){t||(t=!0,f(n,e))})}catch(o){if(t)return;t=!0,f(n,o)}}var a=setTimeout;o.prototype["catch"]=function(e){return this.then(null,e)},o.prototype.then=function(e,n){var o=new this.constructor(t);return r(this,new function(e,n,t){this.onFulfilled="function"==typeof e?e:null,this.onRejected="function"==typeof n?n:null,this.promise=t}(e,n,o)),o},o.prototype["finally"]=e,o.all=function(e){return new o(function(t,o){function r(e,n){try{if(n&&("object"==typeof n||"function"==typeof n)){var u=n.then;if("function"==typeof u)return void u.call(n,function(n){r(e,n)},o)}i[e]=n,0==--f&&t(i)}catch(c){o(c)}}if(!n(e))return o(new TypeError("Promise.all accepts an array"));var i=Array.prototype.slice.call(e);if(0===i.length)return t([]);for(var f=i.length,u=0;i.length>u;u++)r(u,i[u])})},o.resolve=function(e){return e&&"object"==typeof e&&e.constructor===o?e:new o(function(n){n(e)})},o.reject=function(e){return new o(function(n,t){t(e)})},o.race=function(e){return new o(function(t,r){if(!n(e))return r(new TypeError("Promise.race accepts an array"));for(var i=0,f=e.length;f>i;i++)o.resolve(e[i]).then(t,r)})},o._immediateFn="function"==typeof setImmediate&&function(e){setImmediate(e)}||function(e){a(e,0)},o._unhandledRejectionFn=function(e){void 0!==console&&console&&console.warn("Possible Unhandled Promise Rejection:",e)};var l=function(){if("undefined"!=typeof self)return self;if("undefined"!=typeof window)return window;if("undefined"!=typeof global)return global;throw Error("unable to locate global object")}();"Promise"in l?l.Promise.prototype["finally"]||(l.Promise.prototype["finally"]=e):l.Promise=o});
-
- var Keycloak = factory( root["sha256"], root["base64js"] );
- root["Keycloak"] = Keycloak;
-
- if ( typeof define === "function" && define.amd ) {
- define( "keycloak", [], function () { return Keycloak; } );
- }
- }
-})(window, function (sha256_imported, base64js_imported) {
- if (typeof Promise === 'undefined') {
- throw Error('Keycloak requires an environment that supports Promises. Make sure that you include the appropriate polyfill.');
- }
-
- var loggedPromiseDeprecation = false;
-
- function logPromiseDeprecation() {
- if (!loggedPromiseDeprecation) {
- loggedPromiseDeprecation = true;
- console.warn('[KEYCLOAK] Usage of legacy style promise methods such as `.error()` and `.success()` has been deprecated and support will be removed in future versions. Use standard style promise methods such as `.then() and `.catch()` instead.');
- }
- }
-
- function Keycloak (config) {
- if (!(this instanceof Keycloak)) {
- return new Keycloak(config);
- }
-
- var kc = this;
- var adapter;
- var refreshQueue = [];
- var callbackStorage;
-
- var loginIframe = {
- enable: true,
- callbackList: [],
- interval: 5
- };
-
- var scripts = document.getElementsByTagName('script');
- for (var i = 0; i < scripts.length; i++) {
- if ((scripts[i].src.indexOf('keycloak.js') !== -1 || scripts[i].src.indexOf('keycloak.min.js') !== -1) && scripts[i].src.indexOf('version=') !== -1) {
- kc.iframeVersion = scripts[i].src.substring(scripts[i].src.indexOf('version=') + 8).split('&')[0];
- }
- }
-
- var useNonce = true;
- var logInfo = createLogger(console.info);
- var logWarn = createLogger(console.warn);
-
- kc.init = function (initOptions) {
- kc.authenticated = false;
-
- callbackStorage = createCallbackStorage();
- var adapters = ['default', 'cordova', 'cordova-native'];
-
- if (initOptions && adapters.indexOf(initOptions.adapter) > -1) {
- adapter = loadAdapter(initOptions.adapter);
- } else if (initOptions && typeof initOptions.adapter === "object") {
- adapter = initOptions.adapter;
- } else {
- if (window.Cordova || window.cordova) {
- adapter = loadAdapter('cordova');
- } else {
- adapter = loadAdapter();
- }
- }
-
- if (initOptions) {
- if (typeof initOptions.useNonce !== 'undefined') {
- useNonce = initOptions.useNonce;
- }
-
- if (typeof initOptions.checkLoginIframe !== 'undefined') {
- loginIframe.enable = initOptions.checkLoginIframe;
- }
-
- if (initOptions.checkLoginIframeInterval) {
- loginIframe.interval = initOptions.checkLoginIframeInterval;
- }
-
- if (initOptions.onLoad === 'login-required') {
- kc.loginRequired = true;
- }
-
- if (initOptions.responseMode) {
- if (initOptions.responseMode === 'query' || initOptions.responseMode === 'fragment') {
- kc.responseMode = initOptions.responseMode;
- } else {
- throw 'Invalid value for responseMode';
- }
- }
-
- if (initOptions.flow) {
- switch (initOptions.flow) {
- case 'standard':
- kc.responseType = 'code';
- break;
- case 'implicit':
- kc.responseType = 'id_token token';
- break;
- case 'hybrid':
- kc.responseType = 'code id_token token';
- break;
- default:
- throw 'Invalid value for flow';
- }
- kc.flow = initOptions.flow;
- }
-
- if (initOptions.timeSkew != null) {
- kc.timeSkew = initOptions.timeSkew;
- }
-
- if(initOptions.redirectUri) {
- kc.redirectUri = initOptions.redirectUri;
- }
-
- if (initOptions.silentCheckSsoRedirectUri) {
- kc.silentCheckSsoRedirectUri = initOptions.silentCheckSsoRedirectUri;
- }
-
- if (typeof initOptions.silentCheckSsoFallback === 'boolean') {
- kc.silentCheckSsoFallback = initOptions.silentCheckSsoFallback;
- } else {
- kc.silentCheckSsoFallback = true;
- }
-
- if (initOptions.pkceMethod) {
- if (initOptions.pkceMethod !== "S256") {
- throw 'Invalid value for pkceMethod';
- }
- kc.pkceMethod = initOptions.pkceMethod;
- }
-
- if (typeof initOptions.enableLogging === 'boolean') {
- kc.enableLogging = initOptions.enableLogging;
- } else {
- kc.enableLogging = false;
- }
-
- if (typeof initOptions.scope === 'string') {
- kc.scope = initOptions.scope;
- }
-
- if (typeof initOptions.messageReceiveTimeout === 'number' && initOptions.messageReceiveTimeout > 0) {
- kc.messageReceiveTimeout = initOptions.messageReceiveTimeout;
- } else {
- kc.messageReceiveTimeout = 10000;
- }
- }
-
- if (!kc.responseMode) {
- kc.responseMode = 'fragment';
- }
- if (!kc.responseType) {
- kc.responseType = 'code';
- kc.flow = 'standard';
- }
-
- var promise = createPromise();
-
- var initPromise = createPromise();
- initPromise.promise.then(function() {
- kc.onReady && kc.onReady(kc.authenticated);
- promise.setSuccess(kc.authenticated);
- }).catch(function(error) {
- promise.setError(error);
- });
-
- var configPromise = loadConfig(config);
-
- function onLoad() {
- var doLogin = function(prompt) {
- if (!prompt) {
- options.prompt = 'none';
- }
-
- kc.login(options).then(function () {
- initPromise.setSuccess();
- }).catch(function (error) {
- initPromise.setError(error);
- });
- }
-
- var checkSsoSilently = function() {
- var ifrm = document.createElement("iframe");
- var src = kc.createLoginUrl({prompt: 'none', redirectUri: kc.silentCheckSsoRedirectUri});
- ifrm.setAttribute("src", src);
- ifrm.setAttribute("title", "keycloak-silent-check-sso");
- ifrm.style.display = "none";
- document.body.appendChild(ifrm);
-
- var messageCallback = function(event) {
- if (event.origin !== window.location.origin || ifrm.contentWindow !== event.source) {
- return;
- }
-
- var oauth = parseCallback(event.data);
- processCallback(oauth, initPromise);
-
- document.body.removeChild(ifrm);
- window.removeEventListener("message", messageCallback);
- };
-
- window.addEventListener("message", messageCallback);
- };
-
- var options = {};
- switch (initOptions.onLoad) {
- case 'check-sso':
- if (loginIframe.enable) {
- setupCheckLoginIframe().then(function() {
- checkLoginIframe().then(function (unchanged) {
- if (!unchanged) {
- kc.silentCheckSsoRedirectUri ? checkSsoSilently() : doLogin(false);
- } else {
- initPromise.setSuccess();
- }
- }).catch(function (error) {
- initPromise.setError(error);
- });
- });
- } else {
- kc.silentCheckSsoRedirectUri ? checkSsoSilently() : doLogin(false);
- }
- break;
- case 'login-required':
- doLogin(true);
- break;
- default:
- throw 'Invalid value for onLoad';
- }
- }
-
- function processInit() {
- var callback = parseCallback(window.location.href);
-
- if (callback) {
- window.history.replaceState(window.history.state, null, callback.newUrl);
- }
-
- if (callback && callback.valid) {
- return setupCheckLoginIframe().then(function() {
- processCallback(callback, initPromise);
- }).catch(function (error) {
- initPromise.setError(error);
- });
- } else if (initOptions) {
- if (initOptions.token && initOptions.refreshToken) {
- setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken);
-
- if (loginIframe.enable) {
- setupCheckLoginIframe().then(function() {
- checkLoginIframe().then(function (unchanged) {
- if (unchanged) {
- kc.onAuthSuccess && kc.onAuthSuccess();
- initPromise.setSuccess();
- scheduleCheckIframe();
- } else {
- initPromise.setSuccess();
- }
- }).catch(function (error) {
- initPromise.setError(error);
- });
- });
- } else {
- kc.updateToken(-1).then(function() {
- kc.onAuthSuccess && kc.onAuthSuccess();
- initPromise.setSuccess();
- }).catch(function(error) {
- kc.onAuthError && kc.onAuthError();
- if (initOptions.onLoad) {
- onLoad();
- } else {
- initPromise.setError(error);
- }
- });
- }
- } else if (initOptions.onLoad) {
- onLoad();
- } else {
- initPromise.setSuccess();
- }
- } else {
- initPromise.setSuccess();
- }
- }
-
- 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 () {
- domReady()
- .then(check3pCookiesSupported)
- .then(processInit)
- .catch(function (error) {
- promise.setError(error);
- });
- });
- configPromise.catch(function (error) {
- promise.setError(error);
- });
-
- return promise.promise;
- }
-
- kc.login = function (options) {
- return adapter.login(options);
- }
-
- function generateRandomData(len) {
- // use web crypto APIs if possible
- var array = null;
- var crypto = window.crypto || window.msCrypto;
- if (crypto && crypto.getRandomValues && window.Uint8Array) {
- array = new Uint8Array(len);
- crypto.getRandomValues(array);
- return array;
- }
-
- // fallback to Math random
- array = new Array(len);
- for (var j = 0; j < array.length; j++) {
- array[j] = Math.floor(256 * Math.random());
- }
- return array;
- }
-
- function generateCodeVerifier(len) {
- return generateRandomString(len, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789');
- }
-
- function generateRandomString(len, alphabet){
- var randomData = generateRandomData(len);
- var chars = new Array(len);
- for (var i = 0; i < len; i++) {
- chars[i] = alphabet.charCodeAt(randomData[i] % alphabet.length);
- }
- return String.fromCharCode.apply(null, chars);
- }
-
- function generatePkceChallenge(pkceMethod, codeVerifier) {
- switch (pkceMethod) {
- // The use of the "plain" method is considered insecure and therefore not supported.
- case "S256":
- // hash codeVerifier, then encode as url-safe base64 without padding
- var hashBytes = new Uint8Array(sha256_imported.arrayBuffer(codeVerifier));
- var encodedHash = base64js_imported.fromByteArray(hashBytes)
- .replace(/\+/g, '-')
- .replace(/\//g, '_')
- .replace(/\=/g, '');
- return encodedHash;
- default:
- throw 'Invalid value for pkceMethod';
- }
- }
-
- kc.createLoginUrl = function(options) {
- var state = createUUID();
- var nonce = createUUID();
-
- var redirectUri = adapter.redirectUri(options);
-
- var callbackState = {
- state: state,
- nonce: nonce,
- redirectUri: encodeURIComponent(redirectUri)
- };
-
- if (options && options.prompt) {
- callbackState.prompt = options.prompt;
- }
-
- var baseUrl;
- if (options && options.action == 'register') {
- baseUrl = kc.endpoints.register();
- } else {
- baseUrl = kc.endpoints.authorize();
- }
-
- var scope = options && options.scope || kc.scope;
- if (!scope) {
- // if scope is not set, default to "openid"
- scope = "openid";
- } else if (scope.indexOf("openid") === -1) {
- // if openid scope is missing, prefix the given scopes with it
- scope = "openid " + scope;
- }
-
- var url = baseUrl
- + '?client_id=' + encodeURIComponent(kc.clientId)
- + '&redirect_uri=' + encodeURIComponent(redirectUri)
- + '&state=' + encodeURIComponent(state)
- + '&response_mode=' + encodeURIComponent(kc.responseMode)
- + '&response_type=' + encodeURIComponent(kc.responseType)
- + '&scope=' + encodeURIComponent(scope);
- if (useNonce) {
- url = url + '&nonce=' + encodeURIComponent(nonce);
- }
-
- if (options && options.prompt) {
- url += '&prompt=' + encodeURIComponent(options.prompt);
- }
-
- if (options && options.maxAge) {
- url += '&max_age=' + encodeURIComponent(options.maxAge);
- }
-
- if (options && options.loginHint) {
- url += '&login_hint=' + encodeURIComponent(options.loginHint);
- }
-
- if (options && options.idpHint) {
- url += '&kc_idp_hint=' + encodeURIComponent(options.idpHint);
- }
-
- if (options && options.action && options.action != 'register') {
- url += '&kc_action=' + encodeURIComponent(options.action);
- }
-
- if (options && options.locale) {
- url += '&ui_locales=' + encodeURIComponent(options.locale);
- }
-
- if (kc.pkceMethod) {
- var codeVerifier = generateCodeVerifier(96);
- callbackState.pkceCodeVerifier = codeVerifier;
- var pkceChallenge = generatePkceChallenge(kc.pkceMethod, codeVerifier);
- url += '&code_challenge=' + pkceChallenge;
- url += '&code_challenge_method=' + kc.pkceMethod;
- }
-
- callbackStorage.add(callbackState);
-
- return url;
- }
-
- kc.logout = function(options) {
- return adapter.logout(options);
- }
-
- kc.createLogoutUrl = function(options) {
- var url = kc.endpoints.logout()
- + '?redirect_uri=' + encodeURIComponent(adapter.redirectUri(options, false));
-
- return url;
- }
-
- kc.register = function (options) {
- return adapter.register(options);
- }
-
- kc.createRegisterUrl = function(options) {
- if (!options) {
- options = {};
- }
- options.action = 'register';
- return kc.createLoginUrl(options);
- }
-
- kc.createAccountUrl = function(options) {
- var realm = getRealmUrl();
- var url = undefined;
- if (typeof realm !== 'undefined') {
- url = realm
- + '/account'
- + '?referrer=' + encodeURIComponent(kc.clientId)
- + '&referrer_uri=' + encodeURIComponent(adapter.redirectUri(options));
- }
- return url;
- }
-
- kc.accountManagement = function() {
- return adapter.accountManagement();
- }
-
- kc.hasRealmRole = function (role) {
- var access = kc.realmAccess;
- return !!access && access.roles.indexOf(role) >= 0;
- }
-
- kc.hasResourceRole = function(role, resource) {
- if (!kc.resourceAccess) {
- return false;
- }
-
- var access = kc.resourceAccess[resource || kc.clientId];
- return !!access && access.roles.indexOf(role) >= 0;
- }
-
- kc.loadUserProfile = function() {
- var url = getRealmUrl() + '/account';
- var req = new XMLHttpRequest();
- req.open('GET', url, true);
- req.setRequestHeader('Accept', 'application/json');
- req.setRequestHeader('Authorization', 'bearer ' + kc.token);
-
- var promise = createPromise();
-
- req.onreadystatechange = function () {
- if (req.readyState == 4) {
- if (req.status == 200) {
- kc.profile = JSON.parse(req.responseText);
- promise.setSuccess(kc.profile);
- } else {
- promise.setError();
- }
- }
- }
-
- req.send();
-
- return promise.promise;
- }
-
- kc.loadUserInfo = function() {
- var url = kc.endpoints.userinfo();
- var req = new XMLHttpRequest();
- req.open('GET', url, true);
- req.setRequestHeader('Accept', 'application/json');
- req.setRequestHeader('Authorization', 'bearer ' + kc.token);
-
- var promise = createPromise();
-
- req.onreadystatechange = function () {
- if (req.readyState == 4) {
- if (req.status == 200) {
- kc.userInfo = JSON.parse(req.responseText);
- promise.setSuccess(kc.userInfo);
- } else {
- promise.setError();
- }
- }
- }
-
- req.send();
-
- return promise.promise;
- }
-
- kc.isTokenExpired = function(minValidity) {
- if (!kc.tokenParsed || (!kc.refreshToken && kc.flow != 'implicit' )) {
- throw 'Not authenticated';
- }
-
- if (kc.timeSkew == null) {
- logInfo('[KEYCLOAK] Unable to determine if token is expired as timeskew is not set');
- return true;
- }
-
- var expiresIn = kc.tokenParsed['exp'] - Math.ceil(new Date().getTime() / 1000) + kc.timeSkew;
- if (minValidity) {
- if (isNaN(minValidity)) {
- throw 'Invalid minValidity';
- }
- expiresIn -= minValidity;
- }
- return expiresIn < 0;
- }
-
- kc.updateToken = function(minValidity) {
- var promise = createPromise();
-
- if (!kc.refreshToken) {
- promise.setError();
- return promise.promise;
- }
-
- minValidity = minValidity || 5;
-
- var exec = function() {
- var refreshToken = false;
- if (minValidity == -1) {
- refreshToken = true;
- logInfo('[KEYCLOAK] Refreshing token: forced refresh');
- } else if (!kc.tokenParsed || kc.isTokenExpired(minValidity)) {
- refreshToken = true;
- logInfo('[KEYCLOAK] Refreshing token: token expired');
- }
-
- if (!refreshToken) {
- promise.setSuccess(false);
- } else {
- var params = 'grant_type=refresh_token&' + 'refresh_token=' + kc.refreshToken;
- var url = kc.endpoints.token();
-
- refreshQueue.push(promise);
-
- if (refreshQueue.length == 1) {
- var req = new XMLHttpRequest();
- req.open('POST', url, true);
- req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
- req.withCredentials = true;
-
- params += '&client_id=' + encodeURIComponent(kc.clientId);
-
- var timeLocal = new Date().getTime();
-
- req.onreadystatechange = function () {
- if (req.readyState == 4) {
- if (req.status == 200) {
- logInfo('[KEYCLOAK] Token refreshed');
-
- timeLocal = (timeLocal + new Date().getTime()) / 2;
-
- var tokenResponse = JSON.parse(req.responseText);
-
- setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], timeLocal);
-
- kc.onAuthRefreshSuccess && kc.onAuthRefreshSuccess();
- for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) {
- p.setSuccess(true);
- }
- } else {
- logWarn('[KEYCLOAK] Failed to refresh token');
-
- if (req.status == 400) {
- kc.clearToken();
- }
-
- kc.onAuthRefreshError && kc.onAuthRefreshError();
- for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) {
- p.setError(true);
- }
- }
- }
- };
-
- req.send(params);
- }
- }
- }
-
- if (loginIframe.enable) {
- var iframePromise = checkLoginIframe();
- iframePromise.then(function() {
- exec();
- }).catch(function(error) {
- promise.setError(error);
- });
- } else {
- exec();
- }
-
- return promise.promise;
- }
-
- kc.clearToken = function() {
- if (kc.token) {
- setToken(null, null, null);
- kc.onAuthLogout && kc.onAuthLogout();
- if (kc.loginRequired) {
- kc.login();
- }
- }
- }
-
- function getRealmUrl() {
- if (typeof kc.authServerUrl !== 'undefined') {
- if (kc.authServerUrl.charAt(kc.authServerUrl.length - 1) == '/') {
- return kc.authServerUrl + 'realms/' + encodeURIComponent(kc.realm);
- } else {
- return kc.authServerUrl + '/realms/' + encodeURIComponent(kc.realm);
- }
- } else {
- return undefined;
- }
- }
-
- function getOrigin() {
- if (!window.location.origin) {
- return window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: '');
- } else {
- return window.location.origin;
- }
- }
-
- function processCallback(oauth, promise) {
- var code = oauth.code;
- var error = oauth.error;
- var prompt = oauth.prompt;
-
- var timeLocal = new Date().getTime();
-
- if (oauth['kc_action_status']) {
- kc.onActionUpdate && kc.onActionUpdate(oauth['kc_action_status']);
- }
-
- if (error) {
- if (prompt != 'none') {
- var errorData = { error: error, error_description: oauth.error_description };
- kc.onAuthError && kc.onAuthError(errorData);
- promise && promise.setError(errorData);
- } else {
- promise && promise.setSuccess();
- }
- return;
- } else if ((kc.flow != 'standard') && (oauth.access_token || oauth.id_token)) {
- authSuccess(oauth.access_token, null, oauth.id_token, true);
- }
-
- if ((kc.flow != 'implicit') && code) {
- var params = 'code=' + code + '&grant_type=authorization_code';
- var url = kc.endpoints.token();
-
- var req = new XMLHttpRequest();
- req.open('POST', url, true);
- req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
-
- params += '&client_id=' + encodeURIComponent(kc.clientId);
- params += '&redirect_uri=' + oauth.redirectUri;
-
- if (oauth.pkceCodeVerifier) {
- params += '&code_verifier=' + oauth.pkceCodeVerifier;
- }
-
- req.withCredentials = true;
-
- req.onreadystatechange = function() {
- if (req.readyState == 4) {
- if (req.status == 200) {
-
- var tokenResponse = JSON.parse(req.responseText);
- authSuccess(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], kc.flow === 'standard');
- scheduleCheckIframe();
- } else {
- kc.onAuthError && kc.onAuthError();
- promise && promise.setError();
- }
- }
- };
-
- req.send(params);
- }
-
- function authSuccess(accessToken, refreshToken, idToken, fulfillPromise) {
- timeLocal = (timeLocal + new Date().getTime()) / 2;
-
- setToken(accessToken, refreshToken, idToken, timeLocal);
-
- if (useNonce && ((kc.tokenParsed && kc.tokenParsed.nonce != oauth.storedNonce) ||
- (kc.refreshTokenParsed && kc.refreshTokenParsed.nonce != oauth.storedNonce) ||
- (kc.idTokenParsed && kc.idTokenParsed.nonce != oauth.storedNonce))) {
-
- logInfo('[KEYCLOAK] Invalid nonce, clearing token');
- kc.clearToken();
- promise && promise.setError();
- } else {
- if (fulfillPromise) {
- kc.onAuthSuccess && kc.onAuthSuccess();
- promise && promise.setSuccess();
- }
- }
- }
-
- }
-
- function loadConfig(url) {
- var promise = createPromise();
- var configUrl;
-
- if (!config) {
- configUrl = 'keycloak.json';
- } else if (typeof config === 'string') {
- configUrl = config;
- }
-
- function setupOidcEndoints(oidcConfiguration) {
- if (! oidcConfiguration) {
- kc.endpoints = {
- authorize: function() {
- return getRealmUrl() + '/protocol/openid-connect/auth';
- },
- token: function() {
- return getRealmUrl() + '/protocol/openid-connect/token';
- },
- logout: function() {
- return getRealmUrl() + '/protocol/openid-connect/logout';
- },
- checkSessionIframe: function() {
- var src = getRealmUrl() + '/protocol/openid-connect/login-status-iframe.html';
- if (kc.iframeVersion) {
- src = src + '?version=' + kc.iframeVersion;
- }
- return src;
- },
- thirdPartyCookiesIframe: function() {
- var src = getRealmUrl() + '/protocol/openid-connect/3p-cookies/step1.html';
- if (kc.iframeVersion) {
- src = src + '?version=' + kc.iframeVersion;
- }
- return src;
- },
- register: function() {
- return getRealmUrl() + '/protocol/openid-connect/registrations';
- },
- userinfo: function() {
- return getRealmUrl() + '/protocol/openid-connect/userinfo';
- }
- };
- } else {
- kc.endpoints = {
- authorize: function() {
- return oidcConfiguration.authorization_endpoint;
- },
- token: function() {
- return oidcConfiguration.token_endpoint;
- },
- logout: function() {
- if (!oidcConfiguration.end_session_endpoint) {
- throw "Not supported by the OIDC server";
- }
- return oidcConfiguration.end_session_endpoint;
- },
- checkSessionIframe: function() {
- if (!oidcConfiguration.check_session_iframe) {
- throw "Not supported by the OIDC server";
- }
- return oidcConfiguration.check_session_iframe;
- },
- register: function() {
- throw 'Redirection to "Register user" page not supported in standard OIDC mode';
- },
- userinfo: function() {
- if (!oidcConfiguration.userinfo_endpoint) {
- throw "Not supported by the OIDC server";
- }
- return oidcConfiguration.userinfo_endpoint;
- }
- }
- }
- }
-
- if (configUrl) {
- var req = new XMLHttpRequest();
- req.open('GET', configUrl, true);
- req.setRequestHeader('Accept', 'application/json');
-
- req.onreadystatechange = function () {
- if (req.readyState == 4) {
- if (req.status == 200 || fileLoaded(req)) {
- var config = JSON.parse(req.responseText);
-
- kc.authServerUrl = config['auth-server-url'];
- kc.realm = config['realm'];
- kc.clientId = config['resource'];
- setupOidcEndoints(null);
- promise.setSuccess();
- } else {
- promise.setError();
- }
- }
- };
-
- req.send();
- } else {
- if (!config.clientId) {
- throw 'clientId missing';
- }
-
- kc.clientId = config.clientId;
-
- var oidcProvider = config['oidcProvider'];
- if (!oidcProvider) {
- if (!config['url']) {
- var scripts = document.getElementsByTagName('script');
- for (var i = 0; i < scripts.length; i++) {
- if (scripts[i].src.match(/.*keycloak\.js/)) {
- config.url = scripts[i].src.substr(0, scripts[i].src.indexOf('/js/keycloak.js'));
- break;
- }
- }
- }
- if (!config.realm) {
- throw 'realm missing';
- }
-
- kc.authServerUrl = config.url;
- kc.realm = config.realm;
- setupOidcEndoints(null);
- promise.setSuccess();
- } else {
- if (typeof oidcProvider === 'string') {
- var oidcProviderConfigUrl;
- if (oidcProvider.charAt(oidcProvider.length - 1) == '/') {
- oidcProviderConfigUrl = oidcProvider + '.well-known/openid-configuration';
- } else {
- oidcProviderConfigUrl = oidcProvider + '/.well-known/openid-configuration';
- }
- var req = new XMLHttpRequest();
- req.open('GET', oidcProviderConfigUrl, true);
- req.setRequestHeader('Accept', 'application/json');
-
- req.onreadystatechange = function () {
- if (req.readyState == 4) {
- if (req.status == 200 || fileLoaded(req)) {
- var oidcProviderConfig = JSON.parse(req.responseText);
- setupOidcEndoints(oidcProviderConfig);
- promise.setSuccess();
- } else {
- promise.setError();
- }
- }
- };
-
- req.send();
- } else {
- setupOidcEndoints(oidcProvider);
- promise.setSuccess();
- }
- }
- }
-
- return promise.promise;
- }
-
- function fileLoaded(xhr) {
- return xhr.status == 0 && xhr.responseText && xhr.responseURL.startsWith('file:');
- }
-
- function setToken(token, refreshToken, idToken, timeLocal) {
- if (kc.tokenTimeoutHandle) {
- clearTimeout(kc.tokenTimeoutHandle);
- kc.tokenTimeoutHandle = null;
- }
-
- if (refreshToken) {
- kc.refreshToken = refreshToken;
- kc.refreshTokenParsed = decodeToken(refreshToken);
- } else {
- delete kc.refreshToken;
- delete kc.refreshTokenParsed;
- }
-
- if (idToken) {
- kc.idToken = idToken;
- kc.idTokenParsed = decodeToken(idToken);
- } else {
- delete kc.idToken;
- delete kc.idTokenParsed;
- }
-
- if (token) {
- kc.token = token;
- kc.tokenParsed = decodeToken(token);
- kc.sessionId = kc.tokenParsed.session_state;
- kc.authenticated = true;
- kc.subject = kc.tokenParsed.sub;
- kc.realmAccess = kc.tokenParsed.realm_access;
- kc.resourceAccess = kc.tokenParsed.resource_access;
-
- if (timeLocal) {
- kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
- }
-
- if (kc.timeSkew != null) {
- logInfo('[KEYCLOAK] Estimated time difference between browser and server is ' + kc.timeSkew + ' seconds');
-
- if (kc.onTokenExpired) {
- var expiresIn = (kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew) * 1000;
- logInfo('[KEYCLOAK] Token expires in ' + Math.round(expiresIn / 1000) + ' s');
- if (expiresIn <= 0) {
- kc.onTokenExpired();
- } else {
- kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn);
- }
- }
- }
- } else {
- delete kc.token;
- delete kc.tokenParsed;
- delete kc.subject;
- delete kc.realmAccess;
- delete kc.resourceAccess;
-
- kc.authenticated = false;
- }
- }
-
- function decodeToken(str) {
- str = str.split('.')[1];
-
- str = str.replace(/-/g, '+');
- str = str.replace(/_/g, '/');
- switch (str.length % 4) {
- case 0:
- break;
- case 2:
- str += '==';
- break;
- case 3:
- str += '=';
- break;
- default:
- throw 'Invalid token';
- }
-
- str = decodeURIComponent(escape(atob(str)));
-
- str = JSON.parse(str);
- return str;
- }
-
- function createUUID() {
- var hexDigits = '0123456789abcdef';
- var s = generateRandomString(36, hexDigits).split("");
- s[14] = '4';
- s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
- s[8] = s[13] = s[18] = s[23] = '-';
- var uuid = s.join('');
- return uuid;
- }
-
- function parseCallback(url) {
- var oauth = parseCallbackUrl(url);
- if (!oauth) {
- return;
- }
-
- var oauthState = callbackStorage.get(oauth.state);
-
- if (oauthState) {
- oauth.valid = true;
- oauth.redirectUri = oauthState.redirectUri;
- oauth.storedNonce = oauthState.nonce;
- oauth.prompt = oauthState.prompt;
- oauth.pkceCodeVerifier = oauthState.pkceCodeVerifier;
- }
-
- return oauth;
- }
-
- function parseCallbackUrl(url) {
- var supportedParams;
- switch (kc.flow) {
- case 'standard':
- supportedParams = ['code', 'state', 'session_state', 'kc_action_status'];
- break;
- case 'implicit':
- supportedParams = ['access_token', 'token_type', 'id_token', 'state', 'session_state', 'expires_in', 'kc_action_status'];
- break;
- case 'hybrid':
- supportedParams = ['access_token', 'token_type', 'id_token', 'code', 'state', 'session_state', 'expires_in', 'kc_action_status'];
- break;
- }
-
- supportedParams.push('error');
- supportedParams.push('error_description');
- supportedParams.push('error_uri');
-
- var queryIndex = url.indexOf('?');
- var fragmentIndex = url.indexOf('#');
-
- var newUrl;
- var parsed;
-
- if (kc.responseMode === 'query' && queryIndex !== -1) {
- newUrl = url.substring(0, queryIndex);
- parsed = parseCallbackParams(url.substring(queryIndex + 1, fragmentIndex !== -1 ? fragmentIndex : url.length), supportedParams);
- if (parsed.paramsString !== '') {
- newUrl += '?' + parsed.paramsString;
- }
- if (fragmentIndex !== -1) {
- newUrl += url.substring(fragmentIndex);
- }
- } else if (kc.responseMode === 'fragment' && fragmentIndex !== -1) {
- newUrl = url.substring(0, fragmentIndex);
- parsed = parseCallbackParams(url.substring(fragmentIndex + 1), supportedParams);
- if (parsed.paramsString !== '') {
- newUrl += '#' + parsed.paramsString;
- }
- }
-
- if (parsed && parsed.oauthParams) {
- if (kc.flow === 'standard' || kc.flow === 'hybrid') {
- if ((parsed.oauthParams.code || parsed.oauthParams.error) && parsed.oauthParams.state) {
- parsed.oauthParams.newUrl = newUrl;
- return parsed.oauthParams;
- }
- } else if (kc.flow === 'implicit') {
- if ((parsed.oauthParams.access_token || parsed.oauthParams.error) && parsed.oauthParams.state) {
- parsed.oauthParams.newUrl = newUrl;
- return parsed.oauthParams;
- }
- }
- }
- }
-
- function parseCallbackParams(paramsString, supportedParams) {
- var p = paramsString.split('&');
- var result = {
- paramsString: '',
- oauthParams: {}
- }
- for (var i = 0; i < p.length; i++) {
- var split = p[i].indexOf("=");
- var key = p[i].slice(0, split);
- if (supportedParams.indexOf(key) !== -1) {
- result.oauthParams[key] = p[i].slice(split + 1);
- } else {
- if (result.paramsString !== '') {
- result.paramsString += '&';
- }
- result.paramsString += p[i];
- }
- }
- return result;
- }
-
- function createPromise() {
- // Need to create a native Promise which also preserves the
- // interface of the custom promise type previously used by the API
- var p = {
- setSuccess: function(result) {
- p.resolve(result);
- },
-
- setError: function(result) {
- p.reject(result);
- }
- };
- 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;
- }
-
- // Function to extend existing native Promise with timeout
- function applyTimeoutToPromise(promise, timeout, errorMessage) {
- var timeoutHandle = null;
- var timeoutPromise = new Promise(function (resolve, reject) {
- timeoutHandle = setTimeout(function () {
- reject({ "error": errorMessage || "Promise is not settled within timeout of " + timeout + "ms" });
- }, timeout);
- });
-
- return Promise.race([promise, timeoutPromise]).finally(function () {
- clearTimeout(timeoutHandle);
- });
- }
-
- function setupCheckLoginIframe() {
- var promise = createPromise();
-
- if (!loginIframe.enable) {
- promise.setSuccess();
- return promise.promise;
- }
-
- if (loginIframe.iframe) {
- promise.setSuccess();
- return promise.promise;
- }
-
- var iframe = document.createElement('iframe');
- loginIframe.iframe = iframe;
-
- iframe.onload = function() {
- var authUrl = kc.endpoints.authorize();
- if (authUrl.charAt(0) === '/') {
- loginIframe.iframeOrigin = getOrigin();
- } else {
- loginIframe.iframeOrigin = authUrl.substring(0, authUrl.indexOf('/', 8));
- }
- promise.setSuccess();
- }
-
- var src = kc.endpoints.checkSessionIframe();
- iframe.setAttribute('src', src );
- iframe.setAttribute('title', 'keycloak-session-iframe' );
- iframe.style.display = 'none';
- document.body.appendChild(iframe);
-
- var messageCallback = function(event) {
- if ((event.origin !== loginIframe.iframeOrigin) || (loginIframe.iframe.contentWindow !== event.source)) {
- return;
- }
-
- if (!(event.data == 'unchanged' || event.data == 'changed' || event.data == 'error')) {
- return;
- }
-
-
- if (event.data != 'unchanged') {
- kc.clearToken();
- }
-
- var callbacks = loginIframe.callbackList.splice(0, loginIframe.callbackList.length);
-
- for (var i = callbacks.length - 1; i >= 0; --i) {
- var promise = callbacks[i];
- if (event.data == 'error') {
- promise.setError();
- } else {
- promise.setSuccess(event.data == 'unchanged');
- }
- }
- };
-
- window.addEventListener('message', messageCallback, false);
-
- return promise.promise;
- }
-
- function scheduleCheckIframe() {
- if (loginIframe.enable) {
- if (kc.token) {
- setTimeout(function() {
- checkLoginIframe().then(function(unchanged) {
- if (unchanged) {
- scheduleCheckIframe();
- }
- });
- }, loginIframe.interval * 1000);
- }
- }
- }
-
- function checkLoginIframe() {
- var promise = createPromise();
-
- if (loginIframe.iframe && loginIframe.iframeOrigin ) {
- var msg = kc.clientId + ' ' + (kc.sessionId ? kc.sessionId : '');
- loginIframe.callbackList.push(promise);
- var origin = loginIframe.iframeOrigin;
- if (loginIframe.callbackList.length == 1) {
- loginIframe.iframe.contentWindow.postMessage(msg, origin);
- }
- } else {
- promise.setSuccess();
- }
-
- return promise.promise;
- }
-
- function check3pCookiesSupported() {
- var promise = createPromise();
-
- if (loginIframe.enable || kc.silentCheckSsoRedirectUri) {
- var iframe = document.createElement('iframe');
- iframe.setAttribute('src', kc.endpoints.thirdPartyCookiesIframe());
- iframe.setAttribute('title', 'keycloak-3p-check-iframe' );
- iframe.style.display = 'none';
- document.body.appendChild(iframe);
-
- var messageCallback = function(event) {
- if (iframe.contentWindow !== event.source) {
- return;
- }
-
- if (event.data !== "supported" && event.data !== "unsupported") {
- return;
- } else if (event.data === "unsupported") {
- loginIframe.enable = false;
- if (kc.silentCheckSsoFallback) {
- kc.silentCheckSsoRedirectUri = false;
- }
- logWarn("[KEYCLOAK] 3rd party cookies aren't supported by this browser. checkLoginIframe and " +
- "silent check-sso are not available.")
- }
-
- document.body.removeChild(iframe);
- window.removeEventListener("message", messageCallback);
- promise.setSuccess();
- };
-
- window.addEventListener('message', messageCallback, false);
- } else {
- promise.setSuccess();
- }
-
- return applyTimeoutToPromise(promise.promise, kc.messageReceiveTimeout, "Timeout when waiting for 3rd party check iframe message.");
- }
-
- function loadAdapter(type) {
- if (!type || type == 'default') {
- return {
- login: function(options) {
- window.location.replace(kc.createLoginUrl(options));
- return createPromise().promise;
- },
-
- logout: function(options) {
- window.location.replace(kc.createLogoutUrl(options));
- return createPromise().promise;
- },
-
- register: function(options) {
- window.location.replace(kc.createRegisterUrl(options));
- return createPromise().promise;
- },
-
- accountManagement : function() {
- var accountUrl = kc.createAccountUrl();
- if (typeof accountUrl !== 'undefined') {
- window.location.href = accountUrl;
- } else {
- throw "Not supported by the OIDC server";
- }
- return createPromise().promise;
- },
-
- redirectUri: function(options, encodeHash) {
- if (arguments.length == 1) {
- encodeHash = true;
- }
-
- if (options && options.redirectUri) {
- return options.redirectUri;
- } else if (kc.redirectUri) {
- return kc.redirectUri;
- } else {
- return location.href;
- }
- }
- };
- }
-
- if (type == 'cordova') {
- loginIframe.enable = false;
- var cordovaOpenWindowWrapper = function(loginUrl, target, options) {
- if (window.cordova && window.cordova.InAppBrowser) {
- // Use inappbrowser for IOS and Android if available
- return window.cordova.InAppBrowser.open(loginUrl, target, options);
- } else {
- return window.open(loginUrl, target, options);
- }
- };
-
- var shallowCloneCordovaOptions = function (userOptions) {
- if (userOptions && userOptions.cordovaOptions) {
- return Object.keys(userOptions.cordovaOptions).reduce(function (options, optionName) {
- options[optionName] = userOptions.cordovaOptions[optionName];
- return options;
- }, {});
- } else {
- return {};
- }
- };
-
- var formatCordovaOptions = function (cordovaOptions) {
- return Object.keys(cordovaOptions).reduce(function (options, optionName) {
- options.push(optionName+"="+cordovaOptions[optionName]);
- return options;
- }, []).join(",");
- };
-
- var createCordovaOptions = function (userOptions) {
- var cordovaOptions = shallowCloneCordovaOptions(userOptions);
- cordovaOptions.location = 'no';
- if (userOptions && userOptions.prompt == 'none') {
- cordovaOptions.hidden = 'yes';
- }
- return formatCordovaOptions(cordovaOptions);
- };
-
- return {
- login: function(options) {
- var promise = createPromise();
-
- var cordovaOptions = createCordovaOptions(options);
- var loginUrl = kc.createLoginUrl(options);
- var ref = cordovaOpenWindowWrapper(loginUrl, '_blank', cordovaOptions);
- var completed = false;
-
- var closed = false;
- var closeBrowser = function() {
- closed = true;
- ref.close();
- };
-
- ref.addEventListener('loadstart', function(event) {
- if (event.url.indexOf('http://localhost') == 0) {
- var callback = parseCallback(event.url);
- processCallback(callback, promise);
- closeBrowser();
- completed = true;
- }
- });
-
- ref.addEventListener('loaderror', function(event) {
- if (!completed) {
- if (event.url.indexOf('http://localhost') == 0) {
- var callback = parseCallback(event.url);
- processCallback(callback, promise);
- closeBrowser();
- completed = true;
- } else {
- promise.setError();
- closeBrowser();
- }
- }
- });
-
- ref.addEventListener('exit', function(event) {
- if (!closed) {
- promise.setError({
- reason: "closed_by_user"
- });
- }
- });
-
- return promise.promise;
- },
-
- logout: function(options) {
- var promise = createPromise();
-
- var logoutUrl = kc.createLogoutUrl(options);
- var ref = cordovaOpenWindowWrapper(logoutUrl, '_blank', 'location=no,hidden=yes,clearcache=yes');
-
- var error;
-
- ref.addEventListener('loadstart', function(event) {
- if (event.url.indexOf('http://localhost') == 0) {
- ref.close();
- }
- });
-
- ref.addEventListener('loaderror', function(event) {
- if (event.url.indexOf('http://localhost') == 0) {
- ref.close();
- } else {
- error = true;
- ref.close();
- }
- });
-
- ref.addEventListener('exit', function(event) {
- if (error) {
- promise.setError();
- } else {
- kc.clearToken();
- promise.setSuccess();
- }
- });
-
- return promise.promise;
- },
-
- register : function(options) {
- var promise = createPromise();
- var registerUrl = kc.createRegisterUrl();
- var cordovaOptions = createCordovaOptions(options);
- var ref = cordovaOpenWindowWrapper(registerUrl, '_blank', cordovaOptions);
- ref.addEventListener('loadstart', function(event) {
- if (event.url.indexOf('http://localhost') == 0) {
- ref.close();
- var oauth = parseCallback(event.url);
- processCallback(oauth, promise);
- }
- });
- return promise.promise;
- },
-
- accountManagement : function() {
- var accountUrl = kc.createAccountUrl();
- if (typeof accountUrl !== 'undefined') {
- var ref = cordovaOpenWindowWrapper(accountUrl, '_blank', 'location=no');
- ref.addEventListener('loadstart', function(event) {
- if (event.url.indexOf('http://localhost') == 0) {
- ref.close();
- }
- });
- } else {
- throw "Not supported by the OIDC server";
- }
- },
-
- redirectUri: function(options) {
- return 'http://localhost';
- }
- }
- }
-
- if (type == 'cordova-native') {
- loginIframe.enable = false;
-
- return {
- login: function(options) {
- var promise = createPromise();
- var loginUrl = kc.createLoginUrl(options);
-
- universalLinks.subscribe('keycloak', function(event) {
- universalLinks.unsubscribe('keycloak');
- window.cordova.plugins.browsertab.close();
- var oauth = parseCallback(event.url);
- processCallback(oauth, promise);
- });
-
- window.cordova.plugins.browsertab.openUrl(loginUrl);
- return promise.promise;
- },
-
- logout: function(options) {
- var promise = createPromise();
- var logoutUrl = kc.createLogoutUrl(options);
-
- universalLinks.subscribe('keycloak', function(event) {
- universalLinks.unsubscribe('keycloak');
- window.cordova.plugins.browsertab.close();
- kc.clearToken();
- promise.setSuccess();
- });
-
- window.cordova.plugins.browsertab.openUrl(logoutUrl);
- return promise.promise;
- },
-
- register : function(options) {
- var promise = createPromise();
- var registerUrl = kc.createRegisterUrl(options);
- universalLinks.subscribe('keycloak' , function(event) {
- universalLinks.unsubscribe('keycloak');
- window.cordova.plugins.browsertab.close();
- var oauth = parseCallback(event.url);
- processCallback(oauth, promise);
- });
- window.cordova.plugins.browsertab.openUrl(registerUrl);
- return promise.promise;
-
- },
-
- accountManagement : function() {
- var accountUrl = kc.createAccountUrl();
- if (typeof accountUrl !== 'undefined') {
- window.cordova.plugins.browsertab.openUrl(accountUrl);
- } else {
- throw "Not supported by the OIDC server";
- }
- },
-
- redirectUri: function(options) {
- if (options && options.redirectUri) {
- return options.redirectUri;
- } else if (kc.redirectUri) {
- return kc.redirectUri;
- } else {
- return "http://localhost";
- }
- }
- }
- }
-
- throw 'invalid adapter type: ' + type;
- }
-
- var LocalStorage = function() {
- if (!(this instanceof LocalStorage)) {
- return new LocalStorage();
- }
-
- localStorage.setItem('kc-test', 'test');
- localStorage.removeItem('kc-test');
-
- var cs = this;
-
- function clearExpired() {
- var time = new Date().getTime();
- for (var i = 0; i < localStorage.length; i++) {
- var key = localStorage.key(i);
- if (key && key.indexOf('kc-callback-') == 0) {
- var value = localStorage.getItem(key);
- if (value) {
- try {
- var expires = JSON.parse(value).expires;
- if (!expires || expires < time) {
- localStorage.removeItem(key);
- }
- } catch (err) {
- localStorage.removeItem(key);
- }
- }
- }
- }
- }
-
- cs.get = function(state) {
- if (!state) {
- return;
- }
-
- var key = 'kc-callback-' + state;
- var value = localStorage.getItem(key);
- if (value) {
- localStorage.removeItem(key);
- value = JSON.parse(value);
- }
-
- clearExpired();
- return value;
- };
-
- cs.add = function(state) {
- clearExpired();
-
- var key = 'kc-callback-' + state.state;
- state.expires = new Date().getTime() + (60 * 60 * 1000);
- localStorage.setItem(key, JSON.stringify(state));
- };
- };
-
- var CookieStorage = function() {
- if (!(this instanceof CookieStorage)) {
- return new CookieStorage();
- }
-
- var cs = this;
-
- cs.get = function(state) {
- if (!state) {
- return;
- }
-
- var value = getCookie('kc-callback-' + state);
- setCookie('kc-callback-' + state, '', cookieExpiration(-100));
- if (value) {
- return JSON.parse(value);
- }
- };
-
- cs.add = function(state) {
- setCookie('kc-callback-' + state.state, JSON.stringify(state), cookieExpiration(60));
- };
-
- cs.removeItem = function(key) {
- setCookie(key, '', cookieExpiration(-100));
- };
-
- var cookieExpiration = function (minutes) {
- var exp = new Date();
- exp.setTime(exp.getTime() + (minutes*60*1000));
- return exp;
- };
-
- var getCookie = function (key) {
- var name = key + '=';
- var ca = document.cookie.split(';');
- for (var i = 0; i < ca.length; i++) {
- var c = ca[i];
- while (c.charAt(0) == ' ') {
- c = c.substring(1);
- }
- if (c.indexOf(name) == 0) {
- return c.substring(name.length, c.length);
- }
- }
- return '';
- };
-
- var setCookie = function (key, value, expirationDate) {
- var cookie = key + '=' + value + '; '
- + 'expires=' + expirationDate.toUTCString() + '; ';
- document.cookie = cookie;
- }
- };
-
- function createCallbackStorage() {
- try {
- return new LocalStorage();
- } catch (err) {
- }
-
- return new CookieStorage();
- }
-
- function createLogger(fn) {
- return function() {
- if (kc.enableLogging) {
- fn.apply(console, Array.prototype.slice.call(arguments));
- }
- };
- }
- }
-
- return Keycloak;
-})
diff --git a/adapters/oidc/js/tsconfig.json b/adapters/oidc/js/tsconfig.json
new file mode 100644
index 000000000000..34cf7dd5c6b9
--- /dev/null
+++ b/adapters/oidc/js/tsconfig.json
@@ -0,0 +1,5 @@
+{
+ "compilerOptions": {
+ "allowSyntheticDefaultImports": true
+ }
+}
diff --git a/adapters/oidc/kcinit/README.md b/adapters/oidc/kcinit/README.md
deleted file mode 100755
index fb0fdbecc510..000000000000
--- a/adapters/oidc/kcinit/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-CLI Single Sign On
-===================================
-
-This java-based utility is meant for providing Keycloak integration to
-command line applications that are either written in Java or another language. The
-idea is that the Java app provided by this utility performs a login for a specific
-client, parses responses, and exports an access token as an environment variable
-that can be used by the command line utility you are accessing.
-
diff --git a/adapters/oidc/kcinit/pom.xml b/adapters/oidc/kcinit/pom.xml
deleted file mode 100755
index c48f4365df2c..000000000000
--- a/adapters/oidc/kcinit/pom.xml
+++ /dev/null
@@ -1,76 +0,0 @@
-
-
-
-
-
- keycloak-parent
- org.keycloak
- 16.0.0-SNAPSHOT
- ../../../pom.xml
-
- 4.0.0
-
- kcinit
- Keycloak CLI SSO Framework
-
-
-
-
- org.keycloak
- keycloak-installed-adapter
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-shade-plugin
- 3.0.0
-
-
-
- org.keycloak.adapters.KcinitMain
-
-
-
-
-
- *:*
-
- META-INF/*.SF
- META-INF/*.DSA
- META-INF/*.RSA
-
-
-
-
-
-
- package
-
- shade
-
-
-
-
-
-
-
-
diff --git a/adapters/oidc/kcinit/src/main/bin/kcinit b/adapters/oidc/kcinit/src/main/bin/kcinit
deleted file mode 100755
index 4f5c2c6a7215..000000000000
--- a/adapters/oidc/kcinit/src/main/bin/kcinit
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/bin/bash
-
-case "`uname`" in
- CYGWIN*)
- CFILE = `cygpath "$0"`
- RESOLVED_NAME=`readlink -f "$CFILE"`
- ;;
- Darwin*)
- RESOLVED_NAME=`readlink "$0"`
- ;;
- FreeBSD)
- RESOLVED_NAME=`readlink -f "$0"`
- ;;
- Linux)
- RESOLVED_NAME=`readlink -f "$0"`
- ;;
-esac
-
-if [ "x$RESOLVED_NAME" = "x" ]; then
- RESOLVED_NAME="$0"
-fi
-
-SCRIPTPATH=`dirname "$RESOLVED_NAME"`
-JAR=$SCRIPTPATH/kcinit-${project.version}.jar
-
-java -jar $JAR $@
diff --git a/adapters/oidc/kcinit/src/main/bin/kcinit.bat b/adapters/oidc/kcinit/src/main/bin/kcinit.bat
deleted file mode 100755
index 90553091cc0d..000000000000
--- a/adapters/oidc/kcinit/src/main/bin/kcinit.bat
+++ /dev/null
@@ -1,8 +0,0 @@
-@echo off
-
-if "%OS%" == "Windows_NT" (
- set "DIRNAME=%~dp0%"
-) else (
- set DIRNAME=.\
-)
-java -jar %DIRNAME%\kcinit-${project.version}.jar %*
diff --git a/adapters/oidc/osgi-adapter/pom.xml b/adapters/oidc/osgi-adapter/pom.xml
index 961ad0b214b7..92a8b99b1b9e 100755
--- a/adapters/oidc/osgi-adapter/pom.xml
+++ b/adapters/oidc/osgi-adapter/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/oidc/pom.xml b/adapters/oidc/pom.xml
index e031a80f73e5..babca203e68c 100755
--- a/adapters/oidc/pom.xml
+++ b/adapters/oidc/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../pom.xml
Keycloak OIDC Client Adapter Modules
@@ -34,7 +34,6 @@
adapter-core
installed
fuse7
- kcinit
jaxrs-oauth-client
jetty
js
diff --git a/adapters/oidc/servlet-filter/pom.xml b/adapters/oidc/servlet-filter/pom.xml
index 64c839f6ef38..550442c89602 100755
--- a/adapters/oidc/servlet-filter/pom.xml
+++ b/adapters/oidc/servlet-filter/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../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 52b9ce5c2a14..9b04c9f4a383 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../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 2292348ade61..c329a716c985 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../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 150bfecb2fb8..b953ef5111d2 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
spring-boot-legacy-container-bundle
diff --git a/adapters/oidc/spring-boot/pom.xml b/adapters/oidc/spring-boot/pom.xml
index 31ecfe307f2b..01c73e9bebd9 100755
--- a/adapters/oidc/spring-boot/pom.xml
+++ b/adapters/oidc/spring-boot/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/oidc/spring-boot2/pom.xml b/adapters/oidc/spring-boot2/pom.xml
index fac109000bdc..01d48b693b39 100755
--- a/adapters/oidc/spring-boot2/pom.xml
+++ b/adapters/oidc/spring-boot2/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/oidc/spring-security/pom.xml b/adapters/oidc/spring-security/pom.xml
index 3def85ce3694..6f8550ea6fe1 100644
--- a/adapters/oidc/spring-security/pom.xml
+++ b/adapters/oidc/spring-security/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/oidc/tomcat/pom.xml b/adapters/oidc/tomcat/pom.xml
index 2632ef964e0f..4d0451c0b560 100755
--- a/adapters/oidc/tomcat/pom.xml
+++ b/adapters/oidc/tomcat/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
Keycloak Tomcat Integration
diff --git a/adapters/oidc/tomcat/tomcat-core/pom.xml b/adapters/oidc/tomcat/tomcat-core/pom.xml
index da61294cff46..da376921ee3f 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../pom.xml
4.0.0
diff --git a/adapters/oidc/tomcat/tomcat/pom.xml b/adapters/oidc/tomcat/tomcat/pom.xml
index a10ae6ee0f7d..11c4d96ed3bb 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../pom.xml
4.0.0
diff --git a/adapters/oidc/tomcat/tomcat/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java b/adapters/oidc/tomcat/tomcat/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java
index 9da6964405a2..02868820579b 100755
--- a/adapters/oidc/tomcat/tomcat/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java
+++ b/adapters/oidc/tomcat/tomcat/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java
@@ -110,4 +110,9 @@ protected AdapterTokenStore getTokenStore(Request request, HttpFacade facade, Ke
protected AbstractAuthenticatedActionsValve createAuthenticatedActionsValve(AdapterDeploymentContext deploymentContext, Valve next, Container container) {
return new AuthenticatedActionsValve(deploymentContext, next, container);
}
+
+ @Override
+ protected CatalinaRequestAuthenticator createRequestAuthenticator(Request request, CatalinaHttpFacade facade, KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
+ return new TomcatRequestAuthenticator(deployment, tokenStore, facade, request, createPrincipalFactory());
+ }
}
diff --git a/adapters/oidc/tomcat/tomcat7/pom.xml b/adapters/oidc/tomcat/tomcat7/pom.xml
index ca918c87ad8e..30b2e8b8ef2a 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../pom.xml
4.0.0
diff --git a/adapters/oidc/undertow/pom.xml b/adapters/oidc/undertow/pom.xml
index 5998e26221c8..70e420b88569 100755
--- a/adapters/oidc/undertow/pom.xml
+++ b/adapters/oidc/undertow/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/oidc/wildfly-elytron/pom.xml b/adapters/oidc/wildfly-elytron/pom.xml
index 044bac5f2978..74ebb10181d7 100755
--- a/adapters/oidc/wildfly-elytron/pom.xml
+++ b/adapters/oidc/wildfly-elytron/pom.xml
@@ -22,7 +22,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/oidc/wildfly/pom.xml b/adapters/oidc/wildfly/pom.xml
index 6bb9539d7a49..15d260cb1359 100755
--- a/adapters/oidc/wildfly/pom.xml
+++ b/adapters/oidc/wildfly/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
Keycloak WildFly Integration
diff --git a/adapters/oidc/wildfly/wildfly-adapter/pom.xml b/adapters/oidc/wildfly/wildfly-adapter/pom.xml
index 096d140beb21..e056f6ee0f1a 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../../pom.xml
4.0.0
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/pom.xml b/adapters/oidc/wildfly/wildfly-subsystem/pom.xml
index 096b888a4f10..b50a46f94c0d 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../../pom.xml
diff --git a/adapters/pom.xml b/adapters/pom.xml
index d8c1c2b00f70..905846d4b34b 100755
--- a/adapters/pom.xml
+++ b/adapters/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../pom.xml
Keycloak Adapters
diff --git a/adapters/saml/as7-eap6/adapter/pom.xml b/adapters/saml/as7-eap6/adapter/pom.xml
index 807bbb57d8c4..58f52a99c9d3 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../pom.xml
4.0.0
diff --git a/adapters/saml/as7-eap6/pom.xml b/adapters/saml/as7-eap6/pom.xml
index 77a8cdf2f541..6d5dc032bfa2 100755
--- a/adapters/saml/as7-eap6/pom.xml
+++ b/adapters/saml/as7-eap6/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../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 07116a46dee5..c8aeb154481a 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../pom.xml
diff --git a/adapters/saml/core-public/pom.xml b/adapters/saml/core-public/pom.xml
index 33c6cd4ee42a..189960c70cec 100755
--- a/adapters/saml/core-public/pom.xml
+++ b/adapters/saml/core-public/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/saml/core/pom.xml b/adapters/saml/core/pom.xml
index 652a9804860f..29344d404f15 100755
--- a/adapters/saml/core/pom.xml
+++ b/adapters/saml/core/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
index 59b453c1062f..7b728ade620f 100644
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
@@ -33,6 +33,7 @@
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.common.VerificationException;
+import org.keycloak.common.util.Base64;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.dom.saml.v2.assertion.AssertionType;
@@ -57,7 +58,6 @@
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.common.exceptions.ConfigurationException;
import org.keycloak.saml.common.exceptions.ProcessingException;
-import org.keycloak.saml.common.util.Base64;
import org.keycloak.saml.common.util.DocumentUtil;
import org.keycloak.saml.processing.api.saml.v2.sig.SAML2Signature;
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
diff --git a/adapters/saml/jetty/jetty-core/pom.xml b/adapters/saml/jetty/jetty-core/pom.xml
index 5cea4fda9317..e676adecda44 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../../pom.xml
4.0.0
diff --git a/adapters/saml/jetty/jetty9.2/pom.xml b/adapters/saml/jetty/jetty9.2/pom.xml
index a3fed5666340..0400f2523d89 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../../pom.xml
4.0.0
diff --git a/adapters/saml/jetty/jetty9.3/pom.xml b/adapters/saml/jetty/jetty9.3/pom.xml
index ec0c3641c23e..b0d1d73fb819 100644
--- a/adapters/saml/jetty/jetty9.3/pom.xml
+++ b/adapters/saml/jetty/jetty9.3/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../../pom.xml
4.0.0
diff --git a/adapters/saml/jetty/jetty9.4/pom.xml b/adapters/saml/jetty/jetty9.4/pom.xml
index b9681b5dc6f7..d5221cac99fc 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../../pom.xml
4.0.0
diff --git a/adapters/saml/jetty/pom.xml b/adapters/saml/jetty/pom.xml
index a1865ad8a00a..8950007b02c3 100755
--- a/adapters/saml/jetty/pom.xml
+++ b/adapters/saml/jetty/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
Keycloak SAML Jetty Integration
diff --git a/adapters/saml/pom.xml b/adapters/saml/pom.xml
index ffec55a0f7a6..08f49296b85d 100755
--- a/adapters/saml/pom.xml
+++ b/adapters/saml/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../pom.xml
Keycloak SAML Client Adapter Modules
diff --git a/adapters/saml/servlet-filter/pom.xml b/adapters/saml/servlet-filter/pom.xml
index 3329fc0e05f8..8c7f44c08ac7 100755
--- a/adapters/saml/servlet-filter/pom.xml
+++ b/adapters/saml/servlet-filter/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/saml/tomcat/pom.xml b/adapters/saml/tomcat/pom.xml
index 9ed934f691a0..2337ebe6bc24 100755
--- a/adapters/saml/tomcat/pom.xml
+++ b/adapters/saml/tomcat/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../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 9e7de5c65224..855df37bed93 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../pom.xml
4.0.0
diff --git a/adapters/saml/tomcat/tomcat/pom.xml b/adapters/saml/tomcat/tomcat/pom.xml
index 5e189ba73115..df556af9f11f 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../pom.xml
4.0.0
diff --git a/adapters/saml/tomcat/tomcat7/pom.xml b/adapters/saml/tomcat/tomcat7/pom.xml
index 3998a6aca364..c2c2267f39b7 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../pom.xml
4.0.0
diff --git a/adapters/saml/undertow/pom.xml b/adapters/saml/undertow/pom.xml
index e0264a8bebcd..9ea826ac8720 100755
--- a/adapters/saml/undertow/pom.xml
+++ b/adapters/saml/undertow/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/saml/wildfly-elytron/pom.xml b/adapters/saml/wildfly-elytron/pom.xml
index e82ab70ae634..253dc2c7df97 100755
--- a/adapters/saml/wildfly-elytron/pom.xml
+++ b/adapters/saml/wildfly-elytron/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/KeycloakHttpServerAuthenticationMechanism.java b/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/KeycloakHttpServerAuthenticationMechanism.java
index 02b63edb3357..5f9beb653440 100644
--- a/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/KeycloakHttpServerAuthenticationMechanism.java
+++ b/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/KeycloakHttpServerAuthenticationMechanism.java
@@ -173,7 +173,13 @@ static void sendRedirect(final ElytronHttpFacade exchange, final String location
String path = uri.getPath();
String relativePath = exchange.getRequest().getRelativePath();
String contextPath = path.substring(0, path.indexOf(relativePath));
- String loc = exchange.getURI().getScheme() + "://" + exchange.getURI().getHost() + ":" + exchange.getURI().getPort() + contextPath + location;
+ String loc;
+ int port = uri.getPort();
+ if (port == -1) {
+ loc = uri.getScheme() + "://" + uri.getHost() + contextPath + location;
+ } else {
+ loc = uri.getScheme() + "://" + uri.getHost() + ":" + port + contextPath + location;
+ }
exchange.getResponse().setHeader("Location", loc);
}
exchange.getResponse().setStatus(HttpServletResponse.SC_FOUND);
diff --git a/adapters/saml/wildfly/pom.xml b/adapters/saml/wildfly/pom.xml
index 3262a6cd3862..f38515775074 100755
--- a/adapters/saml/wildfly/pom.xml
+++ b/adapters/saml/wildfly/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../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 ae4a42ce0aa3..2dde88827e83 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../../pom.xml
4.0.0
diff --git a/adapters/saml/wildfly/wildfly-subsystem/pom.xml b/adapters/saml/wildfly/wildfly-subsystem/pom.xml
index 935cb3b6fa38..284cc9d66329 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../../pom.xml
diff --git a/adapters/spi/adapter-spi/pom.xml b/adapters/spi/adapter-spi/pom.xml
index 5aaca5663a30..6368549e5eb0 100755
--- a/adapters/spi/adapter-spi/pom.xml
+++ b/adapters/spi/adapter-spi/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/spi/jboss-adapter-core/pom.xml b/adapters/spi/jboss-adapter-core/pom.xml
index 7b9c59453806..91c14ad24dff 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/spi/jetty-adapter-spi/pom.xml b/adapters/spi/jetty-adapter-spi/pom.xml
index ad07ef52e8ff..37fea6ba2085 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/spi/pom.xml b/adapters/spi/pom.xml
index 4790e878ccbc..018019a201e3 100755
--- a/adapters/spi/pom.xml
+++ b/adapters/spi/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../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 e0338db8c21a..2add88a402e6 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/spi/tomcat-adapter-spi/pom.xml b/adapters/spi/tomcat-adapter-spi/pom.xml
index cded8cbaa917..3da26641d005 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/adapters/spi/undertow-adapter-spi/pom.xml b/adapters/spi/undertow-adapter-spi/pom.xml
index 2fab55eed971..892a594772db 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
4.0.0
diff --git a/authz/client/pom.xml b/authz/client/pom.xml
index 266cbc946749..ef47c1ca0835 100644
--- a/authz/client/pom.xml
+++ b/authz/client/pom.xml
@@ -7,7 +7,7 @@
org.keycloak
keycloak-authz-parent
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../pom.xml
diff --git a/authz/policy/common/pom.xml b/authz/policy/common/pom.xml
index 2a3ca901d4b1..163a6df26ed1 100644
--- a/authz/policy/common/pom.xml
+++ b/authz/policy/common/pom.xml
@@ -25,7 +25,7 @@
org.keycloak
keycloak-authz-provider-parent
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../pom.xml
diff --git a/authz/policy/pom.xml b/authz/policy/pom.xml
index 74121e64ca34..52efc423b3a3 100644
--- a/authz/policy/pom.xml
+++ b/authz/policy/pom.xml
@@ -7,7 +7,7 @@
org.keycloak
keycloak-authz-parent
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../pom.xml
diff --git a/authz/pom.xml b/authz/pom.xml
index 93e8eaa9babf..4e81abe82b47 100644
--- a/authz/pom.xml
+++ b/authz/pom.xml
@@ -7,7 +7,7 @@
org.keycloak
keycloak-parent
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../pom.xml
diff --git a/boms/adapter/pom.xml b/boms/adapter/pom.xml
index 92670c136ea3..c86f264fcedb 100644
--- a/boms/adapter/pom.xml
+++ b/boms/adapter/pom.xml
@@ -22,7 +22,7 @@
org.keycloak.bom
keycloak-bom-parent
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
org.keycloak.bom
diff --git a/boms/misc/pom.xml b/boms/misc/pom.xml
index 96514c781377..a1d39d110502 100644
--- a/boms/misc/pom.xml
+++ b/boms/misc/pom.xml
@@ -22,7 +22,7 @@
org.keycloak.bom
keycloak-bom-parent
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
org.keycloak.bom
diff --git a/boms/pom.xml b/boms/pom.xml
index a64833f33521..af91b5046ca7 100644
--- a/boms/pom.xml
+++ b/boms/pom.xml
@@ -26,7 +26,7 @@
org.keycloak.bom
keycloak-bom-parent
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
pom
diff --git a/boms/spi/pom.xml b/boms/spi/pom.xml
index 0d2af4f74f33..2eb91dc94838 100644
--- a/boms/spi/pom.xml
+++ b/boms/spi/pom.xml
@@ -23,7 +23,7 @@
org.keycloak.bom
keycloak-bom-parent
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
org.keycloak.bom
diff --git a/common/pom.xml b/common/pom.xml
index cc01df5b7154..ef25e4513fe8 100755
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../pom.xml
4.0.0
diff --git a/common/src/main/java/org/keycloak/common/Profile.java b/common/src/main/java/org/keycloak/common/Profile.java
index e778083fb3b8..faad755025f7 100755
--- a/common/src/main/java/org/keycloak/common/Profile.java
+++ b/common/src/main/java/org/keycloak/common/Profile.java
@@ -64,7 +64,8 @@ public enum Feature {
CIBA(Type.DEFAULT),
MAP_STORAGE(Type.EXPERIMENTAL),
PAR(Type.DEFAULT),
- DECLARATIVE_USER_PROFILE(Type.PREVIEW);
+ DECLARATIVE_USER_PROFILE(Type.PREVIEW),
+ DYNAMIC_SCOPES(Type.EXPERIMENTAL);
private final Type typeProject;
private final Type typeProduct;
diff --git a/common/src/main/java/org/keycloak/common/util/Base64.java b/common/src/main/java/org/keycloak/common/util/Base64.java
index ae1897da3311..3840d688fc85 100644
--- a/common/src/main/java/org/keycloak/common/util/Base64.java
+++ b/common/src/main/java/org/keycloak/common/util/Base64.java
@@ -1,5 +1,7 @@
package org.keycloak.common.util;
+import java.io.IOException;
+
/**
* Encodes and decodes to and from Base64 notation.
* Homepage: http://iharder.net/base64 .
@@ -35,6 +37,10 @@
* Change Log:
*
*
+ * v2.3.8 - Fixed automatic gzip decoding, based on the content,
+ * as this may lead to unexpected behaviour. Request either gzipped
+ * or non gzipped decoding as excepted. Automatic encoding is especially
+ * problematic with generated input (see KEYCLOAK-18914 for a detailed case).
* v2.3.7 - Fixed subtle bug when base 64 input stream contained the
* value 01111111, which is an invalid base 64 character but should not
* throw an ArrayIndexOutOfBoundsException either. Led to discovery of
@@ -76,7 +82,7 @@
* RFC3548 .
* Throws exceptions instead of returning null values. Because some operations
* (especially those that may permit the GZIP option) use IO streams, there
- * is a possiblity of an java.io.IOException being thrown. After some discussion and
+ * is a possibility of an java.io.IOException being thrown. After some discussion and
* thought, I've changed the behavior of the methods to throw java.io.IOExceptions
* rather than return null if ever there's an error. I think this is more
* appropriate, though it will require some changes to your code. Sorry,
@@ -167,9 +173,8 @@ public class Base64
/** Specify that data should be gzip-compressed in second bit. Value is two. */
public final static int GZIP = 2;
- /** Specify that gzipped data should not be automatically gunzipped. */
- public final static int DONT_GUNZIP = 4;
-
+ /** Specify that data should be gunzipped. */
+ public final static int GUNZIP = 4;
/** Do break lines when encoding. Value is 8. */
public final static int DO_BREAK_LINES = 8;
@@ -179,7 +184,7 @@ public class Base64
* in Section 4 of RFC3548:
* http://www.faqs.org/rfcs/rfc3548.html .
* It is important to note that data encoded this way is not officially valid Base64,
- * or at the very least should not be called Base64 without also specifying that is
+ * or at the very least should not be called Base64 without also specifying that it
* was encoded using the URL- and Filename-safe dialect.
*/
public final static int URL_SAFE = 16;
@@ -476,7 +481,7 @@ private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes,
* anywhere along their length by specifying
* srcOffset and destOffset .
* This method does not check to make sure your arrays
- * are large enough to accomodate srcOffset + 3 for
+ * are large enough to accommodate srcOffset + 3 for
* the source array or destOffset + 4 for
* the destination array.
* The actual number of significant bytes in your array is
@@ -548,7 +553,7 @@ private static byte[] encode3to4(
* writing it to the encoded ByteBuffer.
* This is an experimental feature. Currently it does not
* pass along any options (such as {@link #DO_BREAK_LINES}
- * or {@link #GZIP}.
+ * or {@link #GZIP}).
*
* @param raw input buffer
* @param encoded output buffer
@@ -733,7 +738,7 @@ public static String encodeBytes( byte[] source ) {
* Example options:
* GZIP: gzip-compresses object before encoding it.
* DO_BREAK_LINES: break lines at 76 characters
- * Note: Technically, this makes your encoding non-compliant.
+ * Note: Technically, without line break your encoding may become non-compliant (see rfc2045 and rfc4648).
*
*
* Example: encodeBytes( myData, Base64.GZIP ) or
@@ -1115,15 +1120,8 @@ else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) {
* @return decoded data
* @since 2.3.1
*/
- public static byte[] decode( byte[] source )
- throws java.io.IOException {
- byte[] decoded = null;
-// try {
- decoded = decode( source, 0, source.length, Base64.NO_OPTIONS );
-// } catch( java.io.IOException ex ) {
-// assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage();
-// }
- return decoded;
+ public static byte[] decode( byte[] source ) throws java.io.IOException {
+ return decode( source, 0, source.length, Base64.NO_OPTIONS );
}
@@ -1231,9 +1229,9 @@ public static byte[] decode( String s ) throws java.io.IOException {
* detecting gzip-compressed data and decompressing it.
*
* @param s the string to decode
- * @param options encode options such as URL_SAFE
+ * @param options decode options such as URL_SAFE or GUNZIP
* @return the decoded data
- * @throws java.io.IOException if there is an error
+ * @throws java.io.IOException if there is an error (invalid character in source string or gunzip error)
* @throws NullPointerException if s is null
* @since 1.4
*/
@@ -1257,46 +1255,41 @@ public static byte[] decode( String s, int options ) throws java.io.IOException
// Check to see if it's gzip-compressed
// GZIP Magic Two-Byte Number: 0x8b1f (35615)
- boolean dontGunzip = (options & DONT_GUNZIP) != 0;
- if( (bytes != null) && (bytes.length >= 4) && (!dontGunzip) ) {
+ boolean doGunzip = (options & GUNZIP) != 0;
+ if( (bytes != null) && (bytes.length >= 4) && doGunzip ) {
int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00);
- if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) {
- java.io.ByteArrayInputStream bais = null;
- java.util.zip.GZIPInputStream gzis = null;
- java.io.ByteArrayOutputStream baos = null;
- byte[] buffer = new byte[2048];
- int length = 0;
-
- try {
- baos = new java.io.ByteArrayOutputStream();
- bais = new java.io.ByteArrayInputStream( bytes );
- gzis = new java.util.zip.GZIPInputStream( bais );
-
- while( ( length = gzis.read( buffer ) ) >= 0 ) {
- baos.write(buffer,0,length);
- } // end while: reading input
-
- // No error? Get new bytes.
- bytes = baos.toByteArray();
-
- } // end try
- catch( java.io.IOException e ) {
- if (e.getMessage().equals("Unsupported compression method")) {
- System.out.println("Base64 decoding: Ignoring GZIP header and just returning originally-decoded bytes."); // Better to log as debug, but jboss logging not available in the module :/
- } else {
- e.printStackTrace();
- }
-
- // Just return originally-decoded bytes
- } // end catch
- finally {
- try{ baos.close(); } catch( Exception e ){}
- try{ gzis.close(); } catch( Exception e ){}
- try{ bais.close(); } catch( Exception e ){}
- } // end finally
-
- } // end if: gzipped
+ if( java.util.zip.GZIPInputStream.GZIP_MAGIC != head ) {
+ throw new IOException("Provided data has no GZIP magic header.");
+ }
+ java.io.ByteArrayInputStream bais = null;
+ java.util.zip.GZIPInputStream gzis = null;
+ java.io.ByteArrayOutputStream baos = null;
+ byte[] buffer = new byte[2048];
+ int length = 0;
+
+ try {
+ baos = new java.io.ByteArrayOutputStream();
+ bais = new java.io.ByteArrayInputStream( bytes );
+ gzis = new java.util.zip.GZIPInputStream( bais );
+
+ while( ( length = gzis.read( buffer ) ) >= 0 ) {
+ baos.write(buffer,0,length);
+ } // end while: reading input
+
+ // No error? Get new bytes.
+ bytes = baos.toByteArray();
+
+ } // end try
+ catch( java.io.IOException e ) {
+ throw new IOException("Failed to gunzip", e);
+ } // end catch
+ finally {
+ try{ baos.close(); } catch( Exception e ){}
+ try{ gzis.close(); } catch( Exception e ){}
+ try{ bais.close(); } catch( Exception e ){}
+ } // end finally
+
} // end if: bytes.length >= 2
return bytes;
diff --git a/common/src/main/java/org/keycloak/common/util/Base64Url.java b/common/src/main/java/org/keycloak/common/util/Base64Url.java
index 4a351c9de536..6bf9ac595fc1 100755
--- a/common/src/main/java/org/keycloak/common/util/Base64Url.java
+++ b/common/src/main/java/org/keycloak/common/util/Base64Url.java
@@ -31,8 +31,7 @@ public static String encode(byte[] bytes) {
public static byte[] decode(String s) {
s = encodeBase64UrlToBase64(s);
try {
- // KEYCLOAK-2479 : Avoid to try gzip decoding as for some objects, it may display exception to STDERR. And we know that object wasn't encoded as GZIP
- return Base64.decode(s, Base64.DONT_GUNZIP);
+ return Base64.decode(s);
} catch (Exception e) {
throw new RuntimeException(e);
}
diff --git a/common/src/test/java/org/keycloak/common/ProfileTest.java b/common/src/test/java/org/keycloak/common/ProfileTest.java
index 696c10a268f2..8fc41c6a9957 100644
--- a/common/src/test/java/org/keycloak/common/ProfileTest.java
+++ b/common/src/test/java/org/keycloak/common/ProfileTest.java
@@ -21,7 +21,7 @@ public class ProfileTest {
@Test
public void checkDefaultsKeycloak() {
Assert.assertEquals("community", Profile.getName());
- assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.ADMIN2, Profile.Feature.DOCKER, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.UPLOAD_SCRIPTS, Profile.Feature.MAP_STORAGE, Profile.Feature.DECLARATIVE_USER_PROFILE);
+ assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DYNAMIC_SCOPES, Profile.Feature.ADMIN2, Profile.Feature.DOCKER, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.UPLOAD_SCRIPTS, Profile.Feature.MAP_STORAGE, Profile.Feature.DECLARATIVE_USER_PROFILE);
assertEquals(Profile.getPreviewFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.DECLARATIVE_USER_PROFILE);
assertEquals(Profile.getDeprecatedFeatures(), Profile.Feature.UPLOAD_SCRIPTS);
@@ -37,7 +37,7 @@ public void checkDefaultsRH_SSO() {
Profile.init();
Assert.assertEquals("product", Profile.getName());
- assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.ADMIN2, Profile.Feature.DOCKER, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.UPLOAD_SCRIPTS, Profile.Feature.WEB_AUTHN, Profile.Feature.MAP_STORAGE, Profile.Feature.DECLARATIVE_USER_PROFILE);
+ assertEquals(Profile.getDisabledFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.DYNAMIC_SCOPES, Profile.Feature.ADMIN2, Profile.Feature.DOCKER, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.UPLOAD_SCRIPTS, Profile.Feature.WEB_AUTHN, Profile.Feature.MAP_STORAGE, Profile.Feature.DECLARATIVE_USER_PROFILE);
assertEquals(Profile.getPreviewFeatures(), Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.OPENSHIFT_INTEGRATION, Profile.Feature.WEB_AUTHN, Profile.Feature.DECLARATIVE_USER_PROFILE);
assertEquals(Profile.getDeprecatedFeatures(), Profile.Feature.UPLOAD_SCRIPTS);
diff --git a/common/src/test/java/org/keycloak/common/util/Base64DecodeTest.java b/common/src/test/java/org/keycloak/common/util/Base64DecodeTest.java
new file mode 100644
index 000000000000..6c32f896bd47
--- /dev/null
+++ b/common/src/test/java/org/keycloak/common/util/Base64DecodeTest.java
@@ -0,0 +1,135 @@
+package org.keycloak.common.util;
+
+import org.hamcrest.MatcherAssert;
+import org.junit.Test;
+
+import java.io.IOException;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.instanceOf;
+
+/**
+ * Test for BASE64 decode implementation.
+ *
+ * @author Hans-Christian Halfbrodt
+ */
+public class Base64DecodeTest {
+
+ @Test
+ public void decode_simple() throws IOException {
+ // high level string variant
+ final String testData = "test data";
+ final String encoded = "dGVzdCBkYXRh";
+ final String decoded = new String(Base64.decode(encoded));
+ assertThat(decoded, equalTo(testData));
+
+ // low level byte array variant
+ final byte[] encoded2 = encoded.getBytes();
+ final String decoded2 = new String(Base64.decode(encoded2));
+ assertThat(decoded2, equalTo(testData));
+ }
+
+ @Test
+ public void decode_doNotUseGzip() throws IOException {
+ // Input has gzip magic byte by coincidence and should not be gunzipped. (KEYCLOAK-18914)
+ // high level string variant
+ final byte[] testData = new byte[]{
+ 31, -117, 8, -56, 1, 1, 1, 1, 1, 1, 43, 73, 45, 46,
+ 81, 72, 73, 44, 73, 4, 1, -78, -82, 8, -45, 9, 1, 1, 1};
+ final String encoded = "H4sIyAEBAQEBAStJLS5RSEksSQQBsq4I0wkBAQE=";
+ final byte[] decoded = Base64.decode(encoded);
+ assertThat(decoded, equalTo(testData));
+
+ // low level byte array variant
+ final byte[] encoded2 = encoded.getBytes();
+ final byte[] decoded2 = Base64.decode(encoded2);
+ assertThat(decoded2, equalTo(testData));
+ }
+
+ @Test
+ public void decode_gzip() throws IOException {
+ // high level string variant
+ final String testData = "test data";
+ final String encoded = "H4sIAAAAAAAAACtJLS5RSEksSQQAsq4I0wkAAAA=";
+ final String decoded = new String(Base64.decode(encoded, Base64.GUNZIP));
+ assertThat(decoded, equalTo(testData));
+
+ // low level byte array variant
+ // specified to ignore gunzip option (see javadoc)
+ final byte[] expected2 = new byte[]{
+ 31, -117, 8, 0, 0, 0, 0, 0, 0, 0, 43, 73,
+ 45, 46, 81, 72, 73, 44, 73, 4, 0, -78, -82,
+ 8, -45, 9, 0, 0, 0};
+ final byte[] encoded2 = encoded.getBytes();
+ final byte[] decoded2 = Base64.decode(encoded2, 0, encoded2.length, Base64.GUNZIP);
+ assertThat(decoded2, equalTo(expected2));
+ }
+
+ @Test
+ public void decode_empty() throws IOException {
+ final byte[] result = Base64.decode("");
+ assertThat(result, equalTo(new byte[0]));
+ final byte[] result2 = Base64.decode(new byte[0]);
+ assertThat(result2, equalTo(new byte[0]));
+
+ try {
+ Base64.decode(" ");
+ MatcherAssert.assertThat("Exception excepted", false);
+ } catch (final Exception e) {
+ assertThat(e, instanceOf(IllegalArgumentException.class));
+ }
+
+ try {
+ Base64.decode(" ".getBytes());
+ MatcherAssert.assertThat("Exception excepted", false);
+ } catch (final Exception e) {
+ assertThat(e, instanceOf(IllegalArgumentException.class));
+ }
+
+ try {
+ Base64.decode((String) null);
+ MatcherAssert.assertThat("Exception excepted", false);
+ } catch (final Exception e) {
+ assertThat(e, instanceOf(NullPointerException.class));
+ }
+
+ try {
+ Base64.decode((byte[]) null);
+ MatcherAssert.assertThat("Exception excepted", false);
+ } catch (final Exception e) {
+ assertThat(e, instanceOf(NullPointerException.class));
+ }
+ }
+
+ @Test
+ public void decode_lowLevelInvalidParams() {
+ try {
+ Base64.decode(null, 0, 1, Base64.NO_OPTIONS);
+ MatcherAssert.assertThat("Exception excepted", false);
+ } catch (final Exception e){
+ assertThat(e, instanceOf(NullPointerException.class));
+ }
+
+ try {
+ Base64.decode(new byte[2], 0, 1, Base64.NO_OPTIONS);
+ MatcherAssert.assertThat("Exception excepted", false);
+ } catch (final Exception e){
+ assertThat(e, instanceOf(IllegalArgumentException.class));
+ }
+
+ try {
+ Base64.decode(new byte[8], 0, 10, Base64.NO_OPTIONS);
+ MatcherAssert.assertThat("Exception excepted", false);
+ } catch (final Exception e){
+ assertThat(e, instanceOf(IllegalArgumentException.class));
+ }
+
+ try {
+ Base64.decode(new byte[8], 5, 5, Base64.NO_OPTIONS);
+ MatcherAssert.assertThat("Exception excepted", false);
+ } catch (final Exception e){
+ assertThat(e, instanceOf(IllegalArgumentException.class));
+ }
+ }
+}
diff --git a/core/pom.xml b/core/pom.xml
index acf54eac850d..e76e8b1fe8ad 100755
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../pom.xml
4.0.0
diff --git a/core/src/main/java/org/keycloak/OAuthErrorException.java b/core/src/main/java/org/keycloak/OAuthErrorException.java
index a246b383fe80..df31c6781f2e 100755
--- a/core/src/main/java/org/keycloak/OAuthErrorException.java
+++ b/core/src/main/java/org/keycloak/OAuthErrorException.java
@@ -53,6 +53,9 @@ public class OAuthErrorException extends Exception {
public static final String SLOW_DOWN = "slow_down";
public static final String EXPIRED_TOKEN = "expired_token";
+ // CIBA
+ public static final String INVALID_BINDING_MESSAGE = "invalid_binding_message";
+
// Others
public static final String INVALID_CLIENT = "invalid_client";
public static final String INVALID_GRANT = "invalid_grant";
diff --git a/core/src/main/java/org/keycloak/TokenVerifier.java b/core/src/main/java/org/keycloak/TokenVerifier.java
index ab342e0b4856..12435b314a7e 100755
--- a/core/src/main/java/org/keycloak/TokenVerifier.java
+++ b/core/src/main/java/org/keycloak/TokenVerifier.java
@@ -224,7 +224,7 @@ protected TokenVerifier(T token) {
* @return
*/
public static TokenVerifier create(String tokenString, Class clazz) {
- return new TokenVerifier(tokenString, clazz);
+ return new TokenVerifier<>(tokenString, clazz);
}
/**
@@ -237,7 +237,7 @@ public static TokenVerifier create(String tokenStrin
* @return
*/
public static TokenVerifier createWithoutSignature(T token) {
- return new TokenVerifier(token);
+ return new TokenVerifier<>(token);
}
/**
@@ -271,6 +271,7 @@ private void removeCheck(Predicate super T> check) {
checks.remove(check);
}
+ @SuppressWarnings("unchecked")
private > TokenVerifier replaceCheck(Class extends Predicate>> checkClass, boolean active, P... predicate) {
removeCheck(checkClass);
if (active) {
@@ -279,6 +280,7 @@ private > TokenVerifier replaceCheck(Class e
return this;
}
+ @SuppressWarnings("unchecked")
private > TokenVerifier replaceCheck(Predicate super T> check, boolean active, P... predicate) {
removeCheck(check);
if (active) {
@@ -292,7 +294,8 @@ private > TokenVerifier replaceCheck(Predicate
* @param checks
* @return
*/
- public TokenVerifier withChecks(Predicate super T>... checks) {
+ @SafeVarargs
+ public final TokenVerifier withChecks(Predicate super T>... checks) {
if (checks != null) {
this.checks.addAll(Arrays.asList(checks));
}
@@ -509,6 +512,7 @@ public boolean test(T t) throws VerificationException {
* @param predicates
* @return
*/
+ @SafeVarargs
public static Predicate alternative(final Predicate super T>... predicates) {
return new Predicate() {
@Override
diff --git a/core/src/main/java/org/keycloak/representations/ClaimsRepresentation.java b/core/src/main/java/org/keycloak/representations/ClaimsRepresentation.java
new file mode 100644
index 000000000000..a6f7a6a523b9
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/ClaimsRepresentation.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2021 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.keycloak.representations;
+
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Claims parameter as described in the OIDC specification https://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter
+ *
+ * @author Marek Posolda
+ */
+public class ClaimsRepresentation {
+
+ @JsonProperty("id_token")
+ private Map idTokenClaims;
+
+ @JsonProperty("userinfo")
+ private Map userinfoClaims;
+
+ public Map getIdTokenClaims() {
+ return idTokenClaims;
+ }
+
+ public void setIdTokenClaims(Map idTokenClaims) {
+ this.idTokenClaims = idTokenClaims;
+ }
+
+ public Map getUserinfoClaims() {
+ return userinfoClaims;
+ }
+
+ public void setUserinfoClaims(Map userinfoClaims) {
+ this.userinfoClaims = userinfoClaims;
+ }
+
+ // Helper methods
+
+ /**
+ *
+ * @param claimName
+ * @param ctx Whether we ask for claim to be presented in idToken or userInfo
+ * @return true if claim is presented in the claims parameter either as "null" claim (See OIDC specification for definition of null claim) or claim with some value
+ */
+ public boolean isPresent(String claimName, ClaimContext ctx) {
+ if (ctx == ClaimContext.ID_TOKEN) {
+ return idTokenClaims != null && idTokenClaims.containsKey(claimName);
+ } else if (ctx == ClaimContext.USERINFO){
+ return userinfoClaims != null && userinfoClaims.containsKey(claimName);
+ } else {
+ throw new IllegalArgumentException("Invalid claim context");
+ }
+ }
+
+ /**
+ *
+ * @param claimName
+ * @param ctx Whether we ask for claim to be presented in idToken or userInfo
+ * @return true if claim is presented in the claims parameter as "null" claim (See OIDC specification for definition of null claim)
+ */
+ public boolean isPresentAsNullClaim(String claimName, ClaimContext ctx) {
+ if (!isPresent(claimName, ctx)) return false;
+
+ if (ctx == ClaimContext.ID_TOKEN) {
+ return idTokenClaims.get(claimName) == null;
+ } else if (ctx == ClaimContext.USERINFO){
+ return userinfoClaims.get(claimName) == null;
+ } else {
+ throw new IllegalArgumentException("Invalid claim context");
+ }
+ }
+
+ /**
+ *
+ * @param claimName
+ * @param ctx Whether we ask for claim to be presented in idToken or userInfo
+ * @param claimType claimType class
+ * @return Claim value
+ */
+ public ClaimValue getClaimValue(String claimName, ClaimContext ctx, Class claimType) {
+ if (!isPresent(claimName, ctx)) return null;
+
+ if (ctx == ClaimContext.ID_TOKEN) {
+ return (ClaimValue) idTokenClaims.get(claimName);
+ } else if (ctx == ClaimContext.USERINFO){
+ return (ClaimValue) userinfoClaims.get(claimName);
+ } else {
+ throw new IllegalArgumentException("Invalid claim context");
+ }
+ }
+
+ public enum ClaimContext {
+ ID_TOKEN, USERINFO
+ }
+
+ /**
+ * @param Specifies the type of the claim
+ */
+ public static class ClaimValue {
+
+ private Boolean essential;
+
+ private CLAIM_TYPE value;
+
+ private List values;
+
+ public Boolean getEssential() {
+ return essential;
+ }
+
+ public boolean isEssential() {
+ return essential != null && essential;
+ }
+
+ public void setEssential(Boolean essential) {
+ this.essential = essential;
+ }
+
+ public CLAIM_TYPE getValue() {
+ return value;
+ }
+
+ public void setValue(CLAIM_TYPE value) {
+ this.value = value;
+ }
+
+ public List getValues() {
+ return values;
+ }
+
+ public void setValues(List values) {
+ this.values = values;
+ }
+ }
+}
diff --git a/core/src/test/java/org/keycloak/JsonParserTest.java b/core/src/test/java/org/keycloak/JsonParserTest.java
index b1640fcf6005..acdf0c8ecae9 100755
--- a/core/src/test/java/org/keycloak/JsonParserTest.java
+++ b/core/src/test/java/org/keycloak/JsonParserTest.java
@@ -19,6 +19,8 @@
import org.junit.Assert;
import org.junit.Test;
+import org.keycloak.common.util.ObjectUtil;
+import org.keycloak.representations.ClaimsRepresentation;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.representations.adapters.config.AdapterConfig;
@@ -217,5 +219,48 @@ public void testReadClientPolicy() throws Exception {
Assert.assertNull(configRep.getConfigAsMap().get("not-existing-option"));
}
+ @Test
+ public void testReadClaimsParameter() throws Exception {
+ InputStream is = getClass().getClassLoader().getResourceAsStream("sample-claims.json");
+ ClaimsRepresentation claimsRep = JsonSerialization.readValue(is, ClaimsRepresentation.class);
+
+ Assert.assertTrue(claimsRep.isPresent("auth_time", ClaimsRepresentation.ClaimContext.ID_TOKEN));
+ Assert.assertFalse(claimsRep.isPresent("auth_time", ClaimsRepresentation.ClaimContext.USERINFO));
+
+ Assert.assertFalse(claimsRep.isPresentAsNullClaim("auth_time", ClaimsRepresentation.ClaimContext.ID_TOKEN));
+ Assert.assertTrue(claimsRep.isPresentAsNullClaim("nickname", ClaimsRepresentation.ClaimContext.USERINFO));
+ Assert.assertNull(claimsRep.getClaimValue("nickname", ClaimsRepresentation.ClaimContext.USERINFO, String.class));
+
+ ClaimsRepresentation.ClaimValue email = claimsRep.getClaimValue("email", ClaimsRepresentation.ClaimContext.USERINFO, String.class);
+ assertClaimValue(email, true, null);
+
+ ClaimsRepresentation.ClaimValue emailVerified = claimsRep.getClaimValue("email_verified", ClaimsRepresentation.ClaimContext.USERINFO, Boolean.class);
+ assertClaimValue(emailVerified, true, null);
+ Assert.assertTrue(emailVerified.isEssential());
+
+ emailVerified = claimsRep.getClaimValue("email_verified", ClaimsRepresentation.ClaimContext.ID_TOKEN, Boolean.class);
+ assertClaimValue(emailVerified, false, true);
+ Assert.assertFalse(emailVerified.isEssential());
+
+ ClaimsRepresentation.ClaimValue sub = claimsRep.getClaimValue("sub", ClaimsRepresentation.ClaimContext.ID_TOKEN, String.class);
+ assertClaimValue(sub, null, "248289761001");
+ Assert.assertFalse(sub.isEssential());
+
+ ClaimsRepresentation.ClaimValue acr = claimsRep.getClaimValue("acr", ClaimsRepresentation.ClaimContext.ID_TOKEN, String.class);
+ assertClaimValue(acr, null, null, "urn:mace:incommon:iap:silver", "urn:mace:incommon:iap:gold");
+ }
+
+ private void assertClaimValue(ClaimsRepresentation.ClaimValue claimVal, Boolean expectedEssential, T expectedValue, T... expectedValues) {
+ Assert.assertTrue(ObjectUtil.isEqualOrBothNull(expectedEssential, claimVal.getEssential()));
+ Assert.assertTrue(ObjectUtil.isEqualOrBothNull(expectedValue, claimVal.getValue()));
+
+ if (expectedValues == null) {
+ Assert.assertNull(claimVal.getValues());
+ } else {
+ for (int i = 0; i
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
4.0.0
diff --git a/dependencies/server-all/pom.xml b/dependencies/server-all/pom.xml
index 088d48f1d1e7..97f87c72b874 100755
--- a/dependencies/server-all/pom.xml
+++ b/dependencies/server-all/pom.xml
@@ -21,7 +21,7 @@
keycloak-dependencies-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
4.0.0
@@ -44,6 +44,10 @@
org.keycloak
keycloak-model-map
+
+ org.keycloak
+ keycloak-model-map-jpa
+
org.keycloak
keycloak-model-infinispan
diff --git a/dependencies/server-min/pom.xml b/dependencies/server-min/pom.xml
index a867ef0e4955..76cc6801bd3d 100755
--- a/dependencies/server-min/pom.xml
+++ b/dependencies/server-min/pom.xml
@@ -21,7 +21,7 @@
keycloak-dependencies-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
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 2ea82cb77412..45a14e5d6fc2 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../../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 1602aca5ce3f..68cbd474f23d 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../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 2d93d828e9f3..4c3d17832309 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../pom.xml
diff --git a/distribution/adapters/as7-eap6-adapter/pom.xml b/distribution/adapters/as7-eap6-adapter/pom.xml
index c6af58ef632f..70d6895280dc 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../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 9731bd46d17a..9e6936139892 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
diff --git a/distribution/adapters/jetty92-adapter-zip/pom.xml b/distribution/adapters/jetty92-adapter-zip/pom.xml
index 2d84f239f27b..3c99720ff219 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
diff --git a/distribution/adapters/jetty93-adapter-zip/pom.xml b/distribution/adapters/jetty93-adapter-zip/pom.xml
index b14a8529bd9a..78fb7279e409 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
diff --git a/distribution/adapters/jetty94-adapter-zip/pom.xml b/distribution/adapters/jetty94-adapter-zip/pom.xml
index cc3803fc8090..0c6c5821e740 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
diff --git a/distribution/adapters/js-adapter-npm-zip/assembly.xml b/distribution/adapters/js-adapter-npm-zip/assembly.xml
index ac60b3767d0d..ee91332a6ca2 100755
--- a/distribution/adapters/js-adapter-npm-zip/assembly.xml
+++ b/distribution/adapters/js-adapter-npm-zip/assembly.xml
@@ -37,6 +37,7 @@
dist/
**/*.js
+ **/*.mjs
**/*.map
**/*.d.ts
diff --git a/distribution/adapters/js-adapter-npm-zip/pom.xml b/distribution/adapters/js-adapter-npm-zip/pom.xml
index b6c75b770b21..554f230007c6 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
@@ -53,7 +53,7 @@
org.keycloak
keycloak-js-adapter
${project.build.directory}/unpacked/js-adapter
- *.js,*.map,*.d.ts
+ *.js,*.mjs,*.map,*.d.ts
**/welcome-content/*
diff --git a/distribution/adapters/js-adapter-npm-zip/src/main/resources/package.json b/distribution/adapters/js-adapter-npm-zip/src/main/resources/package.json
index fc079f76f04d..19ddb0b5991d 100644
--- a/distribution/adapters/js-adapter-npm-zip/src/main/resources/package.json
+++ b/distribution/adapters/js-adapter-npm-zip/src/main/resources/package.json
@@ -3,6 +3,7 @@
"version": "${project.version}",
"description": "Keycloak Adapter",
"main": "dist/keycloak.js",
+ "module": "dist/keycloak.mjs",
"typings": "dist/keycloak.d.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
@@ -22,7 +23,7 @@
"authentication"
],
"dependencies": {
- "base64-js": "1.3.1",
- "js-sha256": "0.9.0"
+ "base64-js": "^1.5.1",
+ "js-sha256": "^0.9.0"
}
}
diff --git a/distribution/adapters/js-adapter-zip/assembly.xml b/distribution/adapters/js-adapter-zip/assembly.xml
index 01d555b8dfe4..5330d29d85e7 100755
--- a/distribution/adapters/js-adapter-zip/assembly.xml
+++ b/distribution/adapters/js-adapter-zip/assembly.xml
@@ -30,6 +30,7 @@
**/*.js
+ **/*.mjs
**/*.map
**/*.d.ts
**/*.html
diff --git a/distribution/adapters/js-adapter-zip/pom.xml b/distribution/adapters/js-adapter-zip/pom.xml
index e3513a2dc0f1..ad59040c5174 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
@@ -53,7 +53,7 @@
org.keycloak
keycloak-js-adapter
${project.build.directory}/unpacked/js-adapter
- *.js,*.map,*.d.ts
+ *.js,*.mjs,*.map,*.d.ts
**/welcome-content/*
diff --git a/distribution/adapters/osgi/features/pom.xml b/distribution/adapters/osgi/features/pom.xml
index 84c178a290ec..067ef29e8669 100755
--- a/distribution/adapters/osgi/features/pom.xml
+++ b/distribution/adapters/osgi/features/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../../pom.xml
Keycloak OSGI Features
diff --git a/distribution/adapters/osgi/jaas/pom.xml b/distribution/adapters/osgi/jaas/pom.xml
index 4525e73ba573..1bf8deaa6f79 100755
--- a/distribution/adapters/osgi/jaas/pom.xml
+++ b/distribution/adapters/osgi/jaas/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../../pom.xml
Keycloak OSGI JAAS Realm Configuration
diff --git a/distribution/adapters/osgi/pom.xml b/distribution/adapters/osgi/pom.xml
index b997f7e2fd3d..0cd8e40841c7 100755
--- a/distribution/adapters/osgi/pom.xml
+++ b/distribution/adapters/osgi/pom.xml
@@ -21,7 +21,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
Keycloak OSGI Integration
diff --git a/distribution/adapters/pom.xml b/distribution/adapters/pom.xml
index c44d9f1a0f4e..cad19dcbf0d9 100755
--- a/distribution/adapters/pom.xml
+++ b/distribution/adapters/pom.xml
@@ -20,7 +20,7 @@
keycloak-distribution-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
Adapters Distribution Parent
diff --git a/distribution/adapters/tomcat-adapter-zip/pom.xml b/distribution/adapters/tomcat-adapter-zip/pom.xml
index 0b89f428a469..72f6644456f5 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
diff --git a/distribution/adapters/tomcat7-adapter-zip/pom.xml b/distribution/adapters/tomcat7-adapter-zip/pom.xml
index f11ee33dc73f..0bce98985bd3 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
diff --git a/distribution/adapters/wildfly-adapter/pom.xml b/distribution/adapters/wildfly-adapter/pom.xml
index 2995c7830254..a275393cba51 100644
--- a/distribution/adapters/wildfly-adapter/pom.xml
+++ b/distribution/adapters/wildfly-adapter/pom.xml
@@ -21,9 +21,16 @@
keycloak-adapters-distribution-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
+
+ 23.0.2.Final
+ 1.2.13.Final
+ 15.0.1.Final
+ 5.1.3.Final
+
+
keycloak-wildfly-adapter-dist
pom
Keycloak Adapter Overlay Distribution
diff --git a/distribution/api-docs-dist/pom.xml b/distribution/api-docs-dist/pom.xml
index 38276dc1a592..651519fce9ae 100755
--- a/distribution/api-docs-dist/pom.xml
+++ b/distribution/api-docs-dist/pom.xml
@@ -21,7 +21,7 @@
keycloak-distribution-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
keycloak-api-docs-dist
diff --git a/distribution/downloads/pom.xml b/distribution/downloads/pom.xml
index e6f82a6547a6..38418c4605f3 100755
--- a/distribution/downloads/pom.xml
+++ b/distribution/downloads/pom.xml
@@ -21,7 +21,7 @@
keycloak-distribution-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
keycloak-dist-downloads
diff --git a/distribution/downloads/src/main/resources/files b/distribution/downloads/src/main/resources/files
index 5a78124098b6..c92f32036a81 100644
--- a/distribution/downloads/src/main/resources/files
+++ b/distribution/downloads/src/main/resources/files
@@ -1,11 +1,8 @@
./
keycloak-server-dist:keycloak
keycloak-server-x-dist:keycloak.x-preview
- keycloak-server-overlay:keycloak-overlay
keycloak-api-docs-dist:keycloak-api-docs
- keycloak-as7-adapter-dist:keycloak-oidc-as7-adapter
- keycloak-eap6-adapter-dist:keycloak-oidc-eap6-adapter
keycloak-jetty92-adapter-dist:keycloak-oidc-jetty92-adapter
keycloak-jetty93-adapter-dist:keycloak-oidc-jetty93-adapter
keycloak-jetty94-adapter-dist:keycloak-oidc-jetty94-adapter
@@ -15,8 +12,6 @@
keycloak-wildfly-adapter-dist:keycloak-oidc-wildfly-adapter
keycloak-fuse-adapter-dist:keycloak-oidc-fuse-adapter
- keycloak-saml-as7-adapter-dist:keycloak-saml-as7-adapter
- keycloak-saml-eap6-adapter-dist:keycloak-saml-eap6-adapter
keycloak-saml-jetty92-adapter-dist:keycloak-saml-jetty92-adapter
keycloak-saml-jetty93-adapter-dist:keycloak-saml-jetty93-adapter
keycloak-saml-jetty94-adapter-dist:keycloak-saml-jetty94-adapter
diff --git a/distribution/feature-packs/adapter-feature-pack/pom.xml b/distribution/feature-packs/adapter-feature-pack/pom.xml
index f29e264b21eb..699239b004be 100755
--- a/distribution/feature-packs/adapter-feature-pack/pom.xml
+++ b/distribution/feature-packs/adapter-feature-pack/pom.xml
@@ -19,9 +19,16 @@
org.keycloak
feature-packs-parent
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
+
+ 23.0.2.Final
+ 1.2.13.Final
+ 15.0.1.Final
+ 5.1.3.Final
+
+
4.0.0
org.keycloak
@@ -218,6 +225,7 @@
org.wildfly
wildfly-feature-pack
+ ${wildfly.version}
zip
diff --git a/distribution/feature-packs/pom.xml b/distribution/feature-packs/pom.xml
index 8f67ecff9c53..490c97e38ec9 100644
--- a/distribution/feature-packs/pom.xml
+++ b/distribution/feature-packs/pom.xml
@@ -20,7 +20,7 @@
keycloak-distribution-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
Feature Pack Builds
diff --git a/distribution/feature-packs/server-feature-pack-dependencies/pom.xml b/distribution/feature-packs/server-feature-pack-dependencies/pom.xml
index a8041d056431..4dbc956b23b9 100644
--- a/distribution/feature-packs/server-feature-pack-dependencies/pom.xml
+++ b/distribution/feature-packs/server-feature-pack-dependencies/pom.xml
@@ -19,7 +19,7 @@
org.keycloak
feature-packs-parent
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../pom.xml
4.0.0
diff --git a/distribution/feature-packs/server-feature-pack/feature-pack-build.xml b/distribution/feature-packs/server-feature-pack/feature-pack-build.xml
index 2cb52d6bd4b0..9f2fc921148f 100644
--- a/distribution/feature-packs/server-feature-pack/feature-pack-build.xml
+++ b/distribution/feature-packs/server-feature-pack/feature-pack-build.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
-
+
diff --git a/distribution/feature-packs/server-feature-pack/pom.xml b/distribution/feature-packs/server-feature-pack/pom.xml
index afeddf128e5e..eadfe99ae3c2 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../pom.xml
4.0.0
@@ -79,25 +79,20 @@
-
- org.wildfly.build
- wildfly-feature-pack-build-maven-plugin
- ${wildfly.build-tools.version}
+ org.apache.maven.plugins
+ maven-resources-plugin
+
- feature-pack-build
+ copy-resources
+ none
- build
+ copy-resources
- compile
-
- feature-pack-build.xml
-
-
org.apache.maven.plugins
maven-assembly-plugin
@@ -113,77 +108,17 @@
assembly.xml
true
+ ${project.build.finalName}
false
- target/
- target/assembly/work
+ ${project.build.directory}
+ ${project.build.directory}/assembly/work
+ ${assembly.tarLongFileMode}
-
-
- org.keycloak
- keycloak-distribution-licenses-maven-plugin
-
-
-
- community
-
-
- !product
-
-
-
-
- org.wildfly:wildfly-feature-pack
-
-
-
-
- org.wildfly
- wildfly-feature-pack
- zip
-
-
- *
- *
-
-
-
-
-
-
-
- product
-
-
- product
-
-
-
-
- org.jboss.eap:wildfly-feature-pack
-
-
-
-
- org.jboss.eap
- wildfly-feature-pack
- ${eap.version}
- zip
-
-
- *
- *
-
-
-
-
-
-
-
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/licenses/keycloak/Zocial,MIT License.txt b/distribution/feature-packs/server-feature-pack/src/main/resources/licenses/keycloak/Zocial,MIT License.txt
deleted file mode 100644
index 2274f067bb0e..000000000000
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/licenses/keycloak/Zocial,MIT License.txt
+++ /dev/null
@@ -1,46 +0,0 @@
-# Zocial CSS social buttons
-
-I basically rewrote this entire set so they are full vector buttons, meaning:
-
-- @font-face icons
-- custom font file for all social icons
-- icon font use private unicode spaces for accessibility
-- em sizing based on button font-size
-- support for about 83 different services
-- buttons and icons supported
-- no raster images (sweet)
-- works splendidly on any browser supporting @font-face
-- CSS3 degrades gracefully in IE8 and below etc.
-- also includes generic icon-less primary and secondary buttons
-
-## How to use these buttons
-
- Button label here
-
-or
-
-
-
-- Can be any element e.g. `a`, `div`, `button` etc.
-- Add class of `.zocial`
-- Add class for name of service e.g. `.dropbox`, `.twitter`, `.github`
-- Done :-)
-
-Check out [zocial.smcllns.com](http://zocial.smcllns.com) for demo and code examples.
-
-There's also a LESS version from @gustavohenke [here](https://github.com/gustavohenke/zocial-less)
-
-Problems, questions or requests to [@smcllns](http://twitter.com/smcllns)
-
-## How to contribute
-
-Install [Font Custom](http://fontcustom.com/#installation)
-
-Run `fontcustom compile` to build the font.
-
-New fonts are defined by adding them in the `svg/` folder. For color settings
-see the `templates/zocial.css` file.
-
-## License
-
-Under [MIT License](http://opensource.org/licenses/mit-license.php)
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/licenses/keycloak/licenses.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/licenses/keycloak/licenses.xml
index cd3125f9009f..6717a701c571 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/licenses/keycloak/licenses.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/licenses/keycloak/licenses.xml
@@ -484,18 +484,6 @@
-
- Zocial
-
- themes/keycloak/common/resources/lib/zocial
-
-
-
- MIT License
- https://raw.githubusercontent.com/smcllns/css-social-buttons/547237515694d05eaa38aeae3fb4d2eb4dee1ac9/README.md
-
-
-
rfc4648.js
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/licenses/rh-sso/Zocial,MIT License.txt b/distribution/feature-packs/server-feature-pack/src/main/resources/licenses/rh-sso/Zocial,MIT License.txt
deleted file mode 100644
index 2274f067bb0e..000000000000
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/licenses/rh-sso/Zocial,MIT License.txt
+++ /dev/null
@@ -1,46 +0,0 @@
-# Zocial CSS social buttons
-
-I basically rewrote this entire set so they are full vector buttons, meaning:
-
-- @font-face icons
-- custom font file for all social icons
-- icon font use private unicode spaces for accessibility
-- em sizing based on button font-size
-- support for about 83 different services
-- buttons and icons supported
-- no raster images (sweet)
-- works splendidly on any browser supporting @font-face
-- CSS3 degrades gracefully in IE8 and below etc.
-- also includes generic icon-less primary and secondary buttons
-
-## How to use these buttons
-
- Button label here
-
-or
-
-
-
-- Can be any element e.g. `a`, `div`, `button` etc.
-- Add class of `.zocial`
-- Add class for name of service e.g. `.dropbox`, `.twitter`, `.github`
-- Done :-)
-
-Check out [zocial.smcllns.com](http://zocial.smcllns.com) for demo and code examples.
-
-There's also a LESS version from @gustavohenke [here](https://github.com/gustavohenke/zocial-less)
-
-Problems, questions or requests to [@smcllns](http://twitter.com/smcllns)
-
-## How to contribute
-
-Install [Font Custom](http://fontcustom.com/#installation)
-
-Run `fontcustom compile` to build the font.
-
-New fonts are defined by adding them in the `svg/` folder. For color settings
-see the `templates/zocial.css` file.
-
-## License
-
-Under [MIT License](http://opensource.org/licenses/mit-license.php)
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/licenses/rh-sso/licenses.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/licenses/rh-sso/licenses.xml
index 8480daa89acb..ace445c79efa 100644
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/licenses/rh-sso/licenses.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/licenses/rh-sso/licenses.xml
@@ -484,18 +484,6 @@
-
- Zocial
-
- themes/keycloak/common/resources/lib/zocial
-
-
-
- MIT License
- https://raw.githubusercontent.com/smcllns/css-social-buttons/547237515694d05eaa38aeae3fb4d2eb4dee1ac9/README.md
-
-
-
rfc4648.js
diff --git a/distribution/galleon-feature-packs/adapter-galleon-pack/pom.xml b/distribution/galleon-feature-packs/adapter-galleon-pack/pom.xml
index 0ff1fac1672f..fe50d69c4c3d 100644
--- a/distribution/galleon-feature-packs/adapter-galleon-pack/pom.xml
+++ b/distribution/galleon-feature-packs/adapter-galleon-pack/pom.xml
@@ -19,7 +19,7 @@
org.keycloak
galleon-feature-packs-parent
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
4.0.0
@@ -31,6 +31,11 @@
pom
+ 23.0.2.Final
+ 1.2.13.Final
+ 15.0.1.Final
+ 5.1.3.Final
+
${basedir}/../../feature-packs/adapter-feature-pack/src/main/resources
5.1.3.Final
${feature-pack.resources.directory}/licenses/${product.slot}/licenses.xml
diff --git a/distribution/galleon-feature-packs/pom.xml b/distribution/galleon-feature-packs/pom.xml
index 67e1a5c842fa..dced885aa0f1 100644
--- a/distribution/galleon-feature-packs/pom.xml
+++ b/distribution/galleon-feature-packs/pom.xml
@@ -20,7 +20,7 @@
keycloak-distribution-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
Galleon Feature Pack Builds
diff --git a/distribution/galleon-feature-packs/server-galleon-pack/pom.xml b/distribution/galleon-feature-packs/server-galleon-pack/pom.xml
index dd79509dc155..d3d49fd67c8e 100644
--- a/distribution/galleon-feature-packs/server-galleon-pack/pom.xml
+++ b/distribution/galleon-feature-packs/server-galleon-pack/pom.xml
@@ -27,7 +27,7 @@
org.keycloak
galleon-feature-packs-parent
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
org.keycloak
@@ -64,13 +64,6 @@
provided
-
- org.wildfly.core
- wildfly-core-feature-pack-galleon-pruned
- pom
- provided
-
-
org.wildfly.core
wildfly-core-galleon-pack
@@ -391,10 +384,6 @@
-
- org.wildfly:wildfly-galleon-pack
-
-
org.wildfly
@@ -418,10 +407,6 @@
-
- ${ee.maven.groupId}:wildfly-ee-galleon-pack
-
-
${ee.maven.groupId}
@@ -467,7 +452,6 @@
transitives. Those poms ban transitives at their level -->
org.wildfly.core:wildfly-core-feature-pack-common
org.wildfly.core:wildfly-core-feature-pack-ee-8-api
- org.wildfly.core:wildfly-core-feature-pack-galleon-pruned
org.wildfly.core:wildfly-core-feature-pack-galleon-common
${ee.maven.groupId}:wildfly-servlet-feature-pack-common
${ee.maven.groupId}:wildfly-servlet-feature-pack-ee-8-api
diff --git a/distribution/galleon-feature-packs/server-galleon-pack/src/license/keycloak-server-galleon-pack-licenses.xml b/distribution/galleon-feature-packs/server-galleon-pack/src/license/keycloak-server-galleon-pack-licenses.xml
index 607f418d1767..f06357f4ae0d 100644
--- a/distribution/galleon-feature-packs/server-galleon-pack/src/license/keycloak-server-galleon-pack-licenses.xml
+++ b/distribution/galleon-feature-packs/server-galleon-pack/src/license/keycloak-server-galleon-pack-licenses.xml
@@ -499,18 +499,6 @@
-
- Zocial
-
- themes/keycloak/common/resources/lib/zocial
-
-
-
- MIT License
- https://raw.githubusercontent.com/smcllns/css-social-buttons/547237515694d05eaa38aeae3fb4d2eb4dee1ac9/README.md
-
-
-
rfc4648.js
diff --git a/distribution/galleon-feature-packs/server-galleon-pack/src/license/rh-sso-server-galleon-pack-licenses.xml b/distribution/galleon-feature-packs/server-galleon-pack/src/license/rh-sso-server-galleon-pack-licenses.xml
index b0c681fc0db3..e871c024d714 100644
--- a/distribution/galleon-feature-packs/server-galleon-pack/src/license/rh-sso-server-galleon-pack-licenses.xml
+++ b/distribution/galleon-feature-packs/server-galleon-pack/src/license/rh-sso-server-galleon-pack-licenses.xml
@@ -484,18 +484,6 @@
-
- Zocial
-
- themes/keycloak/common/resources/lib/zocial
-
-
-
- MIT License
- https://raw.githubusercontent.com/smcllns/css-social-buttons/547237515694d05eaa38aeae3fb4d2eb4dee1ac9/README.md
-
-
-
rfc4648.js
diff --git a/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-dist-ejb.xml b/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-dist-ejb.xml
index 2cb9495d651f..71dd839f511d 100644
--- a/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-dist-ejb.xml
+++ b/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-dist-ejb.xml
@@ -6,6 +6,7 @@
+
diff --git a/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-dist-hibernate.xml b/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-dist-hibernate.xml
index 7393106cdae9..2b8073b1b861 100644
--- a/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-dist-hibernate.xml
+++ b/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-dist-hibernate.xml
@@ -5,6 +5,7 @@
+
diff --git a/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-dist-keycloak.xml b/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-dist-keycloak.xml
index f799bba99464..d0433a46427f 100644
--- a/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-dist-keycloak.xml
+++ b/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-dist-keycloak.xml
@@ -4,6 +4,7 @@
+
diff --git a/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-dist-server.xml b/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-dist-server.xml
index ae020a87bae8..396a1e7ea9e3 100644
--- a/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-dist-server.xml
+++ b/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-dist-server.xml
@@ -5,6 +5,7 @@
+
diff --git a/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-dist-web.xml b/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-dist-web.xml
index 57a1d53888df..1cb2473372a4 100644
--- a/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-dist-web.xml
+++ b/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-dist-web.xml
@@ -5,6 +5,7 @@
+
diff --git a/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-local-ejb.xml b/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-local-ejb.xml
index 542512c7628f..929643ca1130 100644
--- a/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-local-ejb.xml
+++ b/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-local-ejb.xml
@@ -6,6 +6,7 @@
+
diff --git a/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-local-hibernate.xml b/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-local-hibernate.xml
index a0b44bb29970..0df787064e48 100644
--- a/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-local-hibernate.xml
+++ b/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-local-hibernate.xml
@@ -4,6 +4,7 @@
+
diff --git a/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-local-keycloak.xml b/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-local-keycloak.xml
index b0d91c53f813..be83abcd1efc 100644
--- a/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-local-keycloak.xml
+++ b/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-local-keycloak.xml
@@ -4,6 +4,7 @@
+
diff --git a/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-local-server.xml b/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-local-server.xml
index a72d3551ee83..3ea87b8974f5 100644
--- a/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-local-server.xml
+++ b/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-local-server.xml
@@ -6,6 +6,7 @@
+
diff --git a/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-local-web.xml b/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-local-web.xml
index 4bd54f4e4b13..7cd129fc3668 100644
--- a/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-local-web.xml
+++ b/distribution/galleon-feature-packs/server-galleon-pack/src/main/resources/feature_groups/infinispan-local-web.xml
@@ -5,6 +5,7 @@
+
@@ -34,4 +35,4 @@
-
\ No newline at end of file
+
diff --git a/distribution/licenses-common/pom.xml b/distribution/licenses-common/pom.xml
index 35f5992e5591..5b128baa2d73 100644
--- a/distribution/licenses-common/pom.xml
+++ b/distribution/licenses-common/pom.xml
@@ -20,7 +20,7 @@
keycloak-distribution-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
keycloak-distribution-licenses-common
diff --git a/distribution/maven-plugins/licenses-processor/pom.xml b/distribution/maven-plugins/licenses-processor/pom.xml
index 8e29229558ca..727b27dc74b5 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
keycloak-distribution-licenses-maven-plugin
diff --git a/distribution/maven-plugins/pom.xml b/distribution/maven-plugins/pom.xml
index b11bda158697..97990ee2afec 100644
--- a/distribution/maven-plugins/pom.xml
+++ b/distribution/maven-plugins/pom.xml
@@ -20,7 +20,7 @@
keycloak-distribution-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
keycloak-distribution-maven-plugins-parent
diff --git a/distribution/pom.xml b/distribution/pom.xml
index 6057937cebcc..4a1c707d9a46 100755
--- a/distribution/pom.xml
+++ b/distribution/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../pom.xml
@@ -43,7 +43,7 @@
licenses-common
maven-plugins
server-dist
- server-overlay
+
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 f01ad1bb7f4e..7fdeff4fd1a3 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../../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 6c68e6c32493..86de3b615748 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../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 3a3d52d36731..0636f6367ecc 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../pom.xml
diff --git a/distribution/saml-adapters/as7-eap6-adapter/pom.xml b/distribution/saml-adapters/as7-eap6-adapter/pom.xml
index 7664d1586efb..6140d280e298 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../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 f0b0ecf97d44..0caecb151c96 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
diff --git a/distribution/saml-adapters/jetty93-adapter-zip/pom.xml b/distribution/saml-adapters/jetty93-adapter-zip/pom.xml
index 1c2361ea5938..807888a5bd27 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
diff --git a/distribution/saml-adapters/jetty94-adapter-zip/pom.xml b/distribution/saml-adapters/jetty94-adapter-zip/pom.xml
index 53ff5ab0f4eb..67556459475d 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
diff --git a/distribution/saml-adapters/pom.xml b/distribution/saml-adapters/pom.xml
index 72701036431d..19fbf9bc2c9e 100755
--- a/distribution/saml-adapters/pom.xml
+++ b/distribution/saml-adapters/pom.xml
@@ -20,7 +20,7 @@
keycloak-distribution-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
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 758acec957d7..132ceee5fb8c 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
diff --git a/distribution/saml-adapters/tomcat7-adapter-zip/pom.xml b/distribution/saml-adapters/tomcat7-adapter-zip/pom.xml
index 30b56d5f0128..8c58bd3cf175 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../pom.xml
diff --git a/distribution/saml-adapters/wildfly-adapter/pom.xml b/distribution/saml-adapters/wildfly-adapter/pom.xml
index f79eae0e70f3..94f39be56746 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../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 29ab59ed5608..fadbe359be80 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../../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 22ad2bc3240e..8ae02b7a5bd9 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../../../pom.xml
diff --git a/distribution/server-dist/pom.xml b/distribution/server-dist/pom.xml
index c1076d950892..f68f949ff9c9 100644
--- a/distribution/server-dist/pom.xml
+++ b/distribution/server-dist/pom.xml
@@ -30,7 +30,7 @@
keycloak-distribution-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
keycloak-server-dist
diff --git a/distribution/server-legacy-dist/pom.xml b/distribution/server-legacy-dist/pom.xml
index 8926e972cb28..4b57559f00a0 100755
--- a/distribution/server-legacy-dist/pom.xml
+++ b/distribution/server-legacy-dist/pom.xml
@@ -21,7 +21,7 @@
keycloak-distribution-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
keycloak-server-legacy-dist
diff --git a/distribution/server-x-dist/assembly.xml b/distribution/server-x-dist/assembly.xml
index 0658b162e00d..d0bead384c21 100755
--- a/distribution/server-x-dist/assembly.xml
+++ b/distribution/server-x-dist/assembly.xml
@@ -94,15 +94,7 @@
true
- target/keycloak-quarkus-server/cluster-local.xml
- conf
-
-
- target/keycloak-quarkus-server/cluster-default.xml
- conf
-
-
- ../../quarkus/runtime/src/main/resources/META-INF/keycloak.properties
+ target/keycloak-quarkus-server/cache-ispn.xml
conf
diff --git a/distribution/server-x-dist/pom.xml b/distribution/server-x-dist/pom.xml
index 7f9b7353e4e2..fa7ada3f610c 100755
--- a/distribution/server-x-dist/pom.xml
+++ b/distribution/server-x-dist/pom.xml
@@ -21,7 +21,7 @@
keycloak-distribution-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
keycloak-server-x-dist
diff --git a/distribution/server-x-dist/src/main/README.md b/distribution/server-x-dist/src/main/README.md
index 269604456ad5..e68a1c0eff1f 100644
--- a/distribution/server-x-dist/src/main/README.md
+++ b/distribution/server-x-dist/src/main/README.md
@@ -1,14 +1,26 @@
Keycloak.X
==========
-To run on Linux/Unix:
+To get help configuring Keycloak via the CLI, run:
+
+on Linux/Unix:
$ bin/kc.sh
-To run on Windows:
+on Windows:
$ bin\kc.bat
+To try Keycloak out in development mode, run:
+
+on Linux/Unix:
+
+ $ bin/kc.sh start-dev
+
+on Windows:
+
+ $ bin\kc.bat start-dev
+
After the server boots, open http://localhost:8080 in your web browser. The welcome page will indicate that the server is running.
To get started, check the [Server Administration Guide](https://www.keycloak.org/docs/latest/server_admin).
\ No newline at end of file
diff --git a/distribution/server-x-dist/src/main/content/bin/kc.bat b/distribution/server-x-dist/src/main/content/bin/kc.bat
index 1a3937ae6802..7bdc86328979 100644
--- a/distribution/server-x-dist/src/main/content/bin/kc.bat
+++ b/distribution/server-x-dist/src/main/content/bin/kc.bat
@@ -31,30 +31,36 @@ if "%KEY%" == "" (
goto MAIN
)
if "%KEY%" == "--debug" (
- set "DEBUG_MODE=true"
- set "DEBUG_PORT_VAR=%~2"
- if "%DEBUG_PORT_VAR%" == "" (
- set DEBUG_PORT_VAR=8787
- )
- shift
- shift
- goto READ-ARGS
+ set "DEBUG_MODE=true"
+ set "DEBUG_PORT_VAR=%~2"
+ if "%DEBUG_PORT_VAR%" == "" (
+ set DEBUG_PORT_VAR=8787
+ )
+ shift
+ shift
+ goto READ-ARGS
+)
+if "%KEY%" == "start-dev" (
+ set "CONFIG_ARGS=%CONFIG_ARGS% --profile=dev %KEY% --auto-build"
+ shift
+ shift
+ goto READ-ARGS
)
-if not "%KEY:~0,2%"=="--" if "%KEY:~0,1%"=="-" (
- set "SERVER_OPTS=%SERVER_OPTS% %KEY%=%~2"
- shift
+if not "%KEY:~0,2%"=="--" if "%KEY:~0,2%"=="-D" (
+ set "SERVER_OPTS=%SERVER_OPTS% %KEY%=%~2"
+ shift
)
if not "%KEY:~0,2%"=="--" if not "%KEY:~0,1%"=="-" (
- set "CONFIG_ARGS=%CONFIG_ARGS% %KEY%"
+ set "CONFIG_ARGS=%CONFIG_ARGS% %KEY%"
)
-if "%KEY:~0,2%"=="--" (
- if "%~2"=="" (
- set "CONFIG_ARGS=%CONFIG_ARGS% %KEY%"
- ) else (
- set "CONFIG_ARGS=%CONFIG_ARGS% %KEY%=%~2%"
- )
-
- shift
+if "%KEY:~0,2%"=="--" if not "%KEY:~0,2%"=="-D" if "%KEY:~0,1%"=="-" (
+ if "%~2"=="" (
+ set "CONFIG_ARGS=%CONFIG_ARGS% %KEY%"
+ ) else (
+ set "CONFIG_ARGS=%CONFIG_ARGS% %KEY% %~2%"
+ )
+
+ shift
)
shift
goto READ-ARGS
@@ -66,6 +72,11 @@ if not "x%JAVA_OPTS%" == "x" (
set "JAVA_OPTS=-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true"
)
+if not "x%JAVA_OPTS_APPEND%" == "x" (
+ echo "Appending additional Java properties to JAVA_OPTS: %JAVA_OPTS_APPEND%"
+ set "JAVA_OPTS=%JAVA_OPTS% %JAVA_OPTS_APPEND%"
+)
+
if NOT "x%DEBUG%" == "x" (
set "DEBUG_MODE=%DEBUG%
)
@@ -103,8 +114,18 @@ if "x%JAVA_HOME%" == "x" (
)
)
-set "CLASSPATH_OPTS=%DIRNAME%..\lib\quarkus-run.jar;%DIRNAME%..\lib\lib\main\*.*"
+set "CLASSPATH_OPTS=%DIRNAME%..\lib\quarkus-run.jar"
+
+set "JAVA_RUN_OPTS=%JAVA_OPTS% -Dkc.home.dir="%DIRNAME%.." -Djboss.server.config.dir="%DIRNAME%..\conf" -Dkeycloak.theme.dir="%DIRNAME%..\themes" %SERVER_OPTS% -cp "%CLASSPATH_OPTS%" io.quarkus.bootstrap.runner.QuarkusEntryPoint %CONFIG_ARGS%"
+
+SetLocal EnableDelayedExpansion
+
+set "AUTO_BUILD_OPTION=auto-build"
+
+if not "!CONFIG_ARGS:%AUTO_BUILD_OPTION%=!"=="!CONFIG_ARGS!" (
+ %JAVA% -Dkc.config.rebuild-and-exit=true %JAVA_RUN_OPTS%
+)
-"%JAVA%" %JAVA_OPTS% -Dkc.home.dir="%DIRNAME%.." -Djboss.server.config.dir="%DIRNAME%..\conf" -Dkeycloak.theme.dir="%DIRNAME%..\themes" %SERVER_OPTS% -cp "%CLASSPATH_OPTS%" io.quarkus.bootstrap.runner.QuarkusEntryPoint %CONFIG_ARGS%
+"%JAVA%" %JAVA_RUN_OPTS%
:END
\ No newline at end of file
diff --git a/distribution/server-x-dist/src/main/content/bin/kc.sh b/distribution/server-x-dist/src/main/content/bin/kc.sh
index d8960c8bab09..ef8e11b63d2d 100644
--- a/distribution/server-x-dist/src/main/content/bin/kc.sh
+++ b/distribution/server-x-dist/src/main/content/bin/kc.sh
@@ -63,11 +63,16 @@ done
# Specify options to pass to the Java VM.
#
if [ "x$JAVA_OPTS" = "x" ]; then
- JAVA_OPTS="-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Dquarkus-log-max-startup-records=10000"
+ JAVA_OPTS="-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true"
else
echo "JAVA_OPTS already set in environment; overriding default settings with values: $JAVA_OPTS"
fi
+if [ "x$JAVA_OPTS_APPEND" != "x" ]; then
+ echo "Appending additional Java properties to JAVA_OPTS: $JAVA_OPTS_APPEND"
+ JAVA_OPTS="$JAVA_OPTS $JAVA_OPTS_APPEND"
+fi
+
# Set debug settings if not already set
if [ "$DEBUG_MODE" = "true" ]; then
DEBUG_OPT=`echo $JAVA_OPTS | $GREP "\-agentlib:jdwp"`
diff --git a/distribution/server-x-dist/src/main/content/conf/keycloak.conf b/distribution/server-x-dist/src/main/content/conf/keycloak.conf
new file mode 100644
index 000000000000..47a579b6a1a5
--- /dev/null
+++ b/distribution/server-x-dist/src/main/content/conf/keycloak.conf
@@ -0,0 +1,34 @@
+# Basic settings for running in production. Change accordingly before deploying the server.
+
+# Database
+
+# The database vendor.
+#db=postgres
+
+# The username of the database user.
+#db-username=keycloak
+
+# The password of the database user.
+#db-password=password
+
+# The full database JDBC URL. If not provided, a default URL is set based on the selected database vendor.
+#db-url=jdbc:postgresql://localhost/keycloak
+
+# Observability
+
+# If the server should expose metrics and healthcheck endpoints.
+#metrics-enabled=true
+
+# HTTP
+
+# The file path to a server certificate or certificate chain in PEM format.
+#https-certificate-file=${kc.home.dir}conf/server.crt.pem
+
+# The file path to a private key in PEM format.
+#https-certificate-key-file=${kc.home.dir}conf/server.key.pem
+
+# The proxy address forwarding mode if the server is behind a reverse proxy.
+#proxy=reencrypt
+
+# Hostname for the Keycloak server.
+#hostname=myhostname
diff --git a/distribution/server-x-dist/src/main/content/providers/README.md b/distribution/server-x-dist/src/main/content/providers/README.md
index 7184eb145599..d47220d0a55d 100644
--- a/distribution/server-x-dist/src/main/content/providers/README.md
+++ b/distribution/server-x-dist/src/main/content/providers/README.md
@@ -1,10 +1,10 @@
Installing Custom Providers
===========================
-You should add to this directory your custom provider JAR files.
+Add your custom provider JAR files in this directory.
-Once you have your providers in this directory you should run the following command to complete the installation:
+Once you have your providers in this directory, run the following command to complete the installation:
```
-${kc.home.dir}/bin/kc.sh config
+${kc.home.dir}/bin/kc.sh build
```
\ No newline at end of file
diff --git a/distribution/server-x-dist/src/main/content/themes/README.md b/distribution/server-x-dist/src/main/content/themes/README.md
index e841d56849b9..ce6c1ba9b40f 100644
--- a/distribution/server-x-dist/src/main/content/themes/README.md
+++ b/distribution/server-x-dist/src/main/content/themes/README.md
@@ -4,10 +4,10 @@ Creating Themes
Themes are used to configure the look and feel of login pages and the account management console.
Custom themes packaged in a JAR file should be deployed to the `${kc.home.dir}/providers` directory and you should run
-the `config` command to install them prior to running the server.
+the `build` command to install them prior to running the server.
-You should also be able to create your custom themes in this directory, directly. Themes within this directory do not require
-the `config` command to install them.
+You are also able to create your custom themes in this directory, directly. Themes within this directory do not require
+the `build` command to install them.
When running the server in development mode, themes are not cached so that you can easily work on them without any need to restart
the server when making changes.
diff --git a/docs/guides/assembly.xml b/docs/guides/assembly.xml
new file mode 100644
index 000000000000..c9792d64b16d
--- /dev/null
+++ b/docs/guides/assembly.xml
@@ -0,0 +1,16 @@
+
+ asciidoc
+
+ zip
+
+
+
+ ${project.build.directory}
+ /
+
+ generated-guides/**
+
+
+
+
diff --git a/docs/guides/pom.xml b/docs/guides/pom.xml
new file mode 100644
index 000000000000..326043217471
--- /dev/null
+++ b/docs/guides/pom.xml
@@ -0,0 +1,107 @@
+
+
+
+ keycloak-docs-parent
+ org.keycloak
+ 17.0.0-SNAPSHOT
+ ../pom.xml
+
+
+ 4.0.0
+
+ Keycloak Guides
+ keycloak-guides
+ Keycloak Guides
+ pom
+
+
+
+ org.keycloak
+ keycloak-guides-maven-plugin
+ ${project.version}
+
+
+
+
+
+
+ org.keycloak
+ keycloak-guides-maven-plugin
+ ${project.version}
+
+
+ generate-asciidoc
+
+ keycloak-guide
+
+
+
+
+
+ org.asciidoctor
+ asciidoctor-maven-plugin
+ 1.5.5
+
+
+ asciidoc-to-html
+ generate-resources
+
+ process-asciidoc
+
+
+ ${basedir}/target/generated-guides/server
+ index.adoc
+ html5
+ coderay
+
+
+ ./
+ left
+ left
+ font
+ true
+
+ -
+ true
+
+
+
+
+
+
+ maven-assembly-plugin
+
+
+ assembly.xml
+
+
+
+
+ create-archive
+ package
+
+ single
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/guides/src/GENERATE-DOCS.md b/docs/guides/src/GENERATE-DOCS.md
new file mode 100644
index 000000000000..8c920e0d43e6
--- /dev/null
+++ b/docs/guides/src/GENERATE-DOCS.md
@@ -0,0 +1,17 @@
+To generate the docs, we use a maven plugin that transforms the guides from FreeMarker enabled AsciiDoc to pure AsciiDoc. This includes linking the options from the Configuration to expose them to FreeMarker templates.
+
+FreeMarker macros are used heavily here to keep consistency throughout the guides, and to make the guides themselves as slim as possible.
+
+To help debugging, for now use the `DocsBuildDebugUtil.java` which has a main method that allows running this step outside of Maven.
+
+To build the guides, run:
+```
+cd docs
+mvn clean install
+```
+After that you will have the following artifacts:
+
+- `docs/guides/target/generated-guides`: pure asciidoc generated versions of the guides
+- `docs/guides/target/generated-docs/index.html`: all guides in a single html file generated with asciidoc maven plugins.
+
+_Note:_ The layout primarily serves as an example for now and is not how we will eventually present the documentation.
\ No newline at end of file
diff --git a/docs/guides/src/main/server/all-config.adoc b/docs/guides/src/main/server/all-config.adoc
new file mode 100644
index 000000000000..8d506405c0d1
--- /dev/null
+++ b/docs/guides/src/main/server/all-config.adoc
@@ -0,0 +1,16 @@
+<#import "/templates/guide.adoc" as template>
+<#import "/templates/options.adoc" as opts>
+
+<@template.guide
+title="All configuration"
+summary="Complete list of all build options and configuration for Keycloak">
+
+<#list ctx.options.categories as category>
+<#assign categoryOptions=ctx.options.getValues(category)>
+<#if categoryOptions?has_content>
+== ${category.heading}
+
+<@opts.list options=categoryOptions>@opts.list>
+#if>
+#list>
+@template.guide>
diff --git a/docs/guides/src/main/server/db.adoc b/docs/guides/src/main/server/db.adoc
new file mode 100644
index 000000000000..d3fe35bafc2d
--- /dev/null
+++ b/docs/guides/src/main/server/db.adoc
@@ -0,0 +1,27 @@
+<#import "/templates/guide.adoc" as tmpl>
+<#import "/templates/kc.adoc" as kc>
+<#import "/templates/options.adoc" as opts>
+<#import "/templates/links.adoc" as links>
+
+<@tmpl.guide
+ title="Relational database setup"
+ summary="Understand how to configure different relational databases for Keycloak"
+ priority=10
+ includedOptions="db db.* hostname">
+
+First step is to decide which database vendor you are going to use. Keycloak has support for a number of different vendors.
+
+Take a look at <@links.server id="all-config"/> for more information.
+
+Selecting the database vendor is done at build-time rather than at runtime. To select the database vendor run:
+
+<@kc.build parameters="--db "/>
+
+Valid options for database vendors include:
+
+<@opts.expectedValues option="db"/>
+
+Once configured you can easily connect to the database with:
+
+<@kc.start parameters="--db-url-host --db-schema --db-username --db-password "/>
+@tmpl.guide>
\ No newline at end of file
diff --git a/docs/guides/src/main/server/enabletls.adoc b/docs/guides/src/main/server/enabletls.adoc
new file mode 100644
index 000000000000..dc6954a926ec
--- /dev/null
+++ b/docs/guides/src/main/server/enabletls.adoc
@@ -0,0 +1,57 @@
+<#import "/templates/guide.adoc" as tmpl>
+<#import "/templates/kc.adoc" as kc>
+
+<@tmpl.guide
+title="Configure TLS"
+summary="Learn how to configure Keycloak's https certificates for in- and outgoing requests as well as mTLS."
+includedOptions="https.* http.enabled">
+
+Transport Layer Security (short: TLS) is crucial to exchange data over a secured channel. For production environments, you should never expose Keycloak endpoints through HTTP, as sensitive data is at the core of what Keycloak exchanges with other applications. In this guide you will learn how to configure Keycloak to use HTTPS/TLS.
+
+== Configure TLS in Keycloak
+Keycloak can be configured to load the needed certificate infrastructure using files in PEM format or from a Java KeyStore. When both alternatives are set up, the PEM files takes precedence over the Java KeyStores.
+
+=== Providing certificates in PEM format
+When you use a pair of matching certificate / private key files in PEM format, configure Keycloak to use them by running:
+
+<@kc.start parameters="--https-certificate-file=/path/to/certfile.pem --https-certificate-key-file=/path/to/keyfile.pem"/>
+
+Keycloak creates a keystore out of these files in memory and uses this keystore afterwards.
+
+=== Providing a Java KeyStore
+When no keystore file is configured explicitly, but `http.enabled` is set to false, Keycloak looks for a `conf/server.keystore` file by default.
+
+As an alternative, you can use an existing keystore by running:
+<@kc.start parameters="--https-key-store-file=/path/to/existing-keystore-file"/>
+
+==== Setting the KeyStore password
+You can set a secure password for your keystore using the `https.key-store.password` option:
+<@kc.start parameters="--https-key-store-password="/>
+If no password is set, the default password `password` is used.
+
+== Configure TLS protocols
+By default, Keycloak does not enable deprecated TLS protocols. In situations where clients only support deprecated protocols, first consider upgrading the client, but as a temporary work-around you can enable deprecated protocols with:
+
+<@kc.start parameters="--https-protocols=[,]"/>
+
+To also allow TLSv1.2, use for example `kc.sh start --http-protocols=TLSv1.3,TLSv1.2`.
+
+== Switch the HTTPS port
+Keycloak listens for HTTPS traffic on port `8443` by default. To change this port, use:
+<@kc.start parameters="--https-port="/>
+
+== Using a truststore
+Keycloak uses a truststore to store certificates to verify clients that are communicating with Keycloak, e.g. to use mutualTLS, certificate-bound tokens or X.509 authentication. This truststore is used for outgoing https requests as well as mTLS requests.
+
+You can configure the location of this truststore by running:
+<@kc.start parameters="--https-trust-store-file=/path/to/file"/>
+
+=== Setting the truststore password
+You can set a secure password for your truststore using the `https.trust-store.password` option:
+<@kc.start parameters="--https.trust-store.password="/>
+If no password is set, the default password `password` is used.
+
+== Securing credentials
+We recommend to not set any password in plaintext via the CLI or in `conf/keycloak.properties`, but instead using good practices such as using a vault / mounted secret. Please refer to the Vault Guide / Production deployment guide for more advice.
+
+@tmpl.guide>
\ No newline at end of file
diff --git a/docs/guides/src/main/server/index.adoc b/docs/guides/src/main/server/index.adoc
new file mode 100644
index 000000000000..61122c51957a
--- /dev/null
+++ b/docs/guides/src/main/server/index.adoc
@@ -0,0 +1,10 @@
+<#list ctx.guides as guide>
+:links_server_${guide.id}_name: ${guide.title}
+:links_server_${guide.id}_url: #${guide.id}
+#list>
+
+= Keycloak server guide
+
+<#list ctx.guides as guide>
+include::${guide.template}[leveloffset=+1]
+#list>
diff --git a/docs/guides/src/main/server/proxy.adoc b/docs/guides/src/main/server/proxy.adoc
new file mode 100644
index 000000000000..e0dec0f538cf
--- /dev/null
+++ b/docs/guides/src/main/server/proxy.adoc
@@ -0,0 +1,38 @@
+<#import "/templates/guide.adoc" as tmpl>
+<#import "/templates/kc.adoc" as kc>
+<#import "/templates/options.adoc" as opts>
+
+<@tmpl.guide
+title="Configuring a reverse proxy"
+summary="Learn how to configure Keycloak together with a reverse proxy, api gateway or load balancer."
+priority=20
+includedOptions="proxy proxy.*">
+
+It is pretty common nowadays to use a reverse proxy in distributed environments. If you want to use Keycloak together with such a proxy, you can use different proxy modes depending on the TLS termination in your specific environment:
+
+== Available proxy modes
+The `none` mode disables proxy support. It is the default mode.
+
+The `edge` mode enables communication through HTTP between the proxy and Keycloak. This mode is suitable for deployments with a highly secure internal network where the reverse proxy keeps a secure connection (HTTP over TLS) with clients while communicating with Keycloak using HTTP.
+
+The `reencrypt` mode requires communication through HTTPS between the proxy and Keycloak. This mode is suitable for deployments where internal communication between the reverse proxy and Keycloak should also be protected, and different keys and certificates are used on the reverse proxy as well as on Keycloak.
+
+The `passthrough` mode enables communication through HTTP or HTTPS between the proxy and Keycloak. This mode is suitable for deployments where the reverse proxy is not terminating TLS, but only forwarding the requests to the Keycloak server so that secure connections between the server and clients are based on the keys and certificates used by the Keycloak server itself.
+
+== Configure the proxy mode in Keycloak
+To select the proxy mode, run:
+
+<@kc.start parameters="--proxy "/>
+
+== Configure the reverse proxy
+Make sure your reverse proxy is configured correctly. To do so, please:
+
+* Properly set X-Forwarded-For and X-Forwarded-Proto HTTP headers.
+
+* Preserve the original 'Host' HTTP header.
+
+Please consult the documentation of your reverse proxy on how to set these headers.
+
+Take extra precautions to ensure that the X-Forwarded-For header is set by your reverse proxy. If it is not configured correctly, rogue clients can set this header themselves and trick Keycloak into thinking the client is connecting from a different IP address than it actually does. This may become really important if you are doing any black or white listing of IP addresses.
+
+@tmpl.guide>
diff --git a/docs/guides/src/main/templates/guide.adoc b/docs/guides/src/main/templates/guide.adoc
new file mode 100644
index 000000000000..e28f608aaaf3
--- /dev/null
+++ b/docs/guides/src/main/templates/guide.adoc
@@ -0,0 +1,19 @@
+<#import "/templates/options.adoc" as opts>
+
+<#macro guide title summary priority=999 includedOptions="">
+:guide-id: ${id}
+:guide-title: ${title}
+:guide-summary: ${summary}
+:guide-priority: ${priority}
+
+[[${id}]]
+= ${title}
+
+<#nested>
+
+<#if includedOptions?has_content>
+== Relevant options
+
+<@opts.list options=ctx.options.getOptions(includedOptions)>@opts.list>
+#if>
+#macro>
\ No newline at end of file
diff --git a/docs/guides/src/main/templates/kc.adoc b/docs/guides/src/main/templates/kc.adoc
new file mode 100644
index 000000000000..7b905d2d9282
--- /dev/null
+++ b/docs/guides/src/main/templates/kc.adoc
@@ -0,0 +1,13 @@
+<#macro build parameters>
+[source,bash]
+----
+bin/kc.[sh|bat] build ${parameters}
+----
+#macro>
+
+<#macro start parameters>
+[source,bash]
+----
+bin/kc.[sh|bat] start ${parameters}
+----
+#macro>
\ No newline at end of file
diff --git a/docs/guides/src/main/templates/links.adoc b/docs/guides/src/main/templates/links.adoc
new file mode 100644
index 000000000000..c1c3c80e960a
--- /dev/null
+++ b/docs/guides/src/main/templates/links.adoc
@@ -0,0 +1,3 @@
+<#macro server id>
+link:{links_server_${id}_url}[{links_server_${id}_name}]
+#macro>
\ No newline at end of file
diff --git a/docs/guides/src/main/templates/options.adoc b/docs/guides/src/main/templates/options.adoc
new file mode 100644
index 000000000000..7895a7df6419
--- /dev/null
+++ b/docs/guides/src/main/templates/options.adoc
@@ -0,0 +1,32 @@
+<#macro expectedValues option>
+<#list ctx.options.getOption(option).expectedValues as expectedValue>
+* ${expectedValue}
+#list>
+#macro>
+
+<#macro list options>
+[cols="12a,4,4,1",role="options"]
+|===
+| |Type|Default|
+
+<#list options as option>
+|
+[.options-key]#${option.key}#
+
+[.options-description]#${option.description}#
+
+[#option-extended-${option.key},role="options-extended"]
+!===
+!<#if option.descriptionExtended?has_content>[.options-description-extended]#${option.descriptionExtended!}##if>
+![.options-description-example]#*CLI:* `${option.keyCli}`#
+![.options-description-example]#*Env:* `${option.keyEnv}`#
+!===
+|<#if option.expectedValues?has_content>[.options-type]#${option.expectedValues?join(", ")}##if>
+
+|<#if option.defaultValue?has_content>[.options-default]#${option.defaultValue!}##if>
+
+|<#if option.build>icon:tools[role=options-build]#if>
+#list>
+
+|===
+#macro>
\ No newline at end of file
diff --git a/docs/guides/src/test/java/org/keycloak/guides/DocsBuildDebugUtil.java b/docs/guides/src/test/java/org/keycloak/guides/DocsBuildDebugUtil.java
new file mode 100644
index 000000000000..108c2a2c6521
--- /dev/null
+++ b/docs/guides/src/test/java/org/keycloak/guides/DocsBuildDebugUtil.java
@@ -0,0 +1,22 @@
+package org.keycloak.guides;
+
+import freemarker.template.TemplateException;
+import org.keycloak.guides.maven.GuideBuilder;
+
+import java.io.File;
+import java.io.IOException;
+
+public class DocsBuildDebugUtil {
+
+ public static void main(String[] args) throws IOException, TemplateException {
+ String userDir = System.getProperty("user.dir");
+ File usrDir = new File(System.getProperty("user.dir"));
+ File srcDir = usrDir.toPath().resolve("docs/guides/src/main").toFile();
+ File targetDir = usrDir.toPath().resolve("target/generated-guides-tests").toFile();
+ targetDir.mkdirs();
+ GuideBuilder builder = new GuideBuilder(srcDir, targetDir, null);
+ builder.server();
+ System.out.println("Guides generated to: " + targetDir.getAbsolutePath().toString());
+ }
+
+}
diff --git a/docs/maven-plugin/pom.xml b/docs/maven-plugin/pom.xml
new file mode 100644
index 000000000000..88a864b7c10c
--- /dev/null
+++ b/docs/maven-plugin/pom.xml
@@ -0,0 +1,86 @@
+
+
+
+
+ keycloak-docs-parent
+ org.keycloak
+ 17.0.0-SNAPSHOT
+ ../pom.xml
+
+
+ 4.0.0
+
+ Keycloak Guides Maven Plugin
+ keycloak-guides-maven-plugin
+ Keycloak Guides Maven Plugin
+ maven-plugin
+
+
+
+
+ io.quarkus
+ quarkus-bom
+ ${quarkus.version}
+ pom
+ import
+
+
+
+
+
+ org.apache.maven
+ maven-plugin-api
+ 3.6.3
+
+
+ org.apache.maven.plugin-tools
+ maven-plugin-annotations
+ 3.6.0
+ provided
+
+
+ org.apache.maven
+ maven-project
+ 2.2.1
+
+
+ org.keycloak
+ keycloak-quarkus-server
+
+
+ *
+ *
+
+
+
+
+ io.quarkus
+ quarkus-core
+
+
+ io.smallrye.config
+ smallrye-config
+
+
+ org.freemarker
+ freemarker
+
+
+
+
\ No newline at end of file
diff --git a/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Context.java b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Context.java
new file mode 100644
index 000000000000..34f1da7de4c0
--- /dev/null
+++ b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Context.java
@@ -0,0 +1,40 @@
+package org.keycloak.guides.maven;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+
+public class Context {
+
+ private File srcDir;
+ private Options options;
+ private List guides;
+
+ public Context(File srcDir) throws IOException {
+ this.srcDir = srcDir;
+ this.options = new Options();
+
+ this.guides = new LinkedList<>();
+
+ GuideParser parser = new GuideParser();
+ for (File f : new File(srcDir, "server").listFiles((dir, f) -> f.endsWith(".adoc") && !f.equals("index.adoc"))) {
+ Guide guide = parser.parse(f);
+ if (guide != null) {
+ guides.add(guide);
+ }
+ }
+
+ Collections.sort(guides, Comparator.comparingInt(Guide::getPriority));
+ }
+
+ public Options getOptions() {
+ return options;
+ }
+
+ public List getGuides() {
+ return guides;
+ }
+}
diff --git a/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/FreeMarker.java b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/FreeMarker.java
new file mode 100644
index 000000000000..2f7901e807a7
--- /dev/null
+++ b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/FreeMarker.java
@@ -0,0 +1,48 @@
+package org.keycloak.guides.maven;
+
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateExceptionHandler;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.HashMap;
+import java.util.Map;
+
+public class FreeMarker {
+
+ private File targetDir;
+ private Map attributes;
+ private Configuration configuration;
+
+ public FreeMarker(File srcDir, File targetDir, Map attributes) throws IOException {
+ this.targetDir = targetDir;
+ this.attributes = attributes;
+
+ configuration = new Configuration(Configuration.VERSION_2_3_31);
+ configuration.setDirectoryForTemplateLoading(srcDir);
+ configuration.setDefaultEncoding("UTF-8");
+ configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
+ configuration.setLogTemplateExceptions(false);
+ }
+
+ public void template(String template) throws IOException, TemplateException {
+ Template t = configuration.getTemplate(template);
+ File out = targetDir.toPath().resolve(template).toFile();
+
+ File parent = out.getParentFile();
+ if (!parent.isDirectory()) {
+ parent.mkdir();
+ }
+
+ HashMap attrs = new HashMap<>(attributes);
+ attrs.put("id", template.split("/")[1].replace(".adoc", ""));
+
+ Writer w = new FileWriter(out);
+ t.process(attrs, w);
+ }
+
+}
diff --git a/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Guide.java b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Guide.java
new file mode 100644
index 000000000000..15912b4e064c
--- /dev/null
+++ b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Guide.java
@@ -0,0 +1,50 @@
+package org.keycloak.guides.maven;
+
+public class Guide {
+
+ private String template;
+ private String id;
+ private String title;
+ private String summary;
+ private int priority;
+
+ public String getTemplate() {
+ return template;
+ }
+
+ public void setTemplate(String template) {
+ this.template = template;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getSummary() {
+ return summary;
+ }
+
+ public void setSummary(String summary) {
+ this.summary = summary;
+ }
+
+ public int getPriority() {
+ return priority;
+ }
+
+ public void setPriority(int priority) {
+ this.priority = priority;
+ }
+}
diff --git a/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/GuideBuilder.java b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/GuideBuilder.java
new file mode 100644
index 000000000000..3f11967dbf0e
--- /dev/null
+++ b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/GuideBuilder.java
@@ -0,0 +1,43 @@
+package org.keycloak.guides.maven;
+
+import freemarker.template.TemplateException;
+import org.apache.maven.plugin.logging.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+public class GuideBuilder {
+
+ private final FreeMarker freeMarker;
+ private final File srcDir;
+ private final File targetDir;
+ private final Log log;
+
+ public GuideBuilder(File srcDir, File targetDir, Log log) throws IOException {
+ this.srcDir = srcDir;
+ this.targetDir = targetDir;
+ this.log = log;
+
+ Map globalAttributes = new HashMap<>();
+ globalAttributes.put("ctx", new Context(srcDir));
+
+ this.freeMarker = new FreeMarker(srcDir, targetDir, globalAttributes);
+ }
+
+ public void server() throws TemplateException, IOException {
+ File serverGuidesDir = new File(srcDir, "server");
+ if (!serverGuidesDir.isDirectory()) {
+ serverGuidesDir.mkdir();
+ }
+
+ for (String t : serverGuidesDir.list((dir, name) -> name.endsWith(".adoc"))) {
+ freeMarker.template("server/" + t);
+ if (log != null) {
+ log.info("Templated: server/" + t);
+ }
+ }
+ }
+
+}
diff --git a/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/GuideMojo.java b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/GuideMojo.java
new file mode 100644
index 000000000000..649fd6effccd
--- /dev/null
+++ b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/GuideMojo.java
@@ -0,0 +1,43 @@
+package org.keycloak.guides.maven;
+
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+
+import java.io.File;
+
+@Mojo(name = "keycloak-guide", defaultPhase = LifecyclePhase.GENERATE_SOURCES)
+public class GuideMojo extends AbstractMojo {
+
+ @Parameter(property = "project.build.sourceDirectory")
+ private String sourceDir;
+
+ @Parameter(property = "project.build.directory")
+ private String targetDir;
+
+ @Override
+ public void execute() throws MojoFailureException {
+ try {
+ Log log = getLog();
+ File srcDir = new File(sourceDir).getParentFile();
+ File targetDir = new File(this.targetDir, "generated-guides");
+ if (!targetDir.isDirectory()) {
+ targetDir.mkdirs();
+ }
+
+ log.info("Guide dir: " + srcDir.getAbsolutePath());
+ log.info("Target dir: " + targetDir.getAbsolutePath());
+
+ GuideBuilder g = new GuideBuilder(srcDir, targetDir, log);
+ g.server();
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new MojoFailureException("Failed to generated asciidoc files", e);
+ }
+ }
+
+}
diff --git a/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/GuideParser.java b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/GuideParser.java
new file mode 100644
index 000000000000..062e070ae293
--- /dev/null
+++ b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/GuideParser.java
@@ -0,0 +1,86 @@
+package org.keycloak.guides.maven;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class GuideParser {
+
+ private Pattern TEMPLATE_IMPORT_PATTERN = Pattern.compile("<#import \"/templates/guide.adoc\" as (?[^ ]*)>");
+ private Pattern GUIDE_ELEMENT_PATTERN = Pattern.compile("(?priority|title|summary)=(\\\"(?[^\\\"]*)\\\"|(?[\\d]*))");
+
+ /**
+ * Parses a FreeMarker template to retrieve Guide attributes
+ * @param file
+ * @return A Guide instance; or null if not a guide
+ * @throws IOException
+ */
+ public Guide parse(File file) throws IOException {
+ try (BufferedReader br = new BufferedReader(new FileReader(file))) {
+ String importName = getImportName(br);
+ String importElement = getGuideElement(br, importName);
+
+ if (importElement != null) {
+ Guide guide = new Guide();
+ guide.setTemplate(file.getName());
+ guide.setPriority(999);
+
+ guide.setId(file.getName().replaceAll(".adoc", ""));
+
+ setAttributes(importElement, guide);
+
+ return guide;
+ }
+
+ return null;
+ }
+ }
+
+ private String getImportName(BufferedReader br) throws IOException {
+ for (String line = br.readLine(); line != null; line = br.readLine()) {
+ Matcher templateImportMatcher = TEMPLATE_IMPORT_PATTERN.matcher(line);
+ if (templateImportMatcher.matches()) {
+ return templateImportMatcher.group("importName");
+ }
+ }
+ return null;
+ }
+
+ private String getGuideElement(BufferedReader br, String importName) throws IOException {
+ if (importName != null) {
+ for (String line = br.readLine(); line != null; line = br.readLine()) {
+ if (line.contains("<@" + importName + ".guide")) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(line.trim());
+ while (!line.contains(">")) {
+ line = br.readLine();
+ sb.append(" " + line.trim());
+ }
+ return sb.toString();
+ }
+ }
+ }
+ return null;
+ }
+
+ private void setAttributes(String importElement, Guide guide) {
+ Matcher attributeMatcher = GUIDE_ELEMENT_PATTERN.matcher(importElement);
+ while (attributeMatcher.find()) {
+ String key = attributeMatcher.group("key");
+ switch (key) {
+ case "title":
+ guide.setTitle(attributeMatcher.group("valueString"));
+ break;
+ case "summary":
+ guide.setSummary(attributeMatcher.group("valueString"));
+ break;
+ case "priority":
+ guide.setPriority(Integer.parseInt(attributeMatcher.group("valueInt")));
+ }
+ }
+ }
+
+}
diff --git a/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Options.java b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Options.java
new file mode 100644
index 000000000000..b78fff143a31
--- /dev/null
+++ b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Options.java
@@ -0,0 +1,106 @@
+package org.keycloak.guides.maven;
+
+import org.keycloak.quarkus.runtime.configuration.mappers.ConfigCategory;
+import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+public class Options {
+
+ private final Map options;
+
+ public Options() {
+ options = PropertyMappers.getMappers().stream()
+ .filter(m -> !m.isHidden())
+ .map(m -> new Option(m.getFrom(), m.getCategory(), m.isBuildTime(), m.getDescription(), m.getDefaultValue(), m.getExpectedValues()))
+ .collect(Collectors.toMap(Option::getKey, o -> o, (o1, o2) -> o1)); // Need to ignore duplicate keys??
+ }
+
+ public ConfigCategory[] getCategories() {
+ return ConfigCategory.values();
+ }
+
+ public Collection getValues() {
+ return options.values();
+ }
+
+ public Collection getValues(ConfigCategory category) {
+ return options.values().stream().filter(o -> o.category.equals(category)).collect(Collectors.toList());
+ }
+
+ public Option getOption(String key) {
+ return options.get(key);
+ }
+
+ public List getOptions(String includeOptions) {
+ final String r = includeOptions.replaceAll("\\.", "\\\\.").replaceAll("\\*", ".*").replace(' ', '|');
+ return this.options.values().stream().filter(o -> o.getKey().matches(r)).collect(Collectors.toList());
+ }
+
+ public class Option {
+
+ private String key;
+ private ConfigCategory category;
+ private boolean build;
+ private String description;
+ private String defaultValue;
+ private List expectedValues;
+
+ public Option(String key, ConfigCategory category, boolean build, String description, String defaultValue, Iterable expectedValues) {
+ this.key = key;
+ this.category = category;
+ this.build = build;
+ this.description = description;
+ this.defaultValue = defaultValue;
+ this.expectedValues = StreamSupport.stream(expectedValues.spliterator(), false).collect(Collectors.toList());
+ }
+
+ public boolean isBuild() {
+ return build;
+ }
+
+ public String getKey() {
+ return key.substring(3);
+ }
+
+ public String getKeyCli() {
+ return "--" + key.substring(3).replace('.', '-');
+ }
+
+ public String getKeyEnv() {
+ return key.toUpperCase().replace('.', '_').replace('-', '_');
+ }
+
+ public String getDescription() {
+ int i = description.indexOf('.');
+ if (i == -1) {
+ return description;
+ } else {
+ return description.substring(0, i + 1).trim();
+ }
+ }
+
+ public String getDescriptionExtended() {
+ int i = description.indexOf('.');
+ if (i == -1) {
+ return null;
+ } else {
+ String extended = description.substring(i + 1).trim();
+ return extended.length() > 0 ? extended : null;
+ }
+ }
+
+ public String getDefaultValue() {
+ return defaultValue;
+ }
+
+ public List getExpectedValues() {
+ return expectedValues;
+ }
+ }
+
+}
diff --git a/docs/pom.xml b/docs/pom.xml
new file mode 100755
index 000000000000..0b780a8a91c4
--- /dev/null
+++ b/docs/pom.xml
@@ -0,0 +1,47 @@
+
+
+
+ keycloak-parent
+ org.keycloak
+ 17.0.0-SNAPSHOT
+ ../pom.xml
+
+ Keycloak Docs Parent
+
+ 4.0.0
+
+ keycloak-docs-parent
+ pom
+
+
+
+ quarkus
+
+ [11,)
+
+ !product
+
+
+
+ maven-plugin
+ guides
+
+
+
+
diff --git a/examples/admin-client/pom.xml b/examples/admin-client/pom.xml
index 51b93841bc21..d7219a365a13 100755
--- a/examples/admin-client/pom.xml
+++ b/examples/admin-client/pom.xml
@@ -22,7 +22,7 @@
keycloak-examples-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
Keycloak Examples - Admin Client
diff --git a/examples/broker/facebook-authentication/pom.xml b/examples/broker/facebook-authentication/pom.xml
index 0f9c1c1c81c1..4390727fc44f 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
Keycloak Broker Examples - Facebook Authentication
diff --git a/examples/broker/google-authentication/pom.xml b/examples/broker/google-authentication/pom.xml
index f081046ffc6e..1d8009bf85e8 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
Keycloak Broker Examples - Google Authentication
diff --git a/examples/broker/pom.xml b/examples/broker/pom.xml
index 2eba456923a1..c5e705e18213 100755
--- a/examples/broker/pom.xml
+++ b/examples/broker/pom.xml
@@ -20,7 +20,7 @@
keycloak-examples-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
Broker Examples
diff --git a/examples/broker/saml-broker-authentication/pom.xml b/examples/broker/saml-broker-authentication/pom.xml
index f82c87ba8c09..589e516eef3f 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
Keycloak Broker Examples - SAML Identity Provider Brokering
diff --git a/examples/broker/twitter-authentication/pom.xml b/examples/broker/twitter-authentication/pom.xml
index 37338c27c5b5..c4d7ee5ee724 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
Keycloak Broker Examples - Twitter Authentication
diff --git a/examples/cors/angular-product-app/pom.xml b/examples/cors/angular-product-app/pom.xml
index fefc25875679..28cb1fd15cbc 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
4.0.0
diff --git a/examples/cors/database-service/pom.xml b/examples/cors/database-service/pom.xml
index 7c4f0e20e0fc..c7d95f108497 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
4.0.0
diff --git a/examples/cors/pom.xml b/examples/cors/pom.xml
index 0926007c7eaa..91c9b7654e60 100755
--- a/examples/cors/pom.xml
+++ b/examples/cors/pom.xml
@@ -21,7 +21,7 @@
keycloak-examples-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
Keycloak Examples - CORS
diff --git a/examples/js-console/pom.xml b/examples/js-console/pom.xml
index f6c65e3ad74f..26e31a352c09 100755
--- a/examples/js-console/pom.xml
+++ b/examples/js-console/pom.xml
@@ -21,7 +21,7 @@
keycloak-examples-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
4.0.0
diff --git a/examples/kerberos/pom.xml b/examples/kerberos/pom.xml
index 7fb8d557c21f..a0dbd4a2a81f 100755
--- a/examples/kerberos/pom.xml
+++ b/examples/kerberos/pom.xml
@@ -22,7 +22,7 @@
keycloak-examples-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
Keycloak Examples - Kerberos Credential Delegation
diff --git a/examples/ldap/pom.xml b/examples/ldap/pom.xml
index d72d617a7d3c..6c000dbb7eea 100644
--- a/examples/ldap/pom.xml
+++ b/examples/ldap/pom.xml
@@ -22,7 +22,7 @@
keycloak-examples-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
4.0.0
diff --git a/examples/pom.xml b/examples/pom.xml
index ef27170b86e8..c17f4380f266 100755
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
Keycloak Examples
diff --git a/examples/providers/authenticator/pom.xml b/examples/providers/authenticator/pom.xml
index 5c56d23b667e..b321477b295e 100755
--- a/examples/providers/authenticator/pom.xml
+++ b/examples/providers/authenticator/pom.xml
@@ -20,7 +20,7 @@
keycloak-examples-providers-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
Authenticator Example
diff --git a/examples/providers/domain-extension/pom.xml b/examples/providers/domain-extension/pom.xml
index 33403dd06c13..d806dcf31a47 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
Domain Extension Example
diff --git a/examples/providers/pom.xml b/examples/providers/pom.xml
index bd8095c97467..2abacd11d5b4 100755
--- a/examples/providers/pom.xml
+++ b/examples/providers/pom.xml
@@ -20,7 +20,7 @@
keycloak-examples-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
Provider Examples
diff --git a/examples/providers/rest/pom.xml b/examples/providers/rest/pom.xml
index 9fa19f94c7c5..b3ad668af69d 100755
--- a/examples/providers/rest/pom.xml
+++ b/examples/providers/rest/pom.xml
@@ -20,7 +20,7 @@
keycloak-examples-providers-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
REST Example
diff --git a/examples/saml/pom.xml b/examples/saml/pom.xml
index 3073173e9faf..644d8f81425d 100755
--- a/examples/saml/pom.xml
+++ b/examples/saml/pom.xml
@@ -20,7 +20,7 @@
keycloak-examples-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
SAML Examples
diff --git a/examples/saml/servlet-filter/pom.xml b/examples/saml/servlet-filter/pom.xml
index ab26741429d0..656196b82cd7 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
saml-servlet-filter
diff --git a/examples/themes/pom.xml b/examples/themes/pom.xml
index 27eb2df6facc..8ec7d8e3dc15 100755
--- a/examples/themes/pom.xml
+++ b/examples/themes/pom.xml
@@ -20,7 +20,7 @@
keycloak-examples-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
Themes Examples
diff --git a/examples/themes/src/main/resources/theme/logo-example/login/resources/css/logo.css b/examples/themes/src/main/resources/theme/logo-example/login/resources/css/logo.css
index e58ebb483291..a820be94f9cb 100755
--- a/examples/themes/src/main/resources/theme/logo-example/login/resources/css/logo.css
+++ b/examples/themes/src/main/resources/theme/logo-example/login/resources/css/logo.css
@@ -15,8 +15,7 @@
* limitations under the License.
*/
-#kc-logo-wrapper {
- background: url("../img/red-hat-logo.png") no-repeat top right;
- height: 45px;
- width: 200px;
+div.kc-logo-text {
+ background: url(../img/red-hat-logo.png) no-repeat center center;
+ background-size: contain;
}
diff --git a/examples/themes/src/main/resources/theme/logo-example/login/resources/img/red-hat-logo.png b/examples/themes/src/main/resources/theme/logo-example/login/resources/img/red-hat-logo.png
index 273939b54370..45a80951d4cb 100644
Binary files a/examples/themes/src/main/resources/theme/logo-example/login/resources/img/red-hat-logo.png and b/examples/themes/src/main/resources/theme/logo-example/login/resources/img/red-hat-logo.png differ
diff --git a/examples/themes/src/main/resources/theme/logo-example/login/theme.properties b/examples/themes/src/main/resources/theme/logo-example/login/theme.properties
index 5f199090e8b6..ddd3c582316b 100755
--- a/examples/themes/src/main/resources/theme/logo-example/login/theme.properties
+++ b/examples/themes/src/main/resources/theme/logo-example/login/theme.properties
@@ -18,4 +18,4 @@
parent=keycloak
import=common/keycloak
-styles=lib/patternfly/css/patternfly.css lib/zocial/zocial.css css/login.css css/logo.css
\ No newline at end of file
+styles=css/login.css css/tile.css css/logo.css
\ No newline at end of file
diff --git a/federation/kerberos/pom.xml b/federation/kerberos/pom.xml
index cf33d463427a..d719c37b7c1d 100755
--- a/federation/kerberos/pom.xml
+++ b/federation/kerberos/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../pom.xml
4.0.0
diff --git a/federation/ldap/pom.xml b/federation/ldap/pom.xml
index 119e2cf5154f..eec0c18e5eff 100755
--- a/federation/ldap/pom.xml
+++ b/federation/ldap/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../pom.xml
4.0.0
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java
index 3a240b6f89b1..f9abcc0a1e5d 100644
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPOperationManager.java
@@ -370,26 +370,35 @@ private SearchControls getSearchControls(Collection returningAttributes,
}
public String getFilterById(String id) {
- String filter = null;
+ StringBuilder filter = new StringBuilder();
+ filter.insert(0, "(&");
if (this.config.isObjectGUID()) {
byte[] objectGUID = LDAPUtil.encodeObjectGUID(id);
-
- filter = "(&(objectClass=*)(" + getUuidAttributeName() + LDAPConstants.EQUAL + LDAPUtil.convertObjectGUIDToByteString(objectGUID) + "))";
+ filter.append("(objectClass=*)(").append(
+ getUuidAttributeName()).append(LDAPConstants.EQUAL)
+ .append(LDAPUtil.convertObjectGUIDToByteString(
+ objectGUID)).append(")");
} else if (this.config.isEdirectoryGUID()) {
- filter = "(&(objectClass=*)(" + getUuidAttributeName().toUpperCase() + LDAPConstants.EQUAL + LDAPUtil.convertGUIDToEdirectoryHexString(id) + "))";
+ filter.append("(objectClass=*)(").append(getUuidAttributeName().toUpperCase())
+ .append(LDAPConstants.EQUAL
+ ).append(LDAPUtil.convertGUIDToEdirectoryHexString(id)).append(")");
+ } else {
+ filter.append("(objectClass=*)(").append(getUuidAttributeName()).append(LDAPConstants.EQUAL)
+ .append(id).append(")");
}
- if (filter == null) {
- filter = "(&(objectClass=*)(" + getUuidAttributeName() + LDAPConstants.EQUAL + id + "))";
+ if (config.getCustomUserSearchFilter() != null) {
+ filter.append(config.getCustomUserSearchFilter());
}
- if (logger.isTraceEnabled()) {
- logger.tracef("Using filter for lookup user by LDAP ID: %s", filter);
- }
+ filter.append(")");
+ String ldapIdFilter = filter.toString();
+
+ logger.tracef("Using filter for lookup user by LDAP ID: %s", ldapIdFilter);
- return filter;
+ return ldapIdFilter;
}
public SearchResult lookupById(final String baseDN, final String id, final Collection returningAttributes) {
@@ -521,6 +530,13 @@ public void authenticate(String dn, String password) throws AuthenticationExcept
}
throw ae;
+ } catch(RuntimeException re){
+ if (logger.isDebugEnabled()) {
+ logger.debugf(re, "LDAP Connection TimeOut for DN [%s]", dn);
+ }
+
+ throw re;
+
} catch (Exception e) {
logger.errorf(e, "Unexpected exception when validating password of DN [%s]", dn);
throw new AuthenticationException("Unexpected exception when validating password of user");
diff --git a/federation/pom.xml b/federation/pom.xml
index 9cef993adfba..510c7e4d913d 100755
--- a/federation/pom.xml
+++ b/federation/pom.xml
@@ -22,7 +22,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../pom.xml
4.0.0
diff --git a/federation/sssd/pom.xml b/federation/sssd/pom.xml
index 11f58bc6cc79..40c0b0023d55 100644
--- a/federation/sssd/pom.xml
+++ b/federation/sssd/pom.xml
@@ -4,7 +4,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../../pom.xml
4.0.0
diff --git a/integration/admin-client/pom.xml b/integration/admin-client/pom.xml
index 12339dae0589..98dd3d27c612 100755
--- a/integration/admin-client/pom.xml
+++ b/integration/admin-client/pom.xml
@@ -22,7 +22,7 @@
keycloak-integration-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
4.0.0
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RoleResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RoleResource.java
index 53ca36df06ad..f873565a1aed 100755
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RoleResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RoleResource.java
@@ -17,6 +17,7 @@
package org.keycloak.admin.client.resource;
+import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.ManagementPermissionReference;
import org.keycloak.representations.idm.ManagementPermissionRepresentation;
@@ -25,6 +26,7 @@
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
@@ -90,6 +92,16 @@ public interface RoleResource {
@Path("composites/clients/{clientUuid}")
@Produces(MediaType.APPLICATION_JSON)
Set getClientRoleComposites(@PathParam("clientUuid") String clientUuid);
+
+ @GET
+ @Path("parents")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Set getParentsRoles();
+
+ @GET
+ @Path("parents")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Set getParentsRoles(@QueryParam("briefRepresentation") @DefaultValue("true") boolean briefRepresentation);
@POST
@Path("composites")
diff --git a/integration/client-cli/admin-cli/pom.xml b/integration/client-cli/admin-cli/pom.xml
index 7f669974c49d..d8dd8a862eaf 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
4.0.0
diff --git a/integration/client-cli/admin-cli/src/main/bin/kcadm.sh b/integration/client-cli/admin-cli/src/main/bin/kcadm.sh
index 0c905b85147d..a7bfc6a48a45 100755
--- a/integration/client-cli/admin-cli/src/main/bin/kcadm.sh
+++ b/integration/client-cli/admin-cli/src/main/bin/kcadm.sh
@@ -22,12 +22,5 @@ fi
DIRNAME=`dirname "$RESOLVED_NAME"`
-# Uncomment out these lines if you are integrating with `kcinit`
-#if [ "$1" = "config" ]; then
-# java $KC_OPTS -cp $DIRNAME/client/keycloak-admin-cli-${project.version}.jar org.keycloak.client.admin.cli.KcAdmMain "$@"
-#else
-# java $KC_OPTS -cp $DIRNAME/client/keycloak-admin-cli-${project.version}.jar org.keycloak.client.admin.cli.KcAdmMain "$@" --noconfig --token $(kcinit token admin-cli) --server $(kcinit show server)
-#fi
-# Remove the next line if you have enabled kcinit
java $KC_OPTS -cp $DIRNAME/client/keycloak-admin-cli-${project.version}.jar org.keycloak.client.admin.cli.KcAdmMain "$@"
diff --git a/integration/client-cli/client-cli-dist/pom.xml b/integration/client-cli/client-cli-dist/pom.xml
index 3dd4619797cb..133ba60610a5 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
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 4b24512484f0..cf506a6ebf4e 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
4.0.0
diff --git a/integration/client-cli/pom.xml b/integration/client-cli/pom.xml
index 2a7525d39b48..7ac6651c0a6b 100644
--- a/integration/client-cli/pom.xml
+++ b/integration/client-cli/pom.xml
@@ -20,7 +20,7 @@
keycloak-integration-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
Keycloak Client CLI
diff --git a/integration/client-registration/pom.xml b/integration/client-registration/pom.xml
index 675a1921a990..2db108f742e3 100755
--- a/integration/client-registration/pom.xml
+++ b/integration/client-registration/pom.xml
@@ -21,7 +21,7 @@
keycloak-integration-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
4.0.0
diff --git a/integration/pom.xml b/integration/pom.xml
index e32210b06fe7..a89212c49133 100755
--- a/integration/pom.xml
+++ b/integration/pom.xml
@@ -20,7 +20,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
../pom.xml
Keycloak Integration
diff --git a/misc/keycloak-test-helper/pom.xml b/misc/keycloak-test-helper/pom.xml
index dd074a7fbc64..a2ea2121b7d6 100644
--- a/misc/keycloak-test-helper/pom.xml
+++ b/misc/keycloak-test-helper/pom.xml
@@ -6,7 +6,7 @@
keycloak-misc-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
org.keycloak
keycloak-test-helper
diff --git a/misc/pom.xml b/misc/pom.xml
index 4bdf75d8b678..aae2cd0a59aa 100644
--- a/misc/pom.xml
+++ b/misc/pom.xml
@@ -3,7 +3,7 @@
keycloak-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
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 036e2fa4db38..4266826d403a 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
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 10a2bb45f89f..d74ccc37b7b1 100644
--- a/misc/spring-boot-starter/pom.xml
+++ b/misc/spring-boot-starter/pom.xml
@@ -5,7 +5,7 @@
keycloak-misc-parent
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
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 d6457314e30c..d4ac52773e56 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
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 61297812eac2..3358c6871d25 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
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
org.keycloak
keycloak-legacy-spring-boot-starter-parent
diff --git a/model/build-processor/pom.xml b/model/build-processor/pom.xml
index 466f9fbd2c21..8043e9c1b12e 100644
--- a/model/build-processor/pom.xml
+++ b/model/build-processor/pom.xml
@@ -3,7 +3,7 @@
keycloak-model-pom
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
4.0.0
diff --git a/model/build-processor/src/main/java/org/keycloak/models/map/annotations/GenerateHotRodEntityImplementation.java b/model/build-processor/src/main/java/org/keycloak/models/map/annotations/GenerateHotRodEntityImplementation.java
new file mode 100644
index 000000000000..52acf954f060
--- /dev/null
+++ b/model/build-processor/src/main/java/org/keycloak/models/map/annotations/GenerateHotRodEntityImplementation.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2021 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.map.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.TYPE)
+public @interface GenerateHotRodEntityImplementation {
+ String implementInterface();
+ String inherits() default "org.keycloak.models.map.common.UpdatableEntity.Impl";
+}
diff --git a/model/build-processor/src/main/java/org/keycloak/models/map/annotations/IgnoreForEntityImplementationGenerator.java b/model/build-processor/src/main/java/org/keycloak/models/map/annotations/IgnoreForEntityImplementationGenerator.java
index d8fd5cc7eacd..c3eebcacbd4d 100644
--- a/model/build-processor/src/main/java/org/keycloak/models/map/annotations/IgnoreForEntityImplementationGenerator.java
+++ b/model/build-processor/src/main/java/org/keycloak/models/map/annotations/IgnoreForEntityImplementationGenerator.java
@@ -25,7 +25,7 @@
*
* @author hmlnarik
*/
-@Retention(RetentionPolicy.SOURCE)
+@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface IgnoreForEntityImplementationGenerator {
}
diff --git a/model/build-processor/src/main/java/org/keycloak/models/map/exceptions/CannotMigrateTypeException.java b/model/build-processor/src/main/java/org/keycloak/models/map/exceptions/CannotMigrateTypeException.java
new file mode 100644
index 000000000000..fd2e8de3e687
--- /dev/null
+++ b/model/build-processor/src/main/java/org/keycloak/models/map/exceptions/CannotMigrateTypeException.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2021 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.map.exceptions;
+
+import javax.lang.model.type.TypeMirror;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+public class CannotMigrateTypeException extends RuntimeException {
+ private final TypeMirror toType;
+ private final TypeMirror[] fromType;
+
+ public CannotMigrateTypeException(TypeMirror toType, TypeMirror[] fromType) {
+ this.toType = toType;
+ this.fromType = fromType;
+ }
+
+ public String getFormattedMessage() {
+ return "Cannot migrate [" + Arrays.stream(fromType).map(TypeMirror::toString).collect(Collectors.joining(", ")) + "] to " + toType.toString();
+ }
+
+}
diff --git a/model/build-processor/src/main/java/org/keycloak/models/map/processor/AbstractGenerateEntityImplementationsProcessor.java b/model/build-processor/src/main/java/org/keycloak/models/map/processor/AbstractGenerateEntityImplementationsProcessor.java
new file mode 100644
index 000000000000..1e9f24fb6d94
--- /dev/null
+++ b/model/build-processor/src/main/java/org/keycloak/models/map/processor/AbstractGenerateEntityImplementationsProcessor.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright 2021 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.map.processor;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.NoType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import javax.tools.Diagnostic;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import static org.keycloak.models.map.processor.FieldAccessorType.GETTER;
+import static org.keycloak.models.map.processor.Util.getGenericsDeclaration;
+import static org.keycloak.models.map.processor.Util.isMapType;
+import static org.keycloak.models.map.processor.Util.singularToPlural;
+
+@SupportedSourceVersion(SourceVersion.RELEASE_8)
+public abstract class AbstractGenerateEntityImplementationsProcessor extends AbstractProcessor {
+
+ protected static final String FQN_DEEP_CLONER = "org.keycloak.models.map.common.DeepCloner";
+ protected static final String FQN_ENTITY_FIELD = "org.keycloak.models.map.common.EntityField";
+ protected static final String FQN_HAS_ENTITY_FIELD_DELEGATE = "org.keycloak.models.map.common.delegate.HasEntityFieldDelegate";
+ protected static final String FQN_ENTITY_FIELD_DELEGATE = "org.keycloak.models.map.common.delegate.EntityFieldDelegate";
+
+ protected Elements elements;
+ protected Types types;
+
+ protected static interface Generator {
+ void generate(TypeElement e) throws IOException;
+ }
+
+ @Override
+ public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ elements = processingEnv.getElementUtils();
+ types = processingEnv.getTypeUtils();
+
+ for (TypeElement annotation : annotations) {
+ Set extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(annotation);
+ annotatedElements.stream()
+ .map(TypeElement.class::cast)
+ .filter(this::testAnnotationElement)
+ .forEach(this::processTypeElement);
+ }
+
+ if (!annotations.isEmpty()) {
+ afterAnnotationProcessing();
+ }
+
+ return true;
+ }
+
+ protected boolean testAnnotationElement(TypeElement kind) { return true; }
+ protected void afterAnnotationProcessing() {}
+ protected abstract Generator[] getGenerators();
+
+ private void processTypeElement(TypeElement e) {
+ for (GenerateEntityImplementationsProcessor.Generator generator : getGenerators()) {
+ try {
+ generator.generate(e);
+ } catch (Exception ex) {
+ processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not generate implementation for class: " + ex, e);
+ }
+ }
+
+// methodsPerAttribute.entrySet().stream()
+// .sorted(Comparator.comparing(Map.Entry::getKey))
+// .forEach(me -> processingEnv.getMessager().printMessage(
+// Diagnostic.Kind.NOTE,
+// "** " + me.getKey() + ": " + me.getValue().stream().map(ExecutableElement::getSimpleName).sorted(Comparator.comparing(Object::toString)).collect(Collectors.joining(", ")))
+// );
+ }
+
+ protected Stream getAllAbstractMethods(TypeElement e) {
+ return elements.getAllMembers(e).stream()
+ .filter(el -> el.getKind() == ElementKind.METHOD)
+ .filter(el -> el.getModifiers().contains(Modifier.ABSTRACT))
+ .filter(ExecutableElement.class::isInstance)
+ .map(ExecutableElement.class::cast);
+ }
+
+ protected Map> methodsPerAttributeMapping(TypeElement e) {
+ Map> methodsPerAttribute = getAllAbstractMethods(e)
+ .filter(Util::isNotIgnored)
+ .filter(ee -> !(ee.getReceiverType() instanceof NoType && ee.getReceiverType().getKind() != TypeKind.NONE))
+ .collect(Collectors.toMap(this::determineAttributeFromMethodName, v -> new HashSet<>(Arrays.asList(v)), (a,b) -> { a.addAll(b); return a; }));
+
+ // Merge plurals with singulars
+ methodsPerAttribute.keySet().stream()
+ .filter(key -> methodsPerAttribute.containsKey(singularToPlural(key)))
+ .collect(Collectors.toSet())
+ .forEach(key -> {
+ HashSet removed = methodsPerAttribute.remove(key);
+ methodsPerAttribute.get(singularToPlural(key)).addAll(removed);
+ });
+
+ return methodsPerAttribute;
+ }
+
+ private static final Pattern BEAN_NAME = Pattern.compile("(get|set|is|delete|remove|add|update)([A-Z]\\S+)");
+ private static final Map FORBIDDEN_PREFIXES = new HashMap<>();
+ static {
+ FORBIDDEN_PREFIXES.put("delete", "remove");
+ }
+
+ protected String determineAttributeFromMethodName(ExecutableElement e) {
+ Name name = e.getSimpleName();
+ Matcher m = BEAN_NAME.matcher(name.toString());
+ if (m.matches()) {
+ String prefix = m.group(1);
+ if (FORBIDDEN_PREFIXES.containsKey(prefix)) {
+ processingEnv.getMessager().printMessage(
+ Diagnostic.Kind.ERROR,
+ "Forbidden prefix " + prefix + "... detected, use " + FORBIDDEN_PREFIXES.get(prefix) + "... instead", e
+ );
+ }
+ return m.group(2);
+ }
+ return null;
+ }
+
+ protected Stream fieldGetters(Map> methodsPerAttribute) {
+ return methodsPerAttribute.entrySet().stream()
+ .map(me -> FieldAccessorType.getMethod(GETTER, me.getValue(), me.getKey(), types, determineFieldType(me.getKey(), me.getValue())))
+ .filter(Optional::isPresent)
+ .map(Optional::get);
+ }
+
+ protected boolean isImmutableFinalType(TypeMirror fieldType) {
+ return isPrimitiveType(fieldType) || isBoxedPrimitiveType(fieldType) || Objects.equals("java.lang.String", fieldType.toString());
+ }
+
+ protected boolean isKnownCollectionOfImmutableFinalTypes(TypeMirror fieldType) {
+ List res = getGenericsDeclaration(fieldType);
+ return isCollection(fieldType) && res.stream().allMatch(this::isImmutableFinalType);
+ }
+
+ protected boolean isCollection(TypeMirror fieldType) {
+ TypeElement typeElement = elements.getTypeElement(types.erasure(fieldType).toString());
+ switch (typeElement.getQualifiedName().toString()) {
+ case "java.util.List":
+ case "java.util.Map":
+ case "java.util.Set":
+ case "java.util.Collection":
+ case "org.keycloak.common.util.MultivaluedHashMap":
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ protected String deepClone(TypeMirror fieldType, String parameterName) {
+ TypeElement typeElement = elements.getTypeElement(types.erasure(fieldType).toString());
+ if (isKnownCollectionOfImmutableFinalTypes(fieldType)) {
+ return parameterName + " == null ? null : " + interfaceToImplementation(typeElement, parameterName);
+ } else if (isMapType(typeElement)) {
+ List mapTypes = getGenericsDeclaration(fieldType);
+ boolean isKeyImmutable = isImmutableFinalType(mapTypes.get(0));
+ boolean isValueImmutable = isImmutableFinalType(mapTypes.get(1));
+
+ return parameterName + " == null ? null : " + parameterName + ".entrySet().stream().collect(" +
+ "java.util.stream.Collectors.toMap(" +
+ (isKeyImmutable ? "java.util.Map.Entry::getKey" : "entry -> " + deepClone(mapTypes.get(0), "entry.getKey()")) +
+ ", " +
+ (isValueImmutable ? "java.util.Map.Entry::getValue" : "entry -> " + deepClone(mapTypes.get(1), "entry.getValue()")) +
+ ", (o1, o2) -> o1" +
+ ", java.util.HashMap::new" +
+ "))";
+ }
+ return "deepClone(" + parameterName + ")";
+ }
+
+ protected boolean isPrimitiveType(TypeMirror fieldType) {
+ try {
+ types.getPrimitiveType(fieldType.getKind());
+ return true;
+ } catch (IllegalArgumentException ex) {
+ return false;
+ }
+ }
+
+ protected boolean isBoxedPrimitiveType(TypeMirror fieldType) {
+ try {
+ types.unboxedType(fieldType);
+ return true;
+ } catch (IllegalArgumentException ex) {
+ return false;
+ }
+ }
+
+ protected String interfaceToImplementation(TypeElement typeElement, String parameter) {
+ Name parameterTypeQN = typeElement.getQualifiedName();
+ switch (parameterTypeQN.toString()) {
+ case "java.util.List":
+ case "java.util.Collection":
+ return "new java.util.LinkedList<>(" + parameter + ")";
+ case "java.util.Map":
+ return "new java.util.HashMap<>(" + parameter + ")";
+ case "java.util.Set":
+ return "new java.util.HashSet<>(" + parameter + ")";
+ default:
+ processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not determine implementation for type " + typeElement, typeElement);
+ return "TODO()";
+ }
+ }
+
+ protected TypeMirror determineFieldType(String fieldName, HashSet methods) {
+ Pattern getter = Pattern.compile("(get|is)" + Pattern.quote(fieldName));
+ TypeMirror res = null;
+ for (ExecutableElement method : methods) {
+ if (getter.matcher(method.getSimpleName()).matches() && method.getParameters().isEmpty()) {
+ return method.getReturnType();
+ }
+ }
+ if (res == null) {
+ processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not determine return type for the field " + fieldName, methods.iterator().next());
+ }
+ return res;
+ }
+
+ protected static class NameFirstComparator implements Comparator {
+ protected static final Comparator ID_INSTANCE = new NameFirstComparator("id").thenComparing(Comparator.naturalOrder());
+ protected static final Comparator GET_ID_INSTANCE = new NameFirstComparator("getId").thenComparing(Comparator.naturalOrder());
+ private final String name;
+ public NameFirstComparator(String name) {
+ this.name = name;
+ }
+ @Override
+ public int compare(String o1, String o2) {
+ return Objects.equals(o1, o2)
+ ? 0
+ : name.equalsIgnoreCase(o1)
+ ? -1
+ : name.equalsIgnoreCase(o2)
+ ? 1
+ : 0;
+ }
+
+ }
+}
diff --git a/model/build-processor/src/main/java/org/keycloak/models/map/processor/FieldAccessorType.java b/model/build-processor/src/main/java/org/keycloak/models/map/processor/FieldAccessorType.java
index 90389d73ea3c..fcd4070a5650 100644
--- a/model/build-processor/src/main/java/org/keycloak/models/map/processor/FieldAccessorType.java
+++ b/model/build-processor/src/main/java/org/keycloak/models/map/processor/FieldAccessorType.java
@@ -26,6 +26,7 @@
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import static org.keycloak.models.map.processor.Util.getGenericsDeclaration;
+import static org.keycloak.models.map.processor.Util.pluralToSingular;
/**
*
@@ -52,7 +53,7 @@ public boolean is(ExecutableElement method, String fieldName, Types types, TypeM
COLLECTION_ADD {
@Override
public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) {
- String fieldNameSingular = fieldName.endsWith("s") ? fieldName.substring(0, fieldName.length() - 1) : fieldName;
+ String fieldNameSingular = pluralToSingular(fieldName);
String methodName = "add" + fieldNameSingular;
List res = getGenericsDeclaration(fieldType);
return Objects.equals(methodName, method.getSimpleName().toString())
@@ -63,7 +64,7 @@ public boolean is(ExecutableElement method, String fieldName, Types types, TypeM
COLLECTION_DELETE {
@Override
public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) {
- String fieldNameSingular = fieldName.endsWith("s") ? fieldName.substring(0, fieldName.length() - 1) : fieldName;
+ String fieldNameSingular = pluralToSingular(fieldName);
String removeFromCollection = "remove" + fieldNameSingular;
List res = getGenericsDeclaration(fieldType);
return Objects.equals(removeFromCollection, method.getSimpleName().toString())
@@ -74,7 +75,7 @@ public boolean is(ExecutableElement method, String fieldName, Types types, TypeM
MAP_ADD {
@Override
public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) {
- String fieldNameSingular = fieldName.endsWith("s") ? fieldName.substring(0, fieldName.length() - 1) : fieldName;
+ String fieldNameSingular = pluralToSingular(fieldName);
String methodName = "set" + fieldNameSingular;
List res = getGenericsDeclaration(fieldType);
return Objects.equals(methodName, method.getSimpleName().toString())
@@ -86,7 +87,7 @@ public boolean is(ExecutableElement method, String fieldName, Types types, TypeM
MAP_GET {
@Override
public boolean is(ExecutableElement method, String fieldName, Types types, TypeMirror fieldType) {
- String fieldNameSingular = fieldName.endsWith("s") ? fieldName.substring(0, fieldName.length() - 1) : fieldName;
+ String fieldNameSingular = pluralToSingular(fieldName);
String methodName = "get" + fieldNameSingular;
List res = getGenericsDeclaration(fieldType);
return Objects.equals(methodName, method.getSimpleName().toString())
diff --git a/model/build-processor/src/main/java/org/keycloak/models/map/processor/GenerateEntityImplementationsProcessor.java b/model/build-processor/src/main/java/org/keycloak/models/map/processor/GenerateEntityImplementationsProcessor.java
index a12747a6e190..bb6a40bc9936 100644
--- a/model/build-processor/src/main/java/org/keycloak/models/map/processor/GenerateEntityImplementationsProcessor.java
+++ b/model/build-processor/src/main/java/org/keycloak/models/map/processor/GenerateEntityImplementationsProcessor.java
@@ -17,20 +17,13 @@
package org.keycloak.models.map.processor;
import org.keycloak.models.map.annotations.GenerateEntityImplementations;
+
import java.io.IOException;
import java.io.PrintWriter;
-import java.util.Arrays;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
import java.util.stream.Collectors;
-import javax.annotation.processing.AbstractProcessor;
-import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
@@ -40,14 +33,11 @@
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
-import javax.lang.model.type.NoType;
import javax.lang.model.type.TypeMirror;
-import javax.lang.model.util.Elements;
-import javax.lang.model.util.Types;
+import javax.tools.Diagnostic;
import javax.tools.Diagnostic.Kind;
import javax.tools.JavaFileObject;
import static org.keycloak.models.map.processor.FieldAccessorType.*;
-import static org.keycloak.models.map.processor.Util.getGenericsDeclaration;
import static org.keycloak.models.map.processor.Util.isSetType;
import static org.keycloak.models.map.processor.Util.methodParameters;
import java.util.Collection;
@@ -67,48 +57,38 @@
*/
@SupportedAnnotationTypes("org.keycloak.models.map.annotations.GenerateEntityImplementations")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
-public class GenerateEntityImplementationsProcessor extends AbstractProcessor {
-
- private static interface Generator {
- void generate(TypeElement e, Map> methodsPerAttribute) throws IOException;
- }
+public class GenerateEntityImplementationsProcessor extends AbstractGenerateEntityImplementationsProcessor {
- private Elements elements;
- private Types types;
- private Collection cloners = new TreeSet<>();
+ private static final Collection autogenerated = new TreeSet<>();
private final Generator[] generators = new Generator[] {
new ClonerGenerator(),
new DelegateGenerator(),
new FieldsGenerator(),
- new ImplGenerator()
+ new FieldDelegateGenerator(),
+ new ImplGenerator(),
};
@Override
- public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
- elements = processingEnv.getElementUtils();
- types = processingEnv.getTypeUtils();
-
- for (TypeElement annotation : annotations) {
- Set extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(annotation);
- annotatedElements.stream()
- .map(TypeElement.class::cast)
- .forEach(this::processTypeElement);
- }
-
- if (! cloners.isEmpty() && ! annotations.isEmpty()) {
+ protected void afterAnnotationProcessing() {
+ if (! autogenerated.isEmpty()) {
try {
JavaFileObject file = processingEnv.getFiler().createSourceFile("org.keycloak.models.map.common.AutogeneratedCloners");
try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) {
pw.println("package org.keycloak.models.map.common;");
-
- pw.println("import org.keycloak.models.map.common.DeepCloner.Cloner;");
+
+ pw.println("import " + FQN_DEEP_CLONER + ".Cloner;");
+ pw.println("import " + FQN_DEEP_CLONER + ".DelegateCreator;");
+ pw.println("import " + FQN_DEEP_CLONER + ".EntityFieldDelegateCreator;");
pw.println("// DO NOT CHANGE THIS CLASS, IT IS GENERATED AUTOMATICALLY BY " + GenerateEntityImplementationsProcessor.class.getSimpleName());
pw.println("public final class AutogeneratedCloners {");
pw.println(" public static final java.util.Map, Cloner>> CLONERS_WITH_ID = new java.util.HashMap<>();");
pw.println(" public static final java.util.Map, Cloner>> CLONERS_WITHOUT_ID = new java.util.HashMap<>();");
+ pw.println(" public static final java.util.Map, DelegateCreator>> DELEGATE_CREATORS = new java.util.HashMap<>();");
+ pw.println(" public static final java.util.Map, EntityFieldDelegateCreator>> ENTITY_FIELD_DELEGATE_CREATORS = new java.util.HashMap<>();");
+ pw.println(" public static final java.util.Map, Object> EMPTY_INSTANCES = new java.util.HashMap<>();");
pw.println(" static {");
- cloners.forEach(pw::println);
+ autogenerated.forEach(pw::println);
pw.println(" }");
pw.println("}");
}
@@ -116,178 +96,32 @@ public boolean process(Set extends TypeElement> annotations, RoundEnvironment
Logger.getLogger(GenerateEntityImplementationsProcessor.class.getName()).log(Level.SEVERE, null, ex);
}
}
+ }
- return true;
+ @Override
+ protected Generator[] getGenerators() {
+ return this.generators;
}
- private static final String FQN_DEEP_CLONER = "org.keycloak.models.map.common.DeepCloner";
- private void processTypeElement(TypeElement e) {
+ @Override
+ protected boolean testAnnotationElement(TypeElement e) {
if (e.getKind() != ElementKind.INTERFACE) {
- processingEnv.getMessager().printMessage(Kind.ERROR, "Annotation @GenerateEntityImplementations is only applicable to an interface", e);
- return;
- }
-
- // Find all properties
- final List extends Element> allMembers = elements.getAllMembers(e);
- Map> methodsPerAttribute = allMembers.stream()
- .filter(el -> el.getKind() == ElementKind.METHOD)
- .filter(el -> el.getModifiers().contains(Modifier.ABSTRACT))
- .filter(Util::isNotIgnored)
- .filter(ExecutableElement.class::isInstance)
- .map(ExecutableElement.class::cast)
- .filter(ee -> ! (ee.getReceiverType() instanceof NoType))
- .collect(Collectors.toMap(this::determineAttributeFromMethodName, v -> new HashSet(Arrays.asList(v)), (a,b) -> { a.addAll(b); return a; }));
-
- // Merge plurals with singulars
- methodsPerAttribute.keySet().stream()
- .filter(key -> methodsPerAttribute.containsKey(key + "s"))
- .collect(Collectors.toSet())
- .forEach(key -> {
- HashSet removed = methodsPerAttribute.remove(key);
- methodsPerAttribute.get(key + "s").addAll(removed);
- });
-
- for (Generator generator : this.generators) {
- try {
- generator.generate(e, methodsPerAttribute);
- } catch (Exception ex) {
- processingEnv.getMessager().printMessage(Kind.ERROR, "Could not generate implementation for class: " + ex, e);
- }
+ processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Annotation @GenerateEntityImplementations is only applicable to an interface", e);
+ return false;
}
-// methodsPerAttribute.entrySet().stream()
-// .sorted(Comparator.comparing(Map.Entry::getKey))
-// .forEach(me -> processingEnv.getMessager().printMessage(
-// Diagnostic.Kind.NOTE,
-// "** " + me.getKey() + ": " + me.getValue().stream().map(ExecutableElement::getSimpleName).sorted(Comparator.comparing(Object::toString)).collect(Collectors.joining(", ")))
-// );
- }
-
- private static final Pattern BEAN_NAME = Pattern.compile("(get|set|is|delete|remove|add|update)([A-Z]\\S+)");
- private static final Map FORBIDDEN_PREFIXES = new HashMap<>();
- static {
- FORBIDDEN_PREFIXES.put("delete", "remove");
- }
-
- private String determineAttributeFromMethodName(ExecutableElement e) {
- Name name = e.getSimpleName();
- Matcher m = BEAN_NAME.matcher(name.toString());
- if (m.matches()) {
- String prefix = m.group(1);
- if (FORBIDDEN_PREFIXES.containsKey(prefix)) {
- processingEnv.getMessager().printMessage(
- Kind.ERROR,
- "Forbidden prefix " + prefix + "... detected, use " + FORBIDDEN_PREFIXES.get(prefix) + "... instead", e
- );
- }
- return m.group(2);
- }
- return null;
+ return true;
}
protected static String toEnumConstant(String key) {
return key.replaceAll("([a-z])([A-Z])", "$1_$2").toUpperCase();
}
- private TypeMirror determineFieldType(String fieldName, HashSet methods) {
- Pattern getter = Pattern.compile("(get|is)" + Pattern.quote(fieldName));
- TypeMirror res = null;
- for (ExecutableElement method : methods) {
- if (getter.matcher(method.getSimpleName()).matches() && method.getParameters().isEmpty()) {
- return method.getReturnType();
- }
- }
- if (res == null) {
- processingEnv.getMessager().printMessage(Kind.ERROR, "Could not determine return type for the field " + fieldName, methods.iterator().next());
- }
- return res;
- }
-
- private boolean isImmutableFinalType(TypeMirror fieldType) {
- return isPrimitiveType(fieldType) || isBoxedPrimitiveType(fieldType) || Objects.equals("java.lang.String", fieldType.toString());
- }
-
- private boolean isKnownCollectionOfImmutableFinalTypes(TypeMirror fieldType) {
- TypeElement typeElement = elements.getTypeElement(types.erasure(fieldType).toString());
- switch (typeElement.getQualifiedName().toString()) {
- case "java.util.List":
- case "java.util.Map":
- case "java.util.Set":
- case "java.util.Collection":
- case "org.keycloak.common.util.MultivaluedHashMap":
- List res = getGenericsDeclaration(fieldType);
- return res.stream().allMatch(tm -> isImmutableFinalType(tm) || isKnownCollectionOfImmutableFinalTypes(tm));
- default:
- return false;
- }
- }
-
- private String deepClone(TypeMirror fieldType, String parameterName) {
- if (isKnownCollectionOfImmutableFinalTypes(fieldType)) {
- TypeElement typeElement = elements.getTypeElement(types.erasure(fieldType).toString());
- return parameterName + " == null ? null : " + interfaceToImplementation(typeElement, parameterName);
- } else {
- return "deepClone(" + parameterName + ")";
- }
- }
-
- private boolean isPrimitiveType(TypeMirror fieldType) {
- try {
- types.getPrimitiveType(fieldType.getKind());
- return true;
- } catch (IllegalArgumentException ex) {
- return false;
- }
- }
-
- private boolean isBoxedPrimitiveType(TypeMirror fieldType) {
- try {
- types.unboxedType(fieldType);
- return true;
- } catch (IllegalArgumentException ex) {
- return false;
- }
- }
-
- private String interfaceToImplementation(TypeElement typeElement, String parameter) {
- Name parameterTypeQN = typeElement.getQualifiedName();
- switch (parameterTypeQN.toString()) {
- case "java.util.List":
- case "java.util.Collection":
- return "new java.util.LinkedList<>(" + parameter + ")";
- case "java.util.Map":
- return "new java.util.HashMap<>(" + parameter + ")";
- case "java.util.Set":
- return "new java.util.HashSet<>(" + parameter + ")";
- default:
- processingEnv.getMessager().printMessage(Kind.ERROR, "Could not determine implementation for type " + typeElement, typeElement);
- return "TODO()";
- }
- }
-
- private static class NameFirstComparator implements Comparator {
- private static final Comparator ID_INSTANCE = new NameFirstComparator("id").thenComparing(Comparator.naturalOrder());
- private static final Comparator GET_ID_INSTANCE = new NameFirstComparator("getId").thenComparing(Comparator.naturalOrder());
- private final String name;
- public NameFirstComparator(String name) {
- this.name = name;
- }
- @Override
- public int compare(String o1, String o2) {
- return Objects.equals(o1, o2)
- ? 0
- : name.equalsIgnoreCase(o1)
- ? -1
- : name.equalsIgnoreCase(o2)
- ? 1
- : 0;
- }
-
- }
-
private class FieldsGenerator implements Generator {
+
@Override
- public void generate(TypeElement e, Map> methodsPerAttribute) throws IOException {
+ public void generate(TypeElement e) throws IOException {
+ Map> methodsPerAttribute = methodsPerAttributeMapping(e);
String className = e.getQualifiedName().toString();
String packageName = null;
int lastDot = className.lastIndexOf('.');
@@ -305,26 +139,117 @@ public void generate(TypeElement e, Map> meth
pw.println("package " + packageName + ";");
}
- pw.println("public enum " + mapSimpleFieldsClassName + " {");
+ pw.println("public enum " + mapSimpleFieldsClassName + " implements " + FQN_ENTITY_FIELD + "<" + className + "> {");
methodsPerAttribute.keySet().stream()
.sorted(NameFirstComparator.ID_INSTANCE)
- .map(GenerateEntityImplementationsProcessor::toEnumConstant)
- .forEach(key -> pw.println(" " + key + ","));
+ .forEach(key -> {
+ pw.println(" " + toEnumConstant(key) + " {");
+ printEntityFieldMethods(pw, className, key, methodsPerAttribute.get(key));
+ pw.println(" },");
+ });
pw.println("}");
}
}
+
+ private void printEntityFieldMethods(PrintWriter pw, String className, String fieldName, HashSet methods) {
+ TypeMirror fieldType = determineFieldType(fieldName, methods);
+ pw.println(" public static final String FIELD_NAME = \"" + fieldName + "\";");
+ pw.println(" public static final String FIELD_NAME_DASHED = \"" + fieldName.replaceAll("([^_A-Z])([A-Z])", "$1-$2").toLowerCase() + "\";");
+ pw.println(" @SuppressWarnings(\"unchecked\") @Override public Class> getFieldClass() {");
+ pw.println(" return " + types.erasure(fieldType) + ".class;");
+ pw.println(" }");
+ pw.println(" @Override public String getName() {");
+ pw.println(" return FIELD_NAME;");
+ pw.println(" }");
+ pw.println(" @Override public String getNameDashed() {");
+ pw.println(" return FIELD_NAME_DASHED;");
+ pw.println(" }");
+
+ FieldAccessorType.getMethod(FieldAccessorType.COLLECTION_ADD, methods, fieldName, types, fieldType).ifPresent(method -> {
+ TypeMirror firstParameterType = method.getParameters().get(0).asType();
+ pw.println(" @SuppressWarnings(\"unchecked\") @Override public Class> getCollectionElementClass() {");
+ pw.println(" return " + types.erasure(firstParameterType) + ".class;");
+ pw.println(" }");
+ });
+
+ FieldAccessorType.getMethod(FieldAccessorType.MAP_ADD, methods, fieldName, types, fieldType).ifPresent(method -> {
+ TypeMirror firstParameterType = method.getParameters().get(0).asType();
+ TypeMirror secondParameterType = method.getParameters().get(1).asType();
+ pw.println(" @SuppressWarnings(\"unchecked\") @Override public Class> getMapKeyClass() {");
+ pw.println(" return " + types.erasure(firstParameterType) + ".class;");
+ pw.println(" }");
+ pw.println(" @SuppressWarnings(\"unchecked\") @Override public Class> getMapValueClass() {");
+ pw.println(" return " + types.erasure(secondParameterType) + ".class;");
+ pw.println(" }");
+ });
+
+ for (ExecutableElement ee : methods) {
+ FieldAccessorType fat = FieldAccessorType.determineType(ee, fieldName, types, fieldType);
+ printMethodBody(pw, fat, ee, className, fieldType);
+ }
+ }
+
+ private void printMethodBody(PrintWriter pw, FieldAccessorType accessorType, ExecutableElement method, String className, TypeMirror fieldType) {
+ TypeMirror firstParameterType = method.getParameters().isEmpty()
+ ? types.getNullType()
+ : method.getParameters().get(0).asType();
+
+ switch (accessorType) {
+ case GETTER:
+ pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " get(" + className + " e) {");
+ pw.println(" return (" + fieldType + ") e." + method.getSimpleName() + "();");
+ pw.println(" }");
+ return;
+ case SETTER:
+ pw.println(" @SuppressWarnings(\"unchecked\") @Override public void set(" + className + " e, T value) {");
+ pw.println(" e." + method.getSimpleName() + "((" + firstParameterType + ") value);");
+ pw.println(" }");
+ return;
+ case COLLECTION_ADD:
+ pw.println(" @SuppressWarnings(\"unchecked\") @Override public void collectionAdd(" + className + " e, T value) {");
+ pw.println(" e." + method.getSimpleName() + "((" + firstParameterType + ") value);");
+ pw.println(" }");
+ return;
+ case COLLECTION_DELETE:
+ String returnType = method.getReturnType().getKind() == TypeKind.VOID ? "Void" : method.getReturnType().toString();
+ TypeElement fieldTypeElement = elements.getTypeElement(types.erasure(fieldType).toString());
+ if (Util.isMapType(fieldTypeElement)) {
+ pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + returnType + " mapRemove(" + className + " e, K p0) {");
+ } else {
+ pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + returnType + " collectionRemove(" + className + " e, T p0) {");
+ }
+ if (method.getReturnType().getKind() == TypeKind.VOID) {
+ pw.println(" e." + method.getSimpleName() + "((" + firstParameterType + ") p0); return null;");
+ } else {
+ pw.println(" return (" + method.getReturnType() + ") e." + method.getSimpleName() + "((" + firstParameterType + ") p0);");
+ }
+ pw.println(" }");
+ return;
+ case MAP_ADD:
+ TypeMirror secondParameterType = method.getParameters().get(1).asType();
+ pw.println(" @SuppressWarnings(\"unchecked\") @Override public void mapPut(" + className + " e, K key, T value) {");
+ pw.println(" e." + method.getSimpleName() + "((" + firstParameterType + ") key, (" + secondParameterType + ") value);");
+ pw.println(" }");
+ return;
+ case MAP_GET:
+ pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " mapGet(" + className + " e, K key) {");
+ pw.println(" return (" + method.getReturnType() + ") e." + method.getSimpleName() + "((" + firstParameterType + ") key);");
+ pw.println(" }");
+ }
+ }
}
private class ImplGenerator implements Generator {
@Override
- public void generate(TypeElement e, Map> methodsPerAttribute) throws IOException {
+ public void generate(TypeElement e) throws IOException {
+ Map> methodsPerAttribute = methodsPerAttributeMapping(e);
GenerateEntityImplementations an = e.getAnnotation(GenerateEntityImplementations.class);
TypeElement parentTypeElement = elements.getTypeElement((an.inherits() == null || an.inherits().isEmpty()) ? "void" : an.inherits());
if (parentTypeElement == null) {
return;
}
- final List extends Element> allMembers = elements.getAllMembers(parentTypeElement);
+ final List extends Element> allParentMembers = elements.getAllMembers(parentTypeElement);
String className = e.getQualifiedName().toString();
String packageName = null;
int lastDot = className.lastIndexOf('.');
@@ -335,11 +260,12 @@ public void generate(TypeElement e, Map> meth
String simpleClassName = className.substring(lastDot + 1);
String mapImplClassName = className + "Impl";
String mapSimpleClassName = simpleClassName + "Impl";
- boolean hasId = methodsPerAttribute.containsKey("Id") || allMembers.stream().anyMatch(el -> "getId".equals(el.getSimpleName().toString()));
- boolean hasDeepClone = allMembers.stream().filter(el -> el.getKind() == ElementKind.METHOD).anyMatch(el -> "deepClone".equals(el.getSimpleName().toString()));
+ boolean hasId = methodsPerAttribute.containsKey("Id") || allParentMembers.stream().anyMatch(el -> "getId".equals(el.getSimpleName().toString()));
+ boolean hasDeepClone = allParentMembers.stream().filter(el -> el.getKind() == ElementKind.METHOD).anyMatch(el -> "deepClone".equals(el.getSimpleName().toString()));
boolean needsDeepClone = fieldGetters(methodsPerAttribute)
.map(ExecutableElement::getReturnType)
.anyMatch(fieldType -> ! isKnownCollectionOfImmutableFinalTypes(fieldType) && ! isImmutableFinalType(fieldType));
+ boolean usingGeneratedCloner = ! hasDeepClone && needsDeepClone;
JavaFileObject file = processingEnv.getFiler().createSourceFile(mapImplClassName);
try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) {
@@ -353,33 +279,31 @@ public void generate(TypeElement e, Map> meth
pw.println("public class " + mapSimpleClassName + (an.inherits().isEmpty() ? "" : " extends " + an.inherits()) + " implements " + className + " {");
// Constructors
- allMembers.stream()
+ allParentMembers.stream()
.filter(ExecutableElement.class::isInstance)
.map(ExecutableElement.class::cast)
.filter((ExecutableElement ee) -> ee.getKind() == ElementKind.CONSTRUCTOR)
.forEach((ExecutableElement ee) -> {
- if (hasDeepClone || ! needsDeepClone) {
- pw.println(" "
- + ee.getModifiers().stream().map(Object::toString).collect(Collectors.joining(" "))
- + " " + mapSimpleClassName + "(" + methodParameters(ee.getParameters()) + ") { super(" + ee.getParameters() + "); }"
- );
- } else if (needsDeepClone) {
+ // Create constructor and initialize cloner to DUMB_CLONER if necessary
+ if (usingGeneratedCloner) {
pw.println(" /**");
pw.println(" * @deprecated This constructor uses a {@link DeepCloner#DUMB_CLONER} that does not clone anything. Use {@link #" + mapSimpleClassName + "(DeepCloner)} variant instead");
pw.println(" */");
- pw.println(" "
- + ee.getModifiers().stream().map(Object::toString).collect(Collectors.joining(" "))
- + " "
- + mapSimpleClassName + "(" + methodParameters(ee.getParameters()) + ") { this(DeepCloner.DUMB_CLONER" + (ee.getParameters().isEmpty() ? "" : ", ") + ee.getParameters() + "); }"
- );
- pw.println(" "
- + ee.getModifiers().stream().map(Object::toString).collect(Collectors.joining(" "))
- + " "
- + mapSimpleClassName + "(DeepCloner cloner" + (ee.getParameters().isEmpty() ? "" : ", ") + methodParameters(ee.getParameters()) + ") { super(" + ee.getParameters() + "); this.cloner = cloner; }"
- );
}
+ pw.println(" "
+ + ee.getModifiers().stream().map(Object::toString).collect(Collectors.joining(" "))
+ + " " + mapSimpleClassName + "(" + methodParameters(ee.getParameters()) + ") {"
+ );
+ pw.println(" super(" + ee.getParameters() + ");");
+ if (usingGeneratedCloner) pw.println(" this.cloner = DeepCloner.DUMB_CLONER;");
+ pw.println(" }");
});
+ pw.println(" "
+ + "public "
+ + mapSimpleClassName + "(DeepCloner cloner) { super(); " + (!usingGeneratedCloner ? "" : "this.cloner = cloner;") + "}"
+ );
+
// equals, hashCode, toString
pw.println(" @Override public boolean equals(Object o) {");
pw.println(" if (o == this) return true; ");
@@ -395,7 +319,7 @@ public void generate(TypeElement e, Map> meth
+ ";");
pw.println(" }");
pw.println(" @Override public int hashCode() {");
- pw.println(" return "
+ pw.println(" return "
+ (hasId
? "(getId() == null ? super.hashCode() : getId().hashCode())"
: "Objects.hash("
@@ -414,7 +338,7 @@ public void generate(TypeElement e, Map> meth
pw.println(" }");
// deepClone
- if (! hasDeepClone && needsDeepClone) {
+ if (usingGeneratedCloner) {
pw.println(" private final DeepCloner cloner;");
pw.println(" public V deepClone(V obj) {");
pw.println(" return cloner.from(obj);");
@@ -434,31 +358,57 @@ public void generate(TypeElement e, Map> meth
for (ExecutableElement method : methods) {
FieldAccessorType fat = FieldAccessorType.determineType(method, me.getKey(), types, fieldType);
- Optional parentMethod = allMembers.stream()
- .filter(ExecutableElement.class::isInstance)
- .map(ExecutableElement.class::cast)
- .filter(ee -> Objects.equals(ee.toString(), method.toString()))
- .filter((ExecutableElement ee) -> ! ee.getModifiers().contains(Modifier.ABSTRACT))
- .findAny();
+ Optional parentMethod = Util.findParentMethodImplementation(allParentMembers, method);
if (parentMethod.isPresent()) {
processingEnv.getMessager().printMessage(Kind.OTHER, "Method " + method + " is declared in a parent class.", method);
- } else if (fat != FieldAccessorType.UNKNOWN && ! printMethodBody(pw, fat, method, "f" + me.getKey(), fieldType)) {
+ } else if (fat == FieldAccessorType.UNKNOWN || ! printMethodBody(pw, fat, method, "f" + me.getKey(), fieldType)) {
processingEnv.getMessager().printMessage(Kind.WARNING, "Could not determine desired semantics of method from its signature", method);
}
}
});
+
+ // Read-only class overrides setters to be no-op
+ pw.println(" public static class Empty " + (an.inherits().isEmpty() ? "" : " extends " + an.inherits()) + " implements " + className + " {");
+ pw.println(" public static final Empty INSTANCE = new Empty();");
+ methodsPerAttribute.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey, NameFirstComparator.ID_INSTANCE))
+ .map(Map.Entry::getValue)
+ .flatMap(Collection::stream)
+ .forEach(ee -> {
+ pw.println(" @Override "
+ + ee.getModifiers().stream().filter(m -> m != Modifier.ABSTRACT).map(Object::toString).collect(Collectors.joining(" "))
+ + " " + ee.getReturnType()
+ + " " + ee.getSimpleName()
+ + "(" + methodParameters(ee.getParameters()) + ") {");
+ if (ee.getReturnType().getKind() == TypeKind.VOID) {
+ pw.println(" }");
+ } else {
+ pw.println(" return null;");
+ pw.println(" }");
+ }
+ });
+ elements.getAllMembers(e).stream()
+ .filter(ee -> ee.getSimpleName().contentEquals("isUpdated"))
+ .filter(ExecutableElement.class::isInstance)
+ .map(ExecutableElement.class::cast)
+ .filter(ee -> ee.getReturnType().getKind() == TypeKind.BOOLEAN)
+ .forEach(ee -> {
+ pw.println(" @Override "
+ + ee.getModifiers().stream().filter(m -> m != Modifier.ABSTRACT).map(Object::toString).collect(Collectors.joining(" "))
+ + " " + ee.getReturnType()
+ + " " + ee.getSimpleName()
+ + "(" + methodParameters(ee.getParameters()) + ") {");
+ pw.println(" return false;");
+ pw.println(" }");
+ });
+ pw.println(" }");
+
+ autogenerated.add(" EMPTY_INSTANCES.put(" + className + ".class, " + mapImplClassName + ".Empty.INSTANCE);");
+
pw.println("}");
}
}
- private Stream fieldGetters(Map> methodsPerAttribute) {
- return methodsPerAttribute.entrySet().stream()
- .map(me -> FieldAccessorType.getMethod(GETTER, me.getValue(), me.getKey(), types, determineFieldType(me.getKey(), me.getValue())))
- .filter(Optional::isPresent)
- .map(Optional::get);
- }
-
private boolean printMethodBody(PrintWriter pw, FieldAccessorType accessorType, ExecutableElement method, String fieldName, TypeMirror fieldType) {
TypeMirror firstParameterType = method.getParameters().isEmpty()
? types.getNullType()
@@ -495,10 +445,12 @@ private boolean printMethodBody(PrintWriter pw, FieldAccessorType accessorType,
pw.println(" }");
return true;
case COLLECTION_DELETE:
+ boolean needsReturn = method.getReturnType().getKind() != TypeKind.VOID;
pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
- pw.println(" if (" + fieldName + " == null) { return; }");
+ pw.println(" if (" + fieldName + " == null) { return" + (needsReturn ? " false" : "") + "; }");
pw.println(" boolean removed = " + fieldName + ".remove(p0)" + ("java.util.Map".equals(typeElement.getQualifiedName().toString()) ? " != null" : "") + ";");
pw.println(" updated |= removed;");
+ if (needsReturn) pw.println(" return removed;");
pw.println(" }");
return true;
case MAP_ADD:
@@ -523,9 +475,126 @@ private boolean printMethodBody(PrintWriter pw, FieldAccessorType accessorType,
}
}
+ private class FieldDelegateGenerator implements Generator {
+
+ @Override
+ public void generate(TypeElement e) throws IOException {
+ Map> methodsPerAttribute = methodsPerAttributeMapping(e);
+ String className = e.getQualifiedName().toString();
+ String packageName = null;
+ int lastDot = className.lastIndexOf('.');
+ if (lastDot > 0) {
+ packageName = className.substring(0, lastDot);
+ }
+
+ String simpleClassName = className.substring(lastDot + 1);
+ String mapClassName = className + "FieldDelegate";
+ String mapSimpleClassName = simpleClassName + "FieldDelegate";
+ String fieldsClassName = className + "Fields";
+
+ GenerateEntityImplementations an = e.getAnnotation(GenerateEntityImplementations.class);
+ TypeElement parentTypeElement = elements.getTypeElement((an.inherits() == null || an.inherits().isEmpty()) ? "void" : an.inherits());
+ if (parentTypeElement == null) {
+ return;
+ }
+
+ JavaFileObject file = processingEnv.getFiler().createSourceFile(mapClassName);
+ IdentityHashMap m2field = new IdentityHashMap<>();
+ methodsPerAttribute.forEach((f, s) -> s.forEach(m -> m2field.put(m, f))); // Create reverse map
+ try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) {
+ if (packageName != null) {
+ pw.println("package " + packageName + ";");
+ }
+
+ pw.println("public class " + mapSimpleClassName + (an.inherits().isEmpty() ? "" : " extends " + an.inherits()) + " implements " + className + ", " + FQN_HAS_ENTITY_FIELD_DELEGATE + "<" + className + ">" + " {");
+ pw.println(" private final " + FQN_ENTITY_FIELD_DELEGATE + "<" + className + "> entityFieldDelegate;");
+ pw.println(" public " + mapSimpleClassName + "(" + FQN_ENTITY_FIELD_DELEGATE + "<" + className + "> entityFieldDelegate) {");
+ pw.println(" this.entityFieldDelegate = entityFieldDelegate;");
+ pw.println(" }");
+ pw.println(" public " + FQN_ENTITY_FIELD_DELEGATE + "<" + className + "> getEntityFieldDelegate() {");
+ pw.println(" return this.entityFieldDelegate;");
+ pw.println(" }");
+
+ getAllAbstractMethods(e)
+ .forEach(ee -> {
+ String originalField = m2field.get(ee);
+ if (originalField == null) {
+ return;
+ }
+ TypeMirror fieldType = determineFieldType(originalField, methodsPerAttribute.get(originalField));
+ String field = fieldsClassName + "." + toEnumConstant(originalField);
+
+ if (ee.getReturnType().getKind() == TypeKind.BOOLEAN && "isUpdated".equals(ee.getSimpleName().toString())) {
+ pw.println(" return entityFieldDelegate.isUpdated();");
+ pw.println(" }");
+ } else if (ee.getReturnType().getKind() == TypeKind.VOID && "clearUpdatedFlag".equals(ee.getSimpleName().toString())) {
+ pw.println(" return entityFieldDelegate.clearUpdatedFlag();");
+ pw.println(" }");
+ } else {
+ FieldAccessorType fat = FieldAccessorType.determineType(ee, originalField, types, fieldType);
+ printMethodBody(pw, fat, ee, field, fieldType);
+ }
+ });
+
+ autogenerated.add(" ENTITY_FIELD_DELEGATE_CREATORS.put(" + className + ".class, (EntityFieldDelegateCreator<" + className + ">) " + mapClassName + "::new);");
+
+ pw.println("}");
+ }
+ }
+
+ private boolean printMethodBody(PrintWriter pw, FieldAccessorType accessorType, ExecutableElement method, String fieldName, TypeMirror fieldType) {
+ TypeMirror firstParameterType = method.getParameters().isEmpty()
+ ? types.getNullType()
+ : method.getParameters().get(0).asType();
+
+ switch (accessorType) {
+ case GETTER:
+ pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method + " {");
+ pw.println(" return (" + fieldType + ") entityFieldDelegate.get(" + fieldName + ");");
+ pw.println(" }");
+ return true;
+ case SETTER:
+ pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
+ pw.println(" entityFieldDelegate.set(" + fieldName + ", p0);");
+ pw.println(" }");
+ return true;
+ case COLLECTION_ADD:
+ pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
+ pw.println(" entityFieldDelegate.collectionAdd(" + fieldName + ", p0);");
+ pw.println(" }");
+ return true;
+ case COLLECTION_DELETE:
+ pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
+ TypeElement fieldTypeElement = elements.getTypeElement(types.erasure(fieldType).toString());
+ String removeMethod = Util.isMapType(fieldTypeElement) ? "mapRemove" : "collectionRemove";
+ if (method.getReturnType().getKind() == TypeKind.VOID) {
+ pw.println(" entityFieldDelegate." + removeMethod + "(" + fieldName + ", p0);");
+ } else {
+ pw.println(" return (" + method.getReturnType() + ") entityFieldDelegate." + removeMethod + "(" + fieldName + ", p0);");
+ }
+ pw.println(" }");
+ return true;
+ case MAP_ADD:
+ TypeMirror secondParameterType = method.getParameters().get(1).asType();
+ pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0, " + secondParameterType + " p1) {");
+ pw.println(" entityFieldDelegate.mapPut(" + fieldName + ", p0, p1);");
+ pw.println(" }");
+ return true;
+ case MAP_GET:
+ pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
+ pw.println(" return (" + method.getReturnType() + ") entityFieldDelegate.mapGet(" + fieldName + ", p0);");
+ pw.println(" }");
+ return true;
+ }
+
+ return false;
+ }
+ }
+
private class DelegateGenerator implements Generator {
@Override
- public void generate(TypeElement e, Map> methodsPerAttribute) throws IOException {
+ public void generate(TypeElement e) throws IOException {
+ Map> methodsPerAttribute = methodsPerAttributeMapping(e);
String className = e.getQualifiedName().toString();
String packageName = null;
int lastDot = className.lastIndexOf('.');
@@ -543,7 +612,6 @@ public void generate(TypeElement e, Map> meth
if (parentTypeElement == null) {
return;
}
- final List extends Element> allMembers = elements.getAllMembers(e);
JavaFileObject file = processingEnv.getFiler().createSourceFile(mapClassName);
IdentityHashMap m2field = new IdentityHashMap<>();
@@ -559,11 +627,7 @@ public void generate(TypeElement e, Map> meth
pw.println(" this.delegateProvider = delegateProvider;");
pw.println(" }");
- allMembers.stream()
- .filter(m -> m.getKind() == ElementKind.METHOD)
- .filter(ExecutableElement.class::isInstance)
- .map(ExecutableElement.class::cast)
- .filter(ee -> ee.getModifiers().contains(Modifier.ABSTRACT))
+ getAllAbstractMethods(e)
.forEach(ee -> {
pw.println(" @Override "
+ ee.getModifiers().stream().filter(m -> m != Modifier.ABSTRACT).map(Object::toString).collect(Collectors.joining(" "))
@@ -575,11 +639,15 @@ public void generate(TypeElement e, Map> meth
if (ee.getReturnType().getKind() == TypeKind.BOOLEAN && "isUpdated".equals(ee.getSimpleName().toString())) {
pw.println(" return delegateProvider.isUpdated();");
} else if (ee.getReturnType().getKind() == TypeKind.VOID) { // write operation
- pw.println(" delegateProvider.getDelegate(false, " + field + ")." + ee.getSimpleName() + "("
+ pw.println(" delegateProvider.getDelegate(false, "
+ + Stream.concat(Stream.of(field), ee.getParameters().stream().map(VariableElement::getSimpleName)).collect(Collectors.joining(", "))
+ + ")." + ee.getSimpleName() + "("
+ ee.getParameters().stream().map(VariableElement::getSimpleName).collect(Collectors.joining(", "))
+ ");");
} else {
- pw.println(" return delegateProvider.getDelegate(true, " + field + ")." + ee.getSimpleName() + "("
+ pw.println(" return delegateProvider.getDelegate(true, "
+ + Stream.concat(Stream.of(field), ee.getParameters().stream().map(VariableElement::getSimpleName)).collect(Collectors.joining(", "))
+ + ")." + ee.getSimpleName() + "("
+ ee.getParameters().stream().map(VariableElement::getSimpleName).collect(Collectors.joining(", "))
+ ");");
}
@@ -587,6 +655,8 @@ public void generate(TypeElement e, Map> meth
});
pw.println("}");
+
+ autogenerated.add(" DELEGATE_CREATORS.put(" + className + ".class, (DelegateCreator<" + className + ">) " + mapClassName + "::new);");
}
}
}
@@ -594,7 +664,8 @@ public void generate(TypeElement e, Map> meth
private class ClonerGenerator implements Generator {
@Override
- public void generate(TypeElement e, Map> methodsPerAttribute) throws IOException {
+ public void generate(TypeElement e) throws IOException {
+ Map> methodsPerAttribute = methodsPerAttributeMapping(e);
String className = e.getQualifiedName().toString();
String packageName = null;
int lastDot = className.lastIndexOf('.');
@@ -635,7 +706,7 @@ public void println(String x) {
pw.println(" return target;");
pw.println(" }");
- cloners.add(" CLONERS_WITH_ID.put(" + className + ".class, (Cloner<" + className + ">) " + clonerImplClassName + "::deepClone);");
+ autogenerated.add(" CLONERS_WITH_ID.put(" + className + ".class, (Cloner<" + className + ">) " + clonerImplClassName + "::deepClone);");
if (methodsPerAttribute.containsKey("Id")) {
pw.println(" public static " + className + " deepCloneNoId(" + className + " original, " + className + " target) {");
@@ -654,7 +725,7 @@ public void println(String x) {
pw.println(" return target;");
pw.println(" }");
- cloners.add(" CLONERS_WITHOUT_ID.put(" + className + ".class, (Cloner<" + className + ">) " + clonerImplClassName + "::deepCloneNoId);");
+ autogenerated.add(" CLONERS_WITHOUT_ID.put(" + className + ".class, (Cloner<" + className + ">) " + clonerImplClassName + "::deepCloneNoId);");
}
pw.println("}");
}
diff --git a/model/build-processor/src/main/java/org/keycloak/models/map/processor/GenerateHotRodEntityImplementationsProcessor.java b/model/build-processor/src/main/java/org/keycloak/models/map/processor/GenerateHotRodEntityImplementationsProcessor.java
new file mode 100644
index 000000000000..2ebb8e3abb53
--- /dev/null
+++ b/model/build-processor/src/main/java/org/keycloak/models/map/processor/GenerateHotRodEntityImplementationsProcessor.java
@@ -0,0 +1,507 @@
+/*
+ * Copyright 2021 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.map.processor;
+
+import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
+import org.keycloak.models.map.exceptions.CannotMigrateTypeException;
+
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.TypeMirror;
+import javax.tools.Diagnostic;
+import javax.tools.JavaFileObject;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.OptionalInt;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import static org.keycloak.models.map.processor.Util.getGenericsDeclaration;
+import static org.keycloak.models.map.processor.Util.isMapType;
+import static org.keycloak.models.map.processor.Util.isSetType;
+import static org.keycloak.models.map.processor.Util.methodParameters;
+
+@SupportedAnnotationTypes("org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation")
+@SupportedSourceVersion(SourceVersion.RELEASE_8)
+public class GenerateHotRodEntityImplementationsProcessor extends AbstractGenerateEntityImplementationsProcessor {
+
+
+ @Override
+ protected Generator[] getGenerators() {
+ return new Generator[] { new HotRodGettersAndSettersDelegateGenerator() };
+ }
+
+
+ private class HotRodGettersAndSettersDelegateGenerator implements Generator {
+
+ private static final String ENTITY_VARIABLE = "hotRodEntity";
+ private String hotRodSimpleClassName;
+ private TypeElement generalHotRodDelegate;
+ private TypeElement abstractEntity;
+ private TypeElement abstractHotRodEntity;
+ private TypeElement hotRodUtils;
+
+ @Override
+ public void generate(TypeElement e) throws IOException {
+ GenerateHotRodEntityImplementation hotRodAnnotation = e.getAnnotation(GenerateHotRodEntityImplementation.class);
+ String interfaceClass = hotRodAnnotation.implementInterface();
+ if (interfaceClass == null || interfaceClass.isEmpty()) return;
+ TypeElement parentClassElement = elements.getTypeElement(hotRodAnnotation.inherits());
+ if (parentClassElement == null) return;
+
+ TypeElement parentInterfaceElement = elements.getTypeElement(interfaceClass);
+ if (parentInterfaceElement == null) return;
+ Map> methodsPerAttribute = methodsPerAttributeMapping(parentInterfaceElement);
+
+
+ final List extends Element> allMembers = elements.getAllMembers(parentClassElement);
+ String className = e.getQualifiedName().toString();
+
+ String packageName = null;
+ int lastDot = className.lastIndexOf('.');
+ if (lastDot > 0) {
+ packageName = className.substring(0, lastDot);
+ }
+
+ String simpleClassName = className.substring(lastDot + 1);
+ String hotRodImplClassName = className + "Delegate";
+ hotRodSimpleClassName = simpleClassName + "Delegate";
+ generalHotRodDelegate = elements.getTypeElement("org.keycloak.models.map.storage.hotRod.common.HotRodEntityDelegate");
+ abstractEntity = elements.getTypeElement("org.keycloak.models.map.common.AbstractEntity");
+ abstractHotRodEntity = elements.getTypeElement("org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity");
+ hotRodUtils = elements.getTypeElement("org.keycloak.models.map.storage.hotRod.common.HotRodTypesUtils");
+
+ boolean hasDeepClone = allMembers.stream().filter(el -> el.getKind() == ElementKind.METHOD).anyMatch(el -> "deepClone".equals(el.getSimpleName().toString()));
+ boolean needsDeepClone = fieldGetters(methodsPerAttribute)
+ .map(ExecutableElement::getReturnType)
+ .anyMatch(fieldType -> ! isKnownCollectionOfImmutableFinalTypes(fieldType) && ! isImmutableFinalType(fieldType));
+ boolean usingGeneratedCloner = ! hasDeepClone && needsDeepClone;
+ boolean hasId = methodsPerAttribute.containsKey("Id") || allMembers.stream().anyMatch(el -> "getId".equals(el.getSimpleName().toString()));
+
+ JavaFileObject file = processingEnv.getFiler().createSourceFile(hotRodImplClassName);
+ try (PrintWriter pw = new PrintWriterNoJavaLang(file.openWriter())) {
+ if (packageName != null) {
+ pw.println("package " + packageName + ";");
+ }
+
+ pw.println("import java.util.Objects;");
+ pw.println("import " + FQN_DEEP_CLONER + ";");
+ pw.println("import java.util.Optional;");
+ pw.println("import java.util.stream.Collectors;");
+ pw.println();
+ pw.println("// DO NOT CHANGE THIS CLASS, IT IS GENERATED AUTOMATICALLY BY " + GenerateHotRodEntityImplementationsProcessor.class.getSimpleName());
+ pw.println("public class " + hotRodSimpleClassName + " extends " + parentClassElement.getQualifiedName().toString() + " implements "
+ + parentInterfaceElement.getQualifiedName().toString()
+ + ", " + generalHotRodDelegate.getQualifiedName().toString() + "<" + e.getQualifiedName().toString() + ">"
+ + " {");
+ pw.println();
+ pw.println(" private final " + className + " " + ENTITY_VARIABLE + ";");
+ pw.println();
+
+ // Constructors
+ allMembers.stream()
+ .filter(ExecutableElement.class::isInstance)
+ .map(ExecutableElement.class::cast)
+ .filter((ExecutableElement ee) -> ee.getKind() == ElementKind.CONSTRUCTOR)
+ .forEach((ExecutableElement ee) -> {
+ // Create constructor and initialize cloner to DUMB_CLONER if necessary
+ if (usingGeneratedCloner) {
+ pw.println(" /**");
+ pw.println(" * @deprecated This constructor uses a {@link DeepCloner#DUMB_CLONER} that does not clone anything. Use {@link #" + hotRodSimpleClassName + "(DeepCloner)} variant instead");
+ pw.println(" */");
+ }
+ pw.println(" "
+ + ee.getModifiers().stream().map(Object::toString).collect(Collectors.joining(" "))
+ + " " + hotRodSimpleClassName + "(" + methodParameters(ee.getParameters()) + ") {"
+ );
+ pw.println(" super(" + ee.getParameters() + ");");
+ if (usingGeneratedCloner) pw.println(" this.cloner = DeepCloner.DUMB_CLONER;");
+ pw.println(" this." + ENTITY_VARIABLE + " = new " + className + "();");
+ pw.println(" }");
+ });
+
+ // Add constructor for setting HotRodEntity
+ if (usingGeneratedCloner) {
+ pw.println(" /**");
+ pw.println(" * @deprecated This constructor uses a {@link DeepCloner#DUMB_CLONER} that does not clone anything. Use {@link #" + hotRodSimpleClassName + "(DeepCloner)} variant instead");
+ pw.println(" */");
+ }
+ pw.println(" " +
+ "public " + hotRodSimpleClassName + "(" + className + " " + ENTITY_VARIABLE + ") {"
+ );
+ pw.println(" this." + ENTITY_VARIABLE + " = " + ENTITY_VARIABLE + ";");
+ if (usingGeneratedCloner) {
+ pw.println(" this.cloner = DeepCloner.DUMB_CLONER;");
+ }
+ pw.println(" }");
+
+ pw.println(" public " + hotRodSimpleClassName + "(DeepCloner cloner) {");
+ pw.println(" super();");
+ pw.println(" this." + ENTITY_VARIABLE + " = new " + className + "();");
+ if (usingGeneratedCloner) pw.println(" this.cloner = cloner;");
+ pw.println(" }");
+
+ // equals, hashCode, toString
+ pw.println(" @Override public boolean equals(Object o) {");
+ pw.println(" if (o == this) return true; ");
+ pw.println(" if (! (o instanceof " + hotRodSimpleClassName + ")) return false; ");
+ pw.println(" " + hotRodSimpleClassName + " other = (" + hotRodSimpleClassName + ") o; ");
+ pw.println(" return "
+ + fieldGetters(methodsPerAttribute)
+ .map(ExecutableElement::getSimpleName)
+ .map(Name::toString)
+ .sorted(NameFirstComparator.GET_ID_INSTANCE)
+ .map(v -> "Objects.equals(" + v + "(), other." + v + "())")
+ .collect(Collectors.joining("\n && "))
+ + ";");
+ pw.println(" }");
+ pw.println(" @Override public int hashCode() {");
+ pw.println(" return "
+ + (hasId
+ ? "(getId() == null ? super.hashCode() : getId().hashCode())"
+ : "Objects.hash("
+ + fieldGetters(methodsPerAttribute)
+ .filter(ee -> isImmutableFinalType(ee.getReturnType()))
+ .map(ExecutableElement::getSimpleName)
+ .map(Name::toString)
+ .sorted(GenerateEntityImplementationsProcessor.NameFirstComparator.GET_ID_INSTANCE)
+ .map(v -> v + "()")
+ .collect(Collectors.joining(",\n "))
+ + ")")
+ + ";");
+ pw.println(" }");
+ pw.println(" @Override public String toString() {");
+ pw.println(" return String.format(\"%s@%08x\", " + (hasId ? "getId()" : "\"" + hotRodSimpleClassName + "\"" ) + ", System.identityHashCode(this));");
+ pw.println(" }");
+
+ pw.println(" public static boolean entityEquals(Object o1, Object o2) {");
+ pw.println(" if (!(o1 instanceof " + className + ")) return false;");
+ pw.println(" if (!(o2 instanceof " + className + ")) return false;");
+
+ pw.println(" if (o1 == o2) return true;");
+
+ pw.println(" " + className + " e1 = (" + className + ") o1;");
+ pw.println(" " + className + " e2 = (" + className + ") o2;");
+
+ pw.print(" return ");
+ pw.println(elements.getAllMembers(e).stream()
+ .filter(VariableElement.class::isInstance)
+ .map(VariableElement.class::cast)
+ .map(var -> "Objects.equals(e1." + var.getSimpleName().toString() + ", e2." + var.getSimpleName().toString() + ")")
+ .collect(Collectors.joining("\n && ")));
+ pw.println(" ;");
+ pw.println(" }");
+
+ pw.println(" public static int entityHashCode(" + className + " e) {");
+ pw.println(" return "
+ + (hasId
+ ? "(e.id == null ? Objects.hash(e) : e.id.hashCode())"
+ : "Objects.hash("
+ + elements.getAllMembers(e).stream()
+ .filter(VariableElement.class::isInstance)
+ .map(VariableElement.class::cast)
+ .map(var -> var.getSimpleName().toString())
+ .collect(Collectors.joining(",\n "))
+ + ")")
+ + ";"
+ );
+ pw.println(" }");
+
+ // deepClone
+ if (! hasDeepClone && needsDeepClone) {
+ pw.println(" private final DeepCloner cloner;");
+ pw.println(" public V deepClone(V obj) {");
+ pw.println(" return cloner.from(obj);");
+ pw.println(" }");
+ }
+
+ // getters, setters
+ methodsPerAttribute.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey, GenerateEntityImplementationsProcessor.NameFirstComparator.ID_INSTANCE)).forEach(me -> {
+ HashSet methods = me.getValue();
+ TypeMirror fieldType = determineFieldType(me.getKey(), methods);
+ if (fieldType == null) {
+ return;
+ }
+
+ // Determine HotRod entity field name by changing case of first letter
+ char[] c = me.getKey().toCharArray();
+ c[0] = Character.toLowerCase(c[0]);
+ String hotRodEntityFieldName = new String(c);
+
+ // Find corresponding variable in HotRod*Entity
+ Optional hotRodVariable = elements.getAllMembers(e).stream()
+ .filter(VariableElement.class::isInstance)
+ .map(VariableElement.class::cast)
+ .filter(variableElement -> variableElement.getSimpleName().toString().equals(hotRodEntityFieldName))
+ .findFirst();
+
+ if (!hotRodVariable.isPresent()) {
+ // throw an error when no variable found
+ processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Cannot find " + e.getSimpleName().toString() + " field for methods: [" + me.getValue().stream().map(ee -> ee.getSimpleName().toString()).collect(Collectors.joining(", ")) + "]", parentInterfaceElement);
+ return;
+ }
+
+ // Implement each method
+ for (ExecutableElement method : methods) {
+ FieldAccessorType fat = FieldAccessorType.determineType(method, me.getKey(), types, fieldType);
+
+ // Check if the parent class implements the method already
+ Optional parentMethod = allMembers.stream()
+ .filter(ExecutableElement.class::isInstance)
+ .map(ExecutableElement.class::cast)
+ .filter(ee -> Objects.equals(ee.toString(), method.toString()))
+ .filter((ExecutableElement ee) -> ! ee.getModifiers().contains(Modifier.ABSTRACT))
+ .findAny();
+
+ try {
+ if (parentMethod.isPresent()) {
+ // Do not implement the method if it is already implemented by the parent class
+ processingEnv.getMessager().printMessage(Diagnostic.Kind.OTHER, "Method " + method + " is declared in a parent class.", method);
+ } else if (fat != FieldAccessorType.UNKNOWN && !printMethodBody(pw, fat, method, hotRodEntityFieldName, fieldType, hotRodVariable.get().asType())) {
+ processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "Could not determine desired semantics of method from its signature", method);
+ }
+ } catch (CannotMigrateTypeException ex) {
+ processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, ex.getFormattedMessage(), method);
+ }
+ }
+ });
+
+ // Implement HotRodDelegate interface
+ pw.println(" public " + className + " getHotRodEntity() {");
+ pw.println(" return this." + ENTITY_VARIABLE + ";");
+ pw.println(" }");
+ pw.println("}");
+ }
+ }
+
+ private String hotRodEntityField(String fieldName) {
+ return "this." + ENTITY_VARIABLE + "." + fieldName;
+ }
+
+ private boolean printMethodBody(PrintWriter pw, FieldAccessorType accessorType, ExecutableElement method, String fieldName, TypeMirror fieldType, TypeMirror hotRodFieldType) {
+ TypeMirror firstParameterType = method.getParameters().isEmpty()
+ ? types.getNullType()
+ : method.getParameters().get(0).asType();
+ TypeElement typeElement = elements.getTypeElement(types.erasure(fieldType).toString());
+
+ switch (accessorType) {
+ case GETTER:
+ pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method + " {");
+ pw.println(" return " + migrateToType(method.getReturnType(), hotRodFieldType, hotRodEntityField(fieldName)) + ";");
+ pw.println(" }");
+ return true;
+ case SETTER:
+ pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
+ if (! isImmutableFinalType(firstParameterType)) {
+ pw.println(" p0 = " + deepClone(fieldType, "p0") + ";");
+ }
+ pw.println(" " + hotRodFieldType.toString() + " migrated = " + migrateToType(hotRodFieldType, firstParameterType, "p0") + ";");
+ pw.println(" updated |= ! Objects.equals(" + hotRodEntityField(fieldName) + ", migrated);");
+ pw.println(" " + hotRodEntityField(fieldName) + " = migrated;");
+ pw.println(" }");
+ return true;
+ case COLLECTION_ADD:
+ TypeMirror collectionItemType = getGenericsDeclaration(hotRodFieldType).get(0);
+ pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
+ pw.println(" if (" + hotRodEntityField(fieldName) + " == null) { " + hotRodEntityField(fieldName) + " = " + interfaceToImplementation(typeElement, "") + "; }");
+ if (! isImmutableFinalType(firstParameterType)) {
+ pw.println(" p0 = " + deepClone(fieldType, "p0") + ";");
+ }
+ pw.println(" " + collectionItemType.toString() + " migrated = " + migrateToType(collectionItemType, firstParameterType, "p0") + ";");
+ if (isSetType(typeElement)) {
+ pw.println(" updated |= " + hotRodEntityField(fieldName) + ".add(migrated);");
+ } else {
+ pw.println(" " + hotRodEntityField(fieldName) + ".add(migrated);");
+ pw.println(" updated = true;");
+ }
+ pw.println(" }");
+ return true;
+ case COLLECTION_DELETE:
+ collectionItemType = getGenericsDeclaration(hotRodFieldType).get(0);
+ pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
+ if (isMapType(typeElement)) {
+ // Maps are stored as sets
+ pw.println(" this.updated |= " + hotRodUtils.getQualifiedName().toString() + ".removeFromSetByMapKey("
+ + hotRodEntityField(fieldName) + ", "
+ + "p0, "
+ + keyGetterReference(collectionItemType) + ");"
+ );
+ } else {
+ pw.println(" if (" + hotRodEntityField(fieldName) + " == null) { return; }");
+ pw.println(" boolean removed = " + hotRodEntityField(fieldName) + ".remove(p0);");
+ pw.println(" updated |= removed;");
+ }
+ pw.println(" }");
+ return true;
+ case MAP_ADD:
+ collectionItemType = getGenericsDeclaration(hotRodFieldType).get(0);
+ TypeMirror secondParameterType = method.getParameters().get(1).asType();
+ pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0, " + secondParameterType + " p1) {");
+ pw.println(" if (" + hotRodEntityField(fieldName) + " == null) { " + hotRodEntityField(fieldName) + " = " + interfaceToImplementation((TypeElement) types.asElement(types.erasure(hotRodFieldType)), "") + "; }");
+ pw.println(" boolean valueUndefined = p1 == null" + (isCollection(secondParameterType) ? " || p1.isEmpty()" : "") + ";");
+ if (! isImmutableFinalType(secondParameterType)) {
+ pw.println(" p1 = " + deepClone(secondParameterType, "p1") + ";");
+ }
+ pw.println(" this.updated |= " + hotRodUtils.getQualifiedName().toString() + ".removeFromSetByMapKey("
+ + hotRodEntityField(fieldName) + ", "
+ + "p0, "
+ + keyGetterReference(collectionItemType) + ");"
+ );
+ pw.println(" this.updated |= !valueUndefined && " + hotRodEntityField(fieldName)
+ + ".add(" + migrateToType(collectionItemType, new TypeMirror[]{firstParameterType, secondParameterType}, new String[]{"p0", "p1"}) + ");");
+ pw.println(" }");
+ return true;
+ case MAP_GET:
+ pw.println(" @SuppressWarnings(\"unchecked\") @Override public " + method.getReturnType() + " " + method.getSimpleName() + "(" + firstParameterType + " p0) {");
+ collectionItemType = getGenericsDeclaration(hotRodFieldType).get(0);
+ pw.println(" return " + hotRodUtils.getQualifiedName().toString() + ".getMapValueFromSet("
+ + hotRodEntityField(fieldName) + ", "
+ + "p0, "
+ + keyGetterReference(collectionItemType) + ", "
+ + valueGetterReference(collectionItemType) + ");"
+ );
+ pw.println(" }");
+ return true;
+ }
+
+ return false;
+ }
+
+ private String migrateToType(TypeMirror toType, TypeMirror fromType, String fieldName) {
+ return migrateToType(toType, new TypeMirror[] {fromType}, new String[]{fieldName});
+ }
+
+ private String toSimpleName(TypeMirror typeMirror) {
+ TypeElement e = elements.getTypeElement(types.erasure(typeMirror).toString());
+ return e.getSimpleName().toString();
+ }
+
+ private String keyGetterReference(TypeMirror type) {
+ if (types.isAssignable(type, abstractHotRodEntity.asType())) {
+ return "e -> e.id";
+ }
+ return hotRodUtils.getQualifiedName().toString() + "::getKey";
+ }
+
+ private String valueGetterReference(TypeMirror type) {
+ if (types.isAssignable(type, abstractHotRodEntity.asType())) {
+ return toSimpleName(type) + "Delegate::new";
+ }
+ return hotRodUtils.getQualifiedName().toString() + "::getValue";
+ }
+
+ private String migrateToType(TypeMirror toType, TypeMirror[] fromType, String[] fieldNames) {
+ // No migration needed, fromType is assignable to toType directly
+ if (fromType.length == 1 && types.isAssignable(types.erasure(fromType[0]), types.erasure(toType))) {
+ return fieldNames[0];
+ }
+
+ // HotRod entities are not allowed to use Maps, therefore we often need to migrate from Map to Set and the other way around
+ if (fromType.length == 1) {
+ if (isSetType((TypeElement) types.asElement(types.erasure(toType)))
+ && isMapType((TypeElement) types.asElement(types.erasure(fromType[0])))) {
+ TypeMirror setType = getGenericsDeclaration(toType).get(0);
+
+ return hotRodUtils.getQualifiedName().toString() + ".migrateMapToSet("
+ + fieldNames[0] + ", "
+ + hotRodUtils.getQualifiedName().toString() + "::create" + toSimpleName(setType) + "FromMapEntry)";
+ } else if (isMapType((TypeElement) types.asElement(types.erasure(toType)))
+ && isSetType((TypeElement) types.asElement(types.erasure(fromType[0])))) {
+ TypeMirror setType = getGenericsDeclaration(fromType[0]).get(0);
+
+ return hotRodUtils.getQualifiedName().toString() + ".migrateSetToMap("
+ + fieldNames[0] + ", "
+ + keyGetterReference(setType) + ", "
+ + valueGetterReference(setType)
+ + ")";
+ }
+
+ }
+
+ // Try to find constructor that can do the migration
+ if (findSuitableConstructor(toType, fromType).isPresent()) {
+ return "new " + toType.toString() + "(" + String.join(", ", fieldNames) + ")";
+ }
+
+ // Check if any of parameters is another Map*Entity
+ OptionalInt anotherMapEntityIndex = IntStream.range(0, fromType.length)
+ .filter(i -> types.isAssignable(fromType[i], abstractEntity.asType()))
+ .findFirst();
+
+ if (anotherMapEntityIndex.isPresent()) {
+ // If yes, we can be sure that it implements HotRodEntityDelegate (this is achieved by HotRod cloner settings) so we can just call getHotRodEntity method
+ return "((" + generalHotRodDelegate.getQualifiedName().toString() + "<" + toType.toString() + ">) " + fieldNames[anotherMapEntityIndex.getAsInt()] + ").getHotRodEntity()";
+ }
+
+ // Check if any of parameters is another HotRod*Entity
+ OptionalInt anotherHotRodEntityIndex = IntStream.range(0, fromType.length)
+ .filter(i -> types.isAssignable(fromType[i], abstractHotRodEntity.asType()))
+ .findFirst();
+
+ if (anotherHotRodEntityIndex.isPresent()) {
+ // If yes, we can be sure that it implements HotRodEntityDelegate (this is achieved by HotRod cloner settings) so we can just call getHotRodEntity method
+ return "new " + fromType[anotherHotRodEntityIndex.getAsInt()] + "Delegate(" + String.join(", ", fieldNames) + ")";
+ }
+
+ throw new CannotMigrateTypeException(toType, fromType);
+ }
+
+ private Optional findSuitableConstructor(TypeMirror desiredType, TypeMirror[] parameters) {
+ // Try to find constructor that can do the migration
+ TypeElement type = (TypeElement) types.asElement(desiredType);
+ return elements.getAllMembers(type)
+ .stream()
+ .filter(ExecutableElement.class::isInstance)
+ .map(ExecutableElement.class::cast)
+ .filter(ee -> ee.getKind() == ElementKind.CONSTRUCTOR)
+ .filter(ee -> ee.getParameters().size() == parameters.length)
+ .filter(method -> IntStream.range(0, parameters.length).allMatch(i -> deepCompareTypes(parameters[i], method.getParameters().get(i).asType())))
+ .findFirst();
+ }
+
+
+ private boolean deepCompareTypes(TypeMirror fromType, TypeMirror toType) {
+ return types.isAssignable(types.erasure(fromType), types.erasure(toType))
+ && deepCompareTypes(getGenericsDeclaration(fromType), getGenericsDeclaration(toType));
+ }
+
+ private boolean deepCompareTypes(List fromTypes, List toTypes) {
+ if (fromTypes.size() == 0 && toTypes.size() == 0) return true;
+ if (fromTypes.size() != toTypes.size()) return false;
+
+ for (int i = 0; i < fromTypes.size(); i++) {
+ if (!deepCompareTypes(fromTypes.get(i), toTypes.get(i))) return false;
+ }
+ return true;
+ }
+ }
+}
diff --git a/model/build-processor/src/main/java/org/keycloak/models/map/processor/Util.java b/model/build-processor/src/main/java/org/keycloak/models/map/processor/Util.java
index bc82b9298709..ade5a0bfed53 100644
--- a/model/build-processor/src/main/java/org/keycloak/models/map/processor/Util.java
+++ b/model/build-processor/src/main/java/org/keycloak/models/map/processor/Util.java
@@ -18,14 +18,20 @@
import org.keycloak.models.map.annotations.IgnoreForEntityImplementationGenerator;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
@@ -40,6 +46,7 @@
public class Util {
private static final HashSet SET_TYPES = new HashSet<>(Arrays.asList(Set.class.getCanonicalName(), TreeSet.class.getCanonicalName(), HashSet.class.getCanonicalName(), LinkedHashSet.class.getCanonicalName()));
+ private static final HashSet MAP_TYPES = new HashSet<>(Arrays.asList(Map.class.getCanonicalName(), HashMap.class.getCanonicalName()));
public static List getGenericsDeclaration(TypeMirror fieldType) {
List res = new LinkedList<>();
@@ -67,6 +74,11 @@ public static boolean isSetType(TypeElement typeElement) {
return SET_TYPES.contains(name.toString());
}
+ public static boolean isMapType(TypeElement typeElement) {
+ Name name = typeElement.getQualifiedName();
+ return MAP_TYPES.contains(name.toString());
+ }
+
public static boolean isNotIgnored(Element el) {
do {
IgnoreForEntityImplementationGenerator a = el.getAnnotation(IgnoreForEntityImplementationGenerator.class);
@@ -78,4 +90,20 @@ public static boolean isNotIgnored(Element el) {
return true;
}
+ protected static Optional findParentMethodImplementation(List extends Element> allParentMembers, ExecutableElement method) {
+ return allParentMembers.stream()
+ .filter(ExecutableElement.class::isInstance)
+ .map(ExecutableElement.class::cast)
+ .filter(ee -> Objects.equals(ee.toString(), method.toString()))
+ .filter((ExecutableElement ee) -> ! ee.getModifiers().contains(Modifier.ABSTRACT))
+ .findAny();
+ }
+
+ public static String singularToPlural(String word) {
+ return word.endsWith("y") ? word.substring(0, word.length() -1) + "ies" : word + "s";
+ }
+
+ public static String pluralToSingular(String word) {
+ return word.endsWith("ies") ? word.substring(0, word.length() - 3) + "y" : word.endsWith("s") ? word.substring(0, word.length() - 1) : word;
+ }
}
diff --git a/model/infinispan/pom.xml b/model/infinispan/pom.xml
index 97558e91acd4..631ac18502a0 100755
--- a/model/infinispan/pom.xml
+++ b/model/infinispan/pom.xml
@@ -21,7 +21,7 @@
keycloak-model-pom
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
4.0.0
diff --git a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java
index 1d126329615a..d64acdd20ef2 100755
--- a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java
@@ -384,6 +384,10 @@ private void configureRemoteCacheStore(ConfigurationBuilder builder, boolean asy
String jdgServer = config.get("remoteStoreHost", "localhost");
Integer jdgPort = config.getInt("remoteStorePort", 11222);
+ // After upgrade to Infinispan 12.1.7.Final it's required that both remote store and embedded cache use
+ // the same key media type to allow segmentation. Also, the number of segments in an embedded cache needs to match number of segments in the remote store.
+ boolean segmented = config.getBoolean("segmented", false);
+
builder.persistence()
.passivation(false)
.addStore(RemoteStoreConfigurationBuilder.class)
@@ -393,6 +397,7 @@ private void configureRemoteCacheStore(ConfigurationBuilder builder, boolean asy
.preload(false)
.shared(true)
.remoteCacheName(cacheName)
+ .segmented(segmented)
.rawValues(true)
.forceReturnValues(false)
.marshaller(KeycloakHotRodMarshallerFactory.class.getName())
@@ -408,6 +413,10 @@ private void configureRemoteActionTokenCacheStore(ConfigurationBuilder builder,
String jdgServer = config.get("remoteStoreHost", "localhost");
Integer jdgPort = config.getInt("remoteStorePort", 11222);
+ // After upgrade to Infinispan 12.1.7.Final it's required that both remote store and embedded cache use
+ // the same key media type to allow segmentation. Also, the number of segments in an embedded cache needs to match number of segments in the remote store.
+ boolean segmented = config.getBoolean("segmented", false);
+
builder.persistence()
.passivation(false)
.addStore(RemoteStoreConfigurationBuilder.class)
@@ -417,6 +426,7 @@ private void configureRemoteActionTokenCacheStore(ConfigurationBuilder builder,
.preload(true)
.shared(true)
.remoteCacheName(InfinispanConnectionProvider.ACTION_TOKEN_CACHE)
+ .segmented(segmented)
.rawValues(true)
.forceReturnValues(false)
.marshaller(KeycloakHotRodMarshallerFactory.class.getName())
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java
index b6c4b78629a6..8f9565d30050 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java
@@ -247,13 +247,24 @@ private void roleRemovalInvalidations(String roleId, String roleName, String rol
}
}
-
+ private void invalidateRoleAndComposite(String id) {
+ invalidations.add(id);
+ RoleAdapter adapter = managedRoles.get(id);
+
+ if (adapter != null) {
+ adapter.invalidate();
+ adapter.invalidateComposites();
+ }
+ }
private void invalidateRole(String id) {
invalidations.add(id);
RoleAdapter adapter = managedRoles.get(id);
- if (adapter != null) adapter.invalidate();
+
+ if (adapter != null) {
+ adapter.invalidate();
+ }
}
private void addedRole(String roleId, String roleContainerId) {
@@ -797,7 +808,7 @@ public RoleModel getClientRole(ClientModel client, String name) {
public boolean removeRole(RoleModel role) {
listInvalidations.add(role.getContainer().getId());
- invalidateRole(role.getId());
+ invalidateRoleAndComposite(role.getId());
invalidationEvents.add(RoleRemovedEvent.create(role.getId(), role.getName(), role.getContainer().getId()));
roleRemovalInvalidations(role.getId(), role.getName(), role.getContainer().getId());
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RoleAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RoleAdapter.java
index 9b4a7e34efa1..1a70e6089bce 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RoleAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RoleAdapter.java
@@ -44,6 +44,7 @@ public class RoleAdapter implements RoleModel {
protected RealmCacheSession cacheSession;
protected RealmModel realm;
protected Set composites;
+ protected Set parents;
private final Supplier modelSupplier;
public RoleAdapter(CachedRole cached, RealmCacheSession session, RealmModel realm) {
@@ -56,10 +57,18 @@ public RoleAdapter(CachedRole cached, RealmCacheSession session, RealmModel real
protected void getDelegateForUpdate() {
if (updated == null) {
cacheSession.registerRoleInvalidation(cached.getId(), cached.getName(), getContainerId());
+
updated = modelSupplier.get();
if (updated == null) throw new IllegalStateException("Not found in database");
}
}
+
+ protected void invalidateComposites() {
+ for (String roleId : cached.getComposites()) {
+ RoleModel role = realm.getRoleById(roleId);
+ cacheSession.registerRoleInvalidation(role.getId(), role.getName(), role.getContainerId());
+ }
+ }
protected boolean invalidated;
@@ -121,6 +130,7 @@ public void addCompositeRole(RoleModel role) {
@Override
public void removeCompositeRole(RoleModel role) {
getDelegateForUpdate();
+ invalidateComposites();
updated.removeCompositeRole(role);
}
@@ -149,6 +159,31 @@ public Stream getCompositesStream(String search, Integer first, Integ
return cacheSession.getRoleDelegate().getRolesStream(realm, cached.getComposites().stream(), search, first, max);
}
+
+ @Override
+ public void addParentRole(RoleModel role) {
+ getDelegateForUpdate();
+ updated.addParentRole(role);
+ }
+
+ @Override
+ public Stream getParentsStream() {
+ if (isUpdated()) return updated.getParentsStream();
+
+ if (parents == null) {
+ parents = new HashSet<>();
+ parents = cached.getParents().stream()
+ .map(id -> {
+ RoleModel role = realm.getRoleById(id);
+ if (role == null) {
+ throw new IllegalStateException("Could not find composite in role " + getName() + ": " + id);
+ }
+ return role;
+ }).collect(Collectors.toSet());
+ }
+
+ return parents.stream();
+ }
@Override
public boolean isClientRole() {
@@ -247,5 +282,4 @@ public boolean equals(Object o) {
public int hashCode() {
return getId().hashCode();
}
-
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java
index 009f644ee000..d4916227170e 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java
@@ -725,17 +725,17 @@ public String getAttribute(String name) {
public Integer getAttribute(String name, Integer defaultValue) {
String v = getAttribute(name);
- return v != null ? Integer.parseInt(v) : defaultValue;
+ return v != null ? Integer.valueOf(v) : defaultValue;
}
public Long getAttribute(String name, Long defaultValue) {
String v = getAttribute(name);
- return v != null ? Long.parseLong(v) : defaultValue;
+ return v != null ? Long.valueOf(v) : defaultValue;
}
public Boolean getAttribute(String name, Boolean defaultValue) {
String v = getAttribute(name);
- return v != null ? Boolean.parseBoolean(v) : defaultValue;
+ return v != null ? Boolean.valueOf(v) : defaultValue;
}
public Map getAttributes() {
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRole.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRole.java
index 13684175058c..37a90f788c07 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRole.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRole.java
@@ -38,7 +38,8 @@ public class CachedRole extends AbstractRevisioned implements InRealm {
final protected String realm;
final protected String description;
final protected boolean composite;
- final protected Set composites = new HashSet<>();
+ final protected Set composites = new HashSet();
+ final protected Set parents = new HashSet();
private final LazyLoader> attributes;
public CachedRole(Long revision, RoleModel model, RealmModel realm) {
@@ -50,6 +51,10 @@ public CachedRole(Long revision, RoleModel model, RealmModel realm) {
if (composite) {
composites.addAll(model.getCompositesStream().map(RoleModel::getId).collect(Collectors.toSet()));
}
+
+ parents.addAll(model.getParentsStream().map(RoleModel::getId).collect(Collectors.toSet()));
+
+
attributes = new DefaultLazyLoader<>(roleModel -> new MultivaluedHashMap<>(roleModel.getAttributes()), MultivaluedHashMap::new);
}
@@ -72,6 +77,10 @@ public boolean isComposite() {
public Set getComposites() {
return composites;
}
+
+ public Set getParents() {
+ return parents;
+ }
public MultivaluedHashMap getAttributes(Supplier roleModel) {
return attributes.get(roleModel);
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/SessionEntityWrapper.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/SessionEntityWrapper.java
index fd6ebc038501..040933be62cc 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/SessionEntityWrapper.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/changes/SessionEntityWrapper.java
@@ -112,7 +112,7 @@ public void putLocalMetadataNote(String key, String value) {
public Integer getLocalMetadataNoteInt(String key) {
String note = getLocalMetadataNote(key);
- return note==null ? null : Integer.parseInt(note);
+ return note==null ? null : Integer.valueOf(note);
}
public void putLocalMetadataNoteInt(String key, int value) {
diff --git a/model/infinispan/src/test/java/org/keycloak/cluster/infinispan/ConcurrencyJDGCachePutTest.java b/model/infinispan/src/test/java/org/keycloak/cluster/infinispan/ConcurrencyJDGCachePutTest.java
index bd8efd07cc52..6e537e526c2b 100644
--- a/model/infinispan/src/test/java/org/keycloak/cluster/infinispan/ConcurrencyJDGCachePutTest.java
+++ b/model/infinispan/src/test/java/org/keycloak/cluster/infinispan/ConcurrencyJDGCachePutTest.java
@@ -165,7 +165,7 @@ public void run() {
}
public static int getClusterStartupTime(Cache cache, String cacheKey, EntryInfo wrapper, int myThreadId) {
- Integer startupTime = myThreadId==1 ? Integer.parseInt(cacheKey.substring(4)) : Integer.parseInt(cacheKey.substring(4)) * 2;
+ Integer startupTime = myThreadId==1 ? Integer.valueOf(cacheKey.substring(4)) : Integer.valueOf(cacheKey.substring(4)) * 2;
// Concurrency doesn't work correctly with this
//Integer existingClusterStartTime = (Integer) cache.putIfAbsent(cacheKey, startupTime);
diff --git a/model/jpa/pom.xml b/model/jpa/pom.xml
index 208b41bc6605..693dcda32049 100755
--- a/model/jpa/pom.xml
+++ b/model/jpa/pom.xml
@@ -21,7 +21,7 @@
keycloak-model-pom
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
4.0.0
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java
index 7c27121a121a..1f63166b67ec 100755
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java
@@ -29,6 +29,7 @@
import org.keycloak.common.util.StackUtil;
import org.keycloak.common.util.StringPropertyReplacer;
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
+import org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProviderFactory;
import org.keycloak.connections.jpa.util.JpaUtils;
import org.keycloak.migration.MigrationModelManager;
import org.keycloak.models.KeycloakSession;
@@ -338,7 +339,7 @@ protected void startGlobalStats(KeycloakSession session, int globalStatsInterval
}
void migration(MigrationStrategy strategy, boolean initializeEmpty, String schema, File databaseUpdateFile, Connection connection, KeycloakSession session) {
- JpaUpdaterProvider updater = session.getProvider(JpaUpdaterProvider.class);
+ JpaUpdaterProvider updater = session.getProvider(JpaUpdaterProvider.class, LiquibaseJpaUpdaterProviderFactory.PROVIDER_ID);
JpaUpdaterProvider.Status status = updater.validate(connection, schema);
if (status == JpaUpdaterProvider.Status.VALID) {
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProviderFactory.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProviderFactory.java
index 26eac9a646c9..6385232e1700 100755
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProviderFactory.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProviderFactory.java
@@ -28,6 +28,8 @@
*/
public class LiquibaseJpaUpdaterProviderFactory implements JpaUpdaterProviderFactory {
+ public static final String PROVIDER_ID = "liquibase";
+
@Override
public JpaUpdaterProvider create(KeycloakSession session) {
return new LiquibaseJpaUpdaterProvider(session);
@@ -48,7 +50,7 @@ public void close() {
@Override
public String getId() {
- return "liquibase";
+ return PROVIDER_ID;
}
}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/JpaUpdate13_0_0_MigrateDefaultRoles.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/JpaUpdate13_0_0_MigrateDefaultRoles.java
index 8bcec48da5be..b3a1ec92c810 100644
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/JpaUpdate13_0_0_MigrateDefaultRoles.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/JpaUpdate13_0_0_MigrateDefaultRoles.java
@@ -48,26 +48,26 @@ protected void generateStatementsImpl() throws CustomChangeException {
// create new default role
new InsertStatement(null, null, database.correctObjectName("KEYCLOAK_ROLE", Table.class))
.addColumnValue("ID", id)
- .addColumnValue("CLIENT_REALM_CONSTRAINT", entry.getValue())
+ .addColumnValue("CLIENT_REALM_CONSTRAINT", entry.getKey())
.addColumnValue("CLIENT_ROLE", Boolean.FALSE)
.addColumnValue("DESCRIPTION", "${role_" + roleName + "}")
.addColumnValue("NAME", roleName)
- .addColumnValue("REALM_ID", entry.getValue())
- .addColumnValue("REALM", entry.getValue())
+ .addColumnValue("REALM_ID", entry.getKey())
+ .addColumnValue("REALM", entry.getKey())
);
statements.add(
// assign the role to the realm
new UpdateStatement(null, null, database.correctObjectName("REALM", Table.class))
.addNewColumnValue("DEFAULT_ROLE", id)
.setWhereClause("REALM.ID=?")
- .addWhereParameter(entry.getValue())
+ .addWhereParameter(entry.getKey())
);
statements.add(
// copy data from REALM_DEFAULT_ROLES to COMPOSITE_ROLE
new RawSqlStatement("INSERT INTO " + compositeRoleTable + " (COMPOSITE, CHILD_ROLE) " +
"SELECT '" + id + "', ROLE_ID FROM " + getTableName("REALM_DEFAULT_ROLES") +
- " WHERE REALM_ID = '" + database.escapeStringForDatabase(entry.getValue()) + "'")
+ " WHERE REALM_ID = '" + database.escapeStringForDatabase(entry.getKey()) + "'")
);
statements.add(
// copy data from CLIENT_DEFAULT_ROLES to COMPOSITE_ROLE
@@ -75,7 +75,7 @@ protected void generateStatementsImpl() throws CustomChangeException {
"SELECT '" + id + "', " + clientDefaultRolesTable + ".ROLE_ID FROM " +
clientDefaultRolesTable + " INNER JOIN " + clientTable + " ON " +
clientTable + ".ID = " + clientDefaultRolesTable + ".CLIENT_ID AND " +
- clientTable + ".REALM_ID = '" + database.escapeStringForDatabase(entry.getValue()) + "'")
+ clientTable + ".REALM_ID = '" + database.escapeStringForDatabase(entry.getKey()) + "'")
);
}
}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProviderFactory.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProviderFactory.java
index 3026f7daf27d..addb7c8220b1 100644
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProviderFactory.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProviderFactory.java
@@ -30,6 +30,7 @@
public class LiquibaseDBLockProviderFactory implements DBLockProviderFactory {
private static final Logger logger = Logger.getLogger(LiquibaseDBLockProviderFactory.class);
+ public static final int PROVIDER_PRIORITY = 1;
private long lockWaitTimeoutMillis;
@@ -68,4 +69,9 @@ public void close() {
public String getId() {
return "jpa";
}
+
+ @Override
+ public int order() {
+ return PROVIDER_PRIORITY;
+ }
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java
index c46a0389918a..21cfac9c3b7e 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java
@@ -17,16 +17,6 @@
package org.keycloak.models.jpa;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.RoleContainerModel;
-import org.keycloak.models.RoleModel;
-import org.keycloak.models.jpa.entities.RoleAttributeEntity;
-import org.keycloak.models.jpa.entities.RoleEntity;
-import org.keycloak.models.utils.KeycloakModelUtils;
-
-import javax.persistence.EntityManager;
-import javax.persistence.Query;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -36,6 +26,17 @@
import java.util.Objects;
import java.util.stream.Stream;
+import javax.persistence.EntityManager;
+import javax.persistence.Query;
+
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleContainerModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.jpa.entities.RoleAttributeEntity;
+import org.keycloak.models.jpa.entities.RoleEntity;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
/**
* @author Bill Burke
* @version $Revision: 1 $
@@ -119,6 +120,21 @@ public Stream getCompositesStream(String search, Integer first, Integ
getEntity().getCompositeRoles().stream().map(RoleEntity::getId),
search, first, max);
}
+
+ @Override
+ public void addParentRole(RoleModel role) {
+ RoleEntity entity = toRoleEntity(role);
+ for (RoleEntity parent : getEntity().getParentRoles()) {
+ if (parent.equals(entity)) return;
+ }
+ getEntity().getParentRoles().add(entity);
+ }
+
+ @Override
+ public Stream getParentsStream() {
+ Stream composites = getEntity().getParentRoles().stream().map(c -> new RoleAdapter(session, realm, em, c));
+ return composites.filter(Objects::nonNull);
+ }
@Override
public boolean hasRole(RoleModel role) {
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 020b5b8a712d..c98fcf6eebed 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
@@ -175,8 +175,10 @@ public void setAttribute(String name, List values) {
}
// Remove all existing
removeAttribute(name);
- for (Iterator it = values.stream().filter(Objects::nonNull).iterator(); it.hasNext();) {
- persistAttributeValue(name, it.next());
+ if (values != null) {
+ for (Iterator it = values.stream().filter(Objects::nonNull).iterator(); it.hasNext();) {
+ persistAttributeValue(name, it.next());
+ }
}
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
index 68c653236fc7..64669f99cab6 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
@@ -96,7 +96,10 @@ public class RoleEntity {
@ManyToMany(fetch = FetchType.LAZY, cascade = {})
@JoinTable(name = "COMPOSITE_ROLE", joinColumns = @JoinColumn(name = "COMPOSITE"), inverseJoinColumns = @JoinColumn(name = "CHILD_ROLE"))
- private Set compositeRoles;
+ private Set compositeRoles = new HashSet<>();
+
+ @ManyToMany(fetch = FetchType.LAZY, cascade = {}, mappedBy = "compositeRoles")
+ private Set parentRoles = new HashSet<>();
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="role")
@Fetch(FetchMode.SELECT)
@@ -157,6 +160,14 @@ public Set getCompositeRoles() {
public void setCompositeRoles(Set compositeRoles) {
this.compositeRoles = compositeRoles;
}
+
+ public Set getParentRoles() {
+ return parentRoles;
+ }
+
+ public void setParentRoles(Set parentRoles) {
+ this.parentRoles = parentRoles;
+ }
public boolean isClientRole() {
return clientRole;
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-17.0.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-17.0.0.xml
new file mode 100644
index 000000000000..ba034fbb0297
--- /dev/null
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-17.0.0.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
index 2509294c0749..308545daaf5c 100755
--- a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
@@ -71,5 +71,6 @@
+
diff --git a/model/map-hot-rod/pom.xml b/model/map-hot-rod/pom.xml
index 6e9bc3b1241c..79530193044b 100644
--- a/model/map-hot-rod/pom.xml
+++ b/model/map-hot-rod/pom.xml
@@ -5,7 +5,7 @@
keycloak-model-pom
org.keycloak
- 16.0.0-SNAPSHOT
+ 17.0.0-SNAPSHOT
4.0.0
@@ -58,6 +58,44 @@
hamcrest
test
+
+ org.keycloak
+ keycloak-model-build-processor
+ ${project.version}
+ true
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ org.infinispan.protostream
+ protostream-processor
+ ${infinispan.protostream.processor.version}
+
+
+ org.keycloak
+ keycloak-model-build-processor
+ ${project.version}
+
+
+ javax.annotation
+ javax.annotation-api
+ ${javax.annotation-api.version}
+
+
+
+ org.infinispan.protostream.annotations.impl.processor.AutoProtoSchemaBuilderAnnotationProcessor
+ org.keycloak.models.map.processor.GenerateHotRodEntityImplementationsProcessor
+
+
+
+
+
+
diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodMapStorage.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodMapStorage.java
index 0e3644ab3f51..ecb980d49c3b 100644
--- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodMapStorage.java
+++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodMapStorage.java
@@ -26,9 +26,10 @@
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.DeepCloner;
+import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
+import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDelegate;
import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDescriptor;
import org.keycloak.models.map.common.StringKeyConvertor;
-import org.keycloak.models.map.common.UpdatableEntity;
import org.keycloak.models.map.storage.MapKeycloakTransaction;
import org.keycloak.models.map.storage.MapStorage;
import org.keycloak.models.map.storage.QueryParameters;
@@ -42,6 +43,7 @@
import java.util.Objects;
import java.util.Spliterators;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
@@ -49,20 +51,22 @@
import static org.keycloak.models.map.storage.hotRod.common.HotRodUtils.paginateQuery;
import static org.keycloak.utils.StreamsUtil.closing;
-public class HotRodMapStorage implements MapStorage, ConcurrentHashMapCrudOperations {
+public class HotRodMapStorage, M> implements MapStorage, ConcurrentHashMapCrudOperations {
private static final Logger LOG = Logger.getLogger(HotRodMapStorage.class);
- private final RemoteCache remoteCache;
+ private final RemoteCache remoteCache;
private final StringKeyConvertor keyConvertor;
- private final HotRodEntityDescriptor storedEntityDescriptor;
+ private final HotRodEntityDescriptor storedEntityDescriptor;
+ private final Function delegateProducer;
private final DeepCloner cloner;
- public HotRodMapStorage(RemoteCache remoteCache, StringKeyConvertor keyConvertor, HotRodEntityDescriptor storedEntityDescriptor, DeepCloner cloner) {
+ public HotRodMapStorage(RemoteCache remoteCache, StringKeyConvertor keyConvertor, HotRodEntityDescriptor storedEntityDescriptor, DeepCloner cloner) {
this.remoteCache = remoteCache;
this.keyConvertor = keyConvertor;
this.storedEntityDescriptor = storedEntityDescriptor;
this.cloner = cloner;
+ this.delegateProducer = storedEntityDescriptor.getHotRodDelegateProvider();
}
@Override
@@ -73,7 +77,7 @@ public V create(V value) {
value = cloner.from(keyConvertor.keyToString(key), value);
}
- remoteCache.putIfAbsent(key, value);
+ remoteCache.putIfAbsent(key, value.getHotRodEntity());
return value;
}
@@ -82,13 +86,13 @@ public V create(V value) {
public V read(String key) {
Objects.requireNonNull(key, "Key must be non-null");
K k = keyConvertor.fromStringSafe(key);
- return remoteCache.get(k);
+ return delegateProducer.apply(remoteCache.get(k));
}
@Override
public V update(V value) {
K key = keyConvertor.fromStringSafe(value.getId());
- return remoteCache.replace(key, value);
+ return delegateProducer.apply(remoteCache.replace(key, value.getHotRodEntity()));
}
@Override
@@ -107,7 +111,7 @@ private static String toOrderString(QueryParameters.OrderBy> orderBy) {
@Override
public Stream read(QueryParameters queryParameters) {
- IckleQueryMapModelCriteriaBuilder iqmcb = queryParameters.getModelCriteriaBuilder()
+ IckleQueryMapModelCriteriaBuilder iqmcb = queryParameters.getModelCriteriaBuilder()
.flashToModelCriteriaBuilder(createCriteriaBuilder());
String queryString = iqmcb.getIckleQuery();
@@ -120,19 +124,21 @@ public Stream read(QueryParameters queryParameters) {
QueryFactory queryFactory = Search.getQueryFactory(remoteCache);
- Query query = paginateQuery(queryFactory.create(queryString), queryParameters.getOffset(),
+ Query query = paginateQuery(queryFactory.create(queryString), queryParameters.getOffset(),
queryParameters.getLimit());
query.setParameters(iqmcb.getParameters());
- CloseableIterator iterator = query.iterator();
+ CloseableIterator iterator = query.iterator();
return closing(StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false))
- .onClose(iterator::close);
+ .onClose(iterator::close)
+ .filter(Objects::nonNull) // see https://github.com/keycloak/keycloak/issues/9271
+ .map(this.delegateProducer);
}
@Override
public long getCount(QueryParameters queryParameters) {
- IckleQueryMapModelCriteriaBuilder iqmcb = queryParameters.getModelCriteriaBuilder()
+ IckleQueryMapModelCriteriaBuilder iqmcb = queryParameters.getModelCriteriaBuilder()
.flashToModelCriteriaBuilder(createCriteriaBuilder());
String queryString = iqmcb.getIckleQuery();
@@ -148,7 +154,7 @@ public long getCount(QueryParameters queryParameters) {
@Override
public long delete(QueryParameters queryParameters) {
- IckleQueryMapModelCriteriaBuilder iqmcb = queryParameters.getModelCriteriaBuilder()
+ IckleQueryMapModelCriteriaBuilder iqmcb = queryParameters.getModelCriteriaBuilder()
.flashToModelCriteriaBuilder(createCriteriaBuilder());
String queryString = "SELECT id " + iqmcb.getIckleQuery();
@@ -178,8 +184,8 @@ public long delete(QueryParameters queryParameters) {
return result.get();
}
- public IckleQueryMapModelCriteriaBuilder createCriteriaBuilder() {
- return new IckleQueryMapModelCriteriaBuilder<>();
+ public IckleQueryMapModelCriteriaBuilder createCriteriaBuilder() {
+ return new IckleQueryMapModelCriteriaBuilder<>(storedEntityDescriptor.getEntityTypeClass());
}
@Override
diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodMapStorageProvider.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodMapStorageProvider.java
index 92f45dad0ef2..438b4d775813 100644
--- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodMapStorageProvider.java
+++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodMapStorageProvider.java
@@ -19,9 +19,10 @@
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.DeepCloner;
+import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
+import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDelegate;
import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDescriptor;
import org.keycloak.models.map.common.StringKeyConvertor;
-import org.keycloak.models.map.common.UpdatableEntity;
import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProvider;
import org.keycloak.models.map.storage.MapStorage;
import org.keycloak.models.map.storage.MapStorageProvider;
@@ -46,8 +47,8 @@ public MapStorage getStorage(Class modelT
}
@SuppressWarnings("unchecked")
- public HotRodMapStorage getHotRodStorage(Class modelType, MapStorageProviderFactory.Flag... flags) {
- HotRodEntityDescriptor entityDescriptor = (HotRodEntityDescriptor) factory.getEntityDescriptor(modelType);
+ public , M> HotRodMapStorage getHotRodStorage(Class modelType, MapStorageProviderFactory.Flag... flags) {
+ HotRodEntityDescriptor entityDescriptor = (HotRodEntityDescriptor) factory.getEntityDescriptor(modelType);
return new HotRodMapStorage<>(connectionProvider.getRemoteCache(entityDescriptor.getCacheName()), StringKeyConvertor.StringKey.INSTANCE, entityDescriptor, cloner);
}
diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodMapStorageProviderFactory.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodMapStorageProviderFactory.java
index 3350918d3db0..e65c632e4d05 100644
--- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodMapStorageProviderFactory.java
+++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/HotRodMapStorageProviderFactory.java
@@ -22,12 +22,13 @@
import org.keycloak.common.Profile;
import org.keycloak.component.AmphibianProviderFactory;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
-import org.keycloak.models.map.storage.hotRod.client.HotRodAttributeEntity;
+import org.keycloak.models.map.group.MapGroupEntity;
import org.keycloak.models.map.storage.hotRod.client.HotRodClientEntity;
-import org.keycloak.models.map.storage.hotRod.client.HotRodProtocolMapperEntity;
-import org.keycloak.models.map.storage.hotRod.common.HotRodPair;
+import org.keycloak.models.map.storage.hotRod.client.HotRodClientEntityDelegate;
+import org.keycloak.models.map.storage.hotRod.client.HotRodProtocolMapperEntityDelegate;
import org.keycloak.models.map.client.MapClientEntity;
import org.keycloak.models.map.client.MapProtocolMapperEntity;
import org.keycloak.models.map.common.DeepCloner;
@@ -35,9 +36,10 @@
import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProvider;
import org.keycloak.models.map.storage.MapStorageProvider;
import org.keycloak.models.map.storage.MapStorageProviderFactory;
+import org.keycloak.models.map.storage.hotRod.group.HotRodGroupEntity;
+import org.keycloak.models.map.storage.hotRod.group.HotRodGroupEntityDelegate;
import org.keycloak.provider.EnvironmentDependentProviderFactory;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@@ -47,18 +49,24 @@ public class HotRodMapStorageProviderFactory implements AmphibianProviderFactory
private static final Logger LOG = Logger.getLogger(HotRodMapStorageProviderFactory.class);
private final static DeepCloner CLONER = new DeepCloner.Builder()
- .constructorDC(MapClientEntity.class, HotRodClientEntity::new)
- .constructor(MapProtocolMapperEntity.class, HotRodProtocolMapperEntity::new)
+ .constructor(MapClientEntity.class, HotRodClientEntityDelegate::new)
+ .constructor(MapProtocolMapperEntity.class, HotRodProtocolMapperEntityDelegate::new)
+ .constructor(MapGroupEntity.class, HotRodGroupEntityDelegate::new)
.build();
- public static final Map, HotRodEntityDescriptor>> ENTITY_DESCRIPTOR_MAP = new HashMap<>();
+ public static final Map, HotRodEntityDescriptor, ?>> ENTITY_DESCRIPTOR_MAP = new HashMap<>();
static {
// Clients descriptor
ENTITY_DESCRIPTOR_MAP.put(ClientModel.class,
new HotRodEntityDescriptor<>(ClientModel.class,
- MapClientEntity.class,
- Arrays.asList(HotRodClientEntity.class, HotRodAttributeEntity.class, HotRodProtocolMapperEntity.class, HotRodPair.class),
- "clients"));
+ HotRodClientEntity.class,
+ HotRodClientEntityDelegate::new));
+
+ // Groups descriptor
+ ENTITY_DESCRIPTOR_MAP.put(GroupModel.class,
+ new HotRodEntityDescriptor<>(GroupModel.class,
+ HotRodGroupEntity.class,
+ HotRodGroupEntityDelegate::new));
}
@Override
@@ -72,7 +80,7 @@ public MapStorageProvider create(KeycloakSession session) {
return new HotRodMapStorageProvider(this, cacheProvider, CLONER);
}
- public HotRodEntityDescriptor> getEntityDescriptor(Class> c) {
+ public HotRodEntityDescriptor, ?> getEntityDescriptor(Class> c) {
return ENTITY_DESCRIPTOR_MAP.get(c);
}
@@ -98,6 +106,6 @@ public boolean isSupported() {
@Override
public String getHelpText() {
- return "HotRod client storage";
+ return "HotRod map storage";
}
}
diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/IckleQueryMapModelCriteriaBuilder.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/IckleQueryMapModelCriteriaBuilder.java
index 07cbc4b35551..ac2420f573bd 100644
--- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/IckleQueryMapModelCriteriaBuilder.java
+++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/IckleQueryMapModelCriteriaBuilder.java
@@ -18,8 +18,11 @@
package org.keycloak.models.map.storage.hotRod;
import org.keycloak.models.ClientModel;
-import org.keycloak.models.map.common.AbstractEntity;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
+import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
import org.keycloak.storage.SearchableModelField;
import java.util.Arrays;
@@ -32,25 +35,44 @@
import static org.keycloak.models.map.storage.hotRod.IckleQueryOperators.C;
import static org.keycloak.models.map.storage.hotRod.IckleQueryOperators.findAvailableNamedParam;
+import static org.keycloak.models.map.storage.hotRod.common.ProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE;
-public class IckleQueryMapModelCriteriaBuilder implements ModelCriteriaBuilder> {
+public class IckleQueryMapModelCriteriaBuilder implements ModelCriteriaBuilder> {
private static final int INITIAL_BUILDER_CAPACITY = 250;
+ private final Class hotRodEntityClass;
private final StringBuilder whereClauseBuilder = new StringBuilder(INITIAL_BUILDER_CAPACITY);
private final Map parameters;
+ private static final String NON_ANALYZED_FIELD_REGEX = "[%_\\\\]";
+ private static final String ANALYZED_FIELD_REGEX = "[+!^\"~*?:\\\\]";
public static final Map, String> INFINISPAN_NAME_OVERRIDES = new HashMap<>();
+ public static final Set> ANALYZED_MODEL_FIELDS = new HashSet<>();
+
static {
INFINISPAN_NAME_OVERRIDES.put(ClientModel.SearchableFields.SCOPE_MAPPING_ROLE, "scopeMappings");
INFINISPAN_NAME_OVERRIDES.put(ClientModel.SearchableFields.ATTRIBUTE, "attributes");
+
+ INFINISPAN_NAME_OVERRIDES.put(GroupModel.SearchableFields.PARENT_ID, "parentId");
+ INFINISPAN_NAME_OVERRIDES.put(GroupModel.SearchableFields.ASSIGNED_ROLE, "grantedRoles");
+ }
+
+ static {
+ // the "filename" analyzer in Infinispan works correctly for case-insensitive search with whitespaces
+ ANALYZED_MODEL_FIELDS.add(RoleModel.SearchableFields.DESCRIPTION);
+ ANALYZED_MODEL_FIELDS.add(UserModel.SearchableFields.FIRST_NAME);
+ ANALYZED_MODEL_FIELDS.add(UserModel.SearchableFields.LAST_NAME);
+ ANALYZED_MODEL_FIELDS.add(UserModel.SearchableFields.EMAIL);
}
- public IckleQueryMapModelCriteriaBuilder(StringBuilder whereClauseBuilder, Map parameters) {
+ public IckleQueryMapModelCriteriaBuilder(Class hotRodEntityClass, StringBuilder whereClauseBuilder, Map parameters) {
+ this.hotRodEntityClass = hotRodEntityClass;
this.whereClauseBuilder.append(whereClauseBuilder);
this.parameters = parameters;
}
- public IckleQueryMapModelCriteriaBuilder() {
+ public IckleQueryMapModelCriteriaBuilder(Class hotRodEntityClass) {
+ this.hotRodEntityClass = hotRodEntityClass;
this.parameters = new HashMap<>();
}
@@ -63,7 +85,7 @@ private static boolean notEmpty(StringBuilder builder) {
}
@Override
- public IckleQueryMapModelCriteriaBuilder compare(SearchableModelField super M> modelField, Operator op, Object... value) {
+ public IckleQueryMapModelCriteriaBuilder compare(SearchableModelField super M> modelField, Operator op, Object... value) {
StringBuilder newBuilder = new StringBuilder(INITIAL_BUILDER_CAPACITY);
newBuilder.append("(");
@@ -78,17 +100,17 @@ public IckleQueryMapModelCriteriaBuilder compare(SearchableModelField
newBuilder.append(")");
}
- return new IckleQueryMapModelCriteriaBuilder<>(newBuilder.append(")"), newParameters);
+ return new IckleQueryMapModelCriteriaBuilder<>(hotRodEntityClass, newBuilder.append(")"), newParameters);
}
- private StringBuilder joinBuilders(IckleQueryMapModelCriteriaBuilder[] builders, String delimiter) {
+ private StringBuilder joinBuilders(IckleQueryMapModelCriteriaBuilder[] builders, String delimiter) {
return new StringBuilder(INITIAL_BUILDER_CAPACITY).append("(").append(Arrays.stream(builders)
.map(IckleQueryMapModelCriteriaBuilder::getWhereClauseBuilder)
.filter(IckleQueryMapModelCriteriaBuilder::notEmpty)
.collect(Collectors.joining(delimiter))).append(")");
}
- private Map joinParameters(IckleQueryMapModelCriteriaBuilder[] builders) {
+ private Map joinParameters(IckleQueryMapModelCriteriaBuilder[] builders) {
return Arrays.stream(builders)
.map(IckleQueryMapModelCriteriaBuilder::getParameters)
.map(Map::entrySet)
@@ -97,7 +119,7 @@ private Map joinParameters(IckleQueryMapModelCriteriaBuilder[] resolveNamedQueryConflicts(IckleQueryMapModelCriteriaBuilder[] builders) {
+ private IckleQueryMapModelCriteriaBuilder[] resolveNamedQueryConflicts(IckleQueryMapModelCriteriaBuilder[] builders) {
final Set existingKeys = new HashSet<>();
return Arrays.stream(builders).map(builder -> {
@@ -123,36 +145,36 @@ private IckleQueryMapModelCriteriaBuilder[] resolveNamedQueryConflicts(
}
}
- return new IckleQueryMapModelCriteriaBuilder<>(new StringBuilder(newWhereClause), newParameters);
+ return new IckleQueryMapModelCriteriaBuilder<>(hotRodEntityClass, new StringBuilder(newWhereClause), newParameters);
}).toArray(IckleQueryMapModelCriteriaBuilder[]::new);
}
@Override
- public IckleQueryMapModelCriteriaBuilder and(IckleQueryMapModelCriteriaBuilder... builders) {
+ public IckleQueryMapModelCriteriaBuilder and(IckleQueryMapModelCriteriaBuilder... builders) {
if (builders.length == 0) {
- return new IckleQueryMapModelCriteriaBuilder<>();
+ return new IckleQueryMapModelCriteriaBuilder<>(hotRodEntityClass);
}
builders = resolveNamedQueryConflicts(builders);
- return new IckleQueryMapModelCriteriaBuilder<>(joinBuilders(builders, " AND "),
+ return new IckleQueryMapModelCriteriaBuilder<>(hotRodEntityClass, joinBuilders(builders, " AND "),
joinParameters(builders));
}
@Override
- public IckleQueryMapModelCriteriaBuilder or(IckleQueryMapModelCriteriaBuilder... builders) {
+ public IckleQueryMapModelCriteriaBuilder or(IckleQueryMapModelCriteriaBuilder... builders) {
if (builders.length == 0) {
- return new IckleQueryMapModelCriteriaBuilder<>();
+ return new IckleQueryMapModelCriteriaBuilder<>(hotRodEntityClass);
}
builders = resolveNamedQueryConflicts(builders);
- return new IckleQueryMapModelCriteriaBuilder<>(joinBuilders(builders, " OR "),
+ return new IckleQueryMapModelCriteriaBuilder<>(hotRodEntityClass, joinBuilders(builders, " OR "),
joinParameters(builders));
}
@Override
- public IckleQueryMapModelCriteriaBuilder not(IckleQueryMapModelCriteriaBuilder builder) {
+ public IckleQueryMapModelCriteriaBuilder not(IckleQueryMapModelCriteriaBuilder builder) {
StringBuilder newBuilder = new StringBuilder(INITIAL_BUILDER_CAPACITY);
StringBuilder originalBuilder = builder.getWhereClauseBuilder();
@@ -160,19 +182,56 @@ public IckleQueryMapModelCriteriaBuilder not(IckleQueryMapModelCriteria
newBuilder.append("not").append(originalBuilder);
}
- return new IckleQueryMapModelCriteriaBuilder<>(newBuilder, builder.getParameters());
+ return new IckleQueryMapModelCriteriaBuilder<>(hotRodEntityClass, newBuilder, builder.getParameters());
}
private StringBuilder getWhereClauseBuilder() {
return whereClauseBuilder;
}
+ public static Object sanitize(Object value) {
+ if (value instanceof String) {
+ String sValue = (String) value;
+ boolean anyBeginning = sValue.startsWith("%");
+ boolean anyEnd = sValue.endsWith("%");
+
+ String sanitizedString = sValue.substring(anyBeginning ? 1 : 0, sValue.length() - (anyEnd ? 1 : 0))
+ .replaceAll(NON_ANALYZED_FIELD_REGEX, "\\\\\\\\" + "$0");
+
+ return (anyBeginning ? "%" : "") + sanitizedString + (anyEnd ? "%" : "");
+ }
+
+ return value;
+ }
+
+ public static Object sanitizeAnalyzed(Object value) {
+ if (value instanceof String) {
+ String sValue = (String) value;
+ boolean anyBeginning = sValue.startsWith("%");
+ boolean anyEnd = sValue.endsWith("%");
+
+ String sanitizedString = sValue.substring(anyBeginning ? 1 : 0, sValue.length() - (anyEnd ? 1 : 0))
+ .replaceAll("\\\\", "\\\\\\\\"); // escape "\" with extra "\"
+ // .replaceAll(ANALYZED_FIELD_REGEX, "\\\\\\\\" + "$0"); skipped for now because Infinispan is not able to escape
+ // special characters for analyzed fields
+ // TODO reevaluate once https://github.com/keycloak/keycloak/issues/9295 is fixed
+
+ return (anyBeginning ? "*" : "") + sanitizedString + (anyEnd ? "*" : "");
+ }
+
+ return value;
+ }
+
+ public static boolean isAnalyzedModelField(SearchableModelField> modelField) {
+ return ANALYZED_MODEL_FIELDS.contains(modelField);
+ }
+
/**
*
* @return Ickle query that represents this QueryBuilder
*/
public String getIckleQuery() {
- return "FROM org.keycloak.models.map.storage.hotrod.HotRodClientEntity " + C + ((whereClauseBuilder.length() != 0) ? " WHERE " + whereClauseBuilder : "");
+ return "FROM " + HOT_ROD_ENTITY_PACKAGE + "." + hotRodEntityClass.getSimpleName() + " " + C + ((whereClauseBuilder.length() != 0) ? " WHERE " + whereClauseBuilder : "");
}
/**
diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/IckleQueryOperators.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/IckleQueryOperators.java
index c962c8015ea2..00aa690b53d0 100644
--- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/IckleQueryOperators.java
+++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/IckleQueryOperators.java
@@ -26,6 +26,7 @@
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
@@ -35,10 +36,10 @@
*
* For example,
*
- * for operator {@link ModelCriteriaBuilder.Operator.EQ} we concatenate left operand and right operand with equal sign:
+ * for operator {@link ModelCriteriaBuilder.Operator#EQ} we concatenate left operand and right operand with equal sign:
* {@code fieldName = :parameterName}
*
- * however, for operator {@link ModelCriteriaBuilder.Operator.EXISTS} we add following:
+ * however, for operator {@link ModelCriteriaBuilder.Operator#EXISTS} we add following:
*
* {@code fieldName IS NOT NULL AND fieldName IS NOT EMPTY"}.
*
@@ -46,7 +47,7 @@
* corresponding value is then saved into {@code Map} that is passed to each {@link ExpressionCombinator}.
*/
public class IckleQueryOperators {
- private static final String UNWANTED_CHARACTERS_REGEX = "[^a-zA-Z\\d]";
+ private static final Pattern UNWANTED_CHARACTERS_REGEX = Pattern.compile("[^a-zA-Z\\d]");
public static final String C = "c";
private static final Map OPERATOR_TO_STRING = new HashMap<>();
private static final Map OPERATOR_TO_EXPRESSION_COMBINATORS = new HashMap<>();
@@ -55,6 +56,8 @@ public class IckleQueryOperators {
OPERATOR_TO_EXPRESSION_COMBINATORS.put(ModelCriteriaBuilder.Operator.IN, IckleQueryOperators::in);
OPERATOR_TO_EXPRESSION_COMBINATORS.put(ModelCriteriaBuilder.Operator.EXISTS, IckleQueryOperators::exists);
OPERATOR_TO_EXPRESSION_COMBINATORS.put(ModelCriteriaBuilder.Operator.NOT_EXISTS, IckleQueryOperators::notExists);
+ OPERATOR_TO_EXPRESSION_COMBINATORS.put(ModelCriteriaBuilder.Operator.ILIKE, IckleQueryOperators::iLike);
+ OPERATOR_TO_EXPRESSION_COMBINATORS.put(ModelCriteriaBuilder.Operator.LIKE, IckleQueryOperators::like);
OPERATOR_TO_STRING.put(ModelCriteriaBuilder.Operator.EQ, "=");
OPERATOR_TO_STRING.put(ModelCriteriaBuilder.Operator.NE, "!=");
@@ -81,17 +84,29 @@ private interface ExpressionCombinator {
String combine(String fieldName, Object[] values, Map parameters);
}
- private static String exists(String modelField, Object[] values, Map parameters) {
- String field = C + "." + modelField;
+ private static String exists(String modelFieldName, Object[] values, Map parameters) {
+ String field = C + "." + modelFieldName;
return field + " IS NOT NULL AND " + field + " IS NOT EMPTY";
}
- private static String notExists(String modelField, Object[] values, Map parameters) {
- String field = C + "." + modelField;
+ private static String notExists(String modelFieldName, Object[] values, Map parameters) {
+ String field = C + "." + modelFieldName;
return field + " IS NULL OR " + field + " IS EMPTY";
}
- private static String in(String modelField, Object[] values, Map parameters) {
+ private static String iLike(String modelFieldName, Object[] values, Map parameters) {
+ String sanitizedValue = (String) IckleQueryMapModelCriteriaBuilder.sanitize(values[0]);
+ return singleValueOperator(ModelCriteriaBuilder.Operator.ILIKE)
+ .combine(modelFieldName + "Lowercase", new String[] {sanitizedValue.toLowerCase()}, parameters);
+ }
+
+ private static String like(String modelFieldName, Object[] values, Map parameters) {
+ String sanitizedValue = (String) IckleQueryMapModelCriteriaBuilder.sanitize(values[0]);
+ return singleValueOperator(ModelCriteriaBuilder.Operator.LIKE)
+ .combine(modelFieldName, new String[] {sanitizedValue}, parameters);
+ }
+
+ private static String in(String modelFieldName, Object[] values, Map parameters) {
if (values == null || values.length == 0) {
return "false";
}
@@ -112,9 +127,9 @@ private static String in(String modelField, Object[] values, Map
operands = new HashSet<>(Arrays.asList(values));
}
- return C + "." + modelField + " IN (" + operands.stream()
+ return operands.isEmpty() ? "false" : C + "." + modelFieldName + " IN (" + operands.stream()
.map(operand -> {
- String namedParam = findAvailableNamedParam(parameters.keySet(), modelField);
+ String namedParam = findAvailableNamedParam(parameters.keySet(), modelFieldName);
parameters.put(namedParam, operand);
return ":" + namedParam;
})
@@ -123,14 +138,18 @@ private static String in(String modelField, Object[] values, Map
}
private static String removeForbiddenCharactersFromNamedParameter(String name) {
- return name.replaceAll(UNWANTED_CHARACTERS_REGEX, "");
+ return UNWANTED_CHARACTERS_REGEX.matcher(name).replaceAll( "");
}
/**
* Maps {@code namePrefix} to next available parameter name. For example, if {@code namePrefix == "id"}
* and {@code existingNames} set already contains {@code id0} and {@code id1} it returns {@code id2}.
+ * Any character that is not an alphanumeric will be stripped, so that it works for prefixes like
+ * {@code "attributes.name"} as well.
*
- * This method is used for computing available names for name query parameters
+ * This method is used for computing available names for name query parameters.
+ * Instead of creating generic named parameters that would be hard to debug and read by humans, it creates readable
+ * named parameters from the prefix.
*
* @param existingNames set of parameter names that are already used in this Ickle query
* @param namePrefix name of the parameter
@@ -149,9 +168,13 @@ private static ExpressionCombinator singleValueOperator(ModelCriteriaBuilder.Ope
return (modelFieldName, values, parameters) -> {
if (values.length != 1) throw new RuntimeException("Invalid arguments, expected (" + modelFieldName + "), got: " + Arrays.toString(values));
- String namedParameter = findAvailableNamedParam(parameters.keySet(), modelFieldName);
+ if (values[0] == null && op.equals(ModelCriteriaBuilder.Operator.EQ)) {
+ return C + "." + modelFieldName + " IS NULL";
+ }
+ String namedParameter = findAvailableNamedParam(parameters.keySet(), modelFieldName);
parameters.put(namedParameter, values[0]);
+
return C + "." + modelFieldName + " " + IckleQueryOperators.operatorToString(op) + " :" + namedParameter;
};
}
@@ -168,13 +191,13 @@ private static ExpressionCombinator operatorToExpressionCombinator(ModelCriteria
* Provides a string containing where clause for given operator, field name and values
*
* @param op operator
- * @param filedName field name
+ * @param modelFieldName field name
* @param values values
* @param parameters mapping between named parameters and their values
* @return where clause
*/
- public static String combineExpressions(ModelCriteriaBuilder.Operator op, String filedName, Object[] values, Map parameters) {
- return operatorToExpressionCombinator(op).combine(filedName, values, parameters);
+ public static String combineExpressions(ModelCriteriaBuilder.Operator op, String modelFieldName, Object[] values, Map parameters) {
+ return operatorToExpressionCombinator(op).combine(modelFieldName, values, parameters);
}
-}
\ No newline at end of file
+}
diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/IckleQueryWhereClauses.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/IckleQueryWhereClauses.java
index 6bcafa853d42..16a2e8a4dffb 100644
--- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/IckleQueryWhereClauses.java
+++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/IckleQueryWhereClauses.java
@@ -26,6 +26,9 @@
import java.util.HashMap;
import java.util.Map;
+import static org.keycloak.models.map.storage.hotRod.IckleQueryMapModelCriteriaBuilder.sanitizeAnalyzed;
+import static org.keycloak.models.map.storage.hotRod.IckleQueryOperators.C;
+
/**
* This class provides knowledge on how to build Ickle query where clauses for specified {@link SearchableModelField}.
*
@@ -71,8 +74,20 @@ private static WhereClauseProducer whereClauseProducerForModelField(SearchableMo
*/
public static String produceWhereClause(SearchableModelField> modelField, ModelCriteriaBuilder.Operator op,
Object[] values, Map parameters) {
- return whereClauseProducerForModelField(modelField)
- .produceWhereClause(IckleQueryMapModelCriteriaBuilder.getFieldName(modelField), op, values, parameters);
+ String fieldName = IckleQueryMapModelCriteriaBuilder.getFieldName(modelField);
+
+ if (IckleQueryMapModelCriteriaBuilder.isAnalyzedModelField(modelField) &&
+ (op.equals(ModelCriteriaBuilder.Operator.ILIKE) || op.equals(ModelCriteriaBuilder.Operator.EQ) || op.equals(ModelCriteriaBuilder.Operator.NE))) {
+
+ String clause = C + "." + fieldName + " : '" + sanitizeAnalyzed(values[0]) + "'";
+ if (op.equals(ModelCriteriaBuilder.Operator.NE)) {
+ return "not(" + clause + ")";
+ }
+
+ return clause;
+ }
+
+ return whereClauseProducerForModelField(modelField).produceWhereClause(fieldName, op, values, parameters);
}
private static String whereClauseForClientsAttributes(String modelFieldName, ModelCriteriaBuilder.Operator op, Object[] values, Map parameters) {
diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/client/HotRodClientEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/client/HotRodClientEntity.java
index 3f2c75292c06..629d379bd70c 100644
--- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/client/HotRodClientEntity.java
+++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/client/HotRodClientEntity.java
@@ -17,698 +17,188 @@
package org.keycloak.models.map.storage.hotRod.client;
+import org.infinispan.protostream.annotations.ProtoDoc;
import org.infinispan.protostream.annotations.ProtoField;
-import org.keycloak.models.map.client.MapClientEntity;
-import org.keycloak.models.map.client.MapProtocolMapperEntity;
-import org.keycloak.models.map.common.DeepCloner;
+import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
+import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
+import org.keycloak.models.map.storage.hotRod.common.HotRodAttributeEntity;
+import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDelegate;
import org.keycloak.models.map.storage.hotRod.common.HotRodPair;
-import org.keycloak.models.map.storage.hotRod.common.Versioned;
+import org.keycloak.models.map.client.MapClientEntity;
+import org.keycloak.models.map.common.UpdatableEntity;
import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
+import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
-import java.util.Optional;
import java.util.Set;
-import java.util.function.Function;
-import java.util.stream.Collectors;
import java.util.stream.Stream;
-public class HotRodClientEntity implements MapClientEntity, Versioned {
+
+@GenerateHotRodEntityImplementation(
+ implementInterface = "org.keycloak.models.map.client.MapClientEntity",
+ inherits = "org.keycloak.models.map.storage.hotRod.client.HotRodClientEntity.AbstractHotRodClientEntityDelegate"
+)
+@ProtoDoc("@Indexed")
+public class HotRodClientEntity implements AbstractHotRodEntity {
@ProtoField(number = 1, required = true)
public int entityVersion = 1;
+ @ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
@ProtoField(number = 2, required = true)
public String id;
+ @ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
@ProtoField(number = 3)
public String realmId;
+ @ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
@ProtoField(number = 4)
public String clientId;
+ /**
+ * Lowercase interpretation of {@link #clientId} field. Infinispan doesn't support case-insensitive LIKE for non-analyzed fields.
+ * Search on analyzed fields can be case-insensitive (based on used analyzer) but doesn't support ORDER BY analyzed field.
+ */
+ @ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
@ProtoField(number = 5)
- public String name;
+ public String clientIdLowercase;
@ProtoField(number = 6)
- public String description;
+ public String name;
@ProtoField(number = 7)
- public Set redirectUris = new HashSet<>();
+ public String description;
@ProtoField(number = 8)
- public Boolean enabled;
+ public Set redirectUris;
@ProtoField(number = 9)
- public Boolean alwaysDisplayInConsole;
-
+ public Boolean enabled;
+
@ProtoField(number = 10)
- public String clientAuthenticatorType;
+ public Boolean alwaysDisplayInConsole;
@ProtoField(number = 11)
- public String secret;
+ public String clientAuthenticatorType;
@ProtoField(number = 12)
- public String registrationToken;
+ public String secret;
@ProtoField(number = 13)
- public String protocol;
+ public String registrationToken;
@ProtoField(number = 14)
- public Set attributes = new HashSet<>();
+ public String protocol;
+ @ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
@ProtoField(number = 15)
- public Set> authFlowBindings = new HashSet<>();
+ public Set attributes;
@ProtoField(number = 16)
- public Boolean publicClient;
+ public Set> authenticationFlowBindingOverrides;
@ProtoField(number = 17)
- public Boolean fullScopeAllowed;
+ public Boolean publicClient;
@ProtoField(number = 18)
- public Boolean frontchannelLogout;
+ public Boolean fullScopeAllowed;
@ProtoField(number = 19)
- public Integer notBefore;
+ public Boolean frontchannelLogout;
@ProtoField(number = 20)
- public Set scope = new HashSet<>();
+ public Integer notBefore;
@ProtoField(number = 21)
- public Set webOrigins = new HashSet<>();
+ public Set scope;
@ProtoField(number = 22)
- public Set protocolMappers = new HashSet<>();
+ public Set webOrigins;
@ProtoField(number = 23)
- public Set> clientScopes = new HashSet<>();
+ public Set protocolMappers;
@ProtoField(number = 24)
- public Set scopeMappings = new HashSet<>();
+ public Set> clientScopes;
- @ProtoField(number = 25)
- public Boolean surrogateAuthRequired;
+ @ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
+ @ProtoField(number = 25, collectionImplementation = LinkedList.class)
+ public Collection scopeMappings;
@ProtoField(number = 26)
- public String managementUrl;
+ public Boolean surrogateAuthRequired;
@ProtoField(number = 27)
- public String baseUrl;
+ public String managementUrl;
@ProtoField(number = 28)
- public Boolean bearerOnly;
+ public String baseUrl;
@ProtoField(number = 29)
- public Boolean consentRequired;
+ public Boolean bearerOnly;
@ProtoField(number = 30)
- public String rootUrl;
+ public Boolean consentRequired;
@ProtoField(number = 31)
- public Boolean standardFlowEnabled;
+ public String rootUrl;
@ProtoField(number = 32)
- public Boolean implicitFlowEnabled;
+ public Boolean standardFlowEnabled;
@ProtoField(number = 33)
- public Boolean directAccessGrantsEnabled;
+ public Boolean implicitFlowEnabled;
@ProtoField(number = 34)
- public Boolean serviceAccountsEnabled;
+ public Boolean directAccessGrantsEnabled;
@ProtoField(number = 35)
- public Integer nodeReRegistrationTimeout;
-
- private boolean updated = false;
-
- private final DeepCloner cloner;
-
- public HotRodClientEntity() {
- this(DeepCloner.DUMB_CLONER);
- }
-
- public HotRodClientEntity(DeepCloner cloner) {
- this.cloner = cloner;
- }
-
- @Override
- public int getEntityVersion() {
- return entityVersion;
- }
-
- @Override
- public List getAttribute(String name) {
- return attributes.stream()
- .filter(attributeEntity -> Objects.equals(attributeEntity.getName(), name))
- .findFirst()
- .map(HotRodAttributeEntity::getValues)
- .orElse(Collections.emptyList());
- }
-
- @Override
- public Map> getAttributes() {
- return attributes.stream().collect(Collectors.toMap(HotRodAttributeEntity::getName, HotRodAttributeEntity::getValues));
- }
-
- @Override
- public void setAttribute(String name, List values) {
- boolean valueUndefined = values == null || values.isEmpty();
-
- Optional first = attributes.stream()
- .filter(attributeEntity -> Objects.equals(attributeEntity.getName(), name))
- .findFirst();
-
- if (first.isPresent()) {
- HotRodAttributeEntity attributeEntity = first.get();
- if (valueUndefined) {
- this.updated = true;
- removeAttribute(name);
- } else {
- this.updated |= !Objects.equals(attributeEntity.getValues(), values);
- attributeEntity.setValues(values);
- }
-
- return;
- }
-
- // do not create attributes if empty or null
- if (valueUndefined) {
- return;
- }
-
- HotRodAttributeEntity newAttributeEntity = new HotRodAttributeEntity(name, values);
- updated |= attributes.add(newAttributeEntity);
- }
-
- @Override
- public void removeAttribute(String name) {
- attributes.stream()
- .filter(attributeEntity -> Objects.equals(attributeEntity.getName(), name))
- .findFirst()
- .ifPresent(attr -> {
- this.updated |= attributes.remove(attr);
- });
- }
-
- @Override
- public String getClientId() {
- return clientId;
- }
-
- @Override
- public void setClientId(String clientId) {
- this.updated |= ! Objects.equals(this.clientId, clientId);
- this.clientId = clientId;
- }
-
- @Override
- public String getName() {
- return name;
- }
-
- @Override
- public void setName(String name) {
- this.updated |= ! Objects.equals(this.name, name);
- this.name = name;
- }
-
- @Override
- public String getDescription() {
- return description;
- }
-
- @Override
- public void setDescription(String description) {
- this.updated |= ! Objects.equals(this.description, description);
- this.description = description;
- }
-
- @Override
- public Set getRedirectUris() {
- return redirectUris;
- }
-
- @Override
- public void setRedirectUris(Set redirectUris) {
- if (redirectUris == null || redirectUris.isEmpty()) {
- this.updated |= !this.redirectUris.isEmpty();
- this.redirectUris.clear();
- return;
- }
-
- this.updated |= ! Objects.equals(this.redirectUris, redirectUris);
- this.redirectUris.clear();
- this.redirectUris.addAll(redirectUris);
- }
-
- @Override
- public Boolean isEnabled() {
- return enabled;
- }
-
- @Override
- public void setEnabled(Boolean enabled) {
- this.updated |= ! Objects.equals(this.enabled, enabled);
- this.enabled = enabled;
- }
-
- @Override
- public Boolean isAlwaysDisplayInConsole() {
- return alwaysDisplayInConsole;
- }
-
- @Override
- public void setAlwaysDisplayInConsole(Boolean alwaysDisplayInConsole) {
- this.updated |= ! Objects.equals(this.alwaysDisplayInConsole, alwaysDisplayInConsole);
- this.alwaysDisplayInConsole = alwaysDisplayInConsole;
- }
-
- @Override
- public String getClientAuthenticatorType() {
- return clientAuthenticatorType;
- }
-
- @Override
- public void setClientAuthenticatorType(String clientAuthenticatorType) {
- this.updated |= ! Objects.equals(this.clientAuthenticatorType, clientAuthenticatorType);
- this.clientAuthenticatorType = clientAuthenticatorType;
- }
-
- @Override
- public String getSecret() {
- return secret;
- }
-
- @Override
- public void setSecret(String secret) {
- this.updated |= ! Objects.equals(this.secret, secret);
- this.secret = secret;
- }
-
- @Override
- public String getRegistrationToken() {
- return registrationToken;
- }
-
- @Override
- public void setRegistrationToken(String registrationToken) {
- this.updated |= ! Objects.equals(this.registrationToken, registrationToken);
- this.registrationToken = registrationToken;
- }
-
- @Override
- public String getProtocol() {
- return protocol;
- }
-
- @Override
- public void setProtocol(String protocol) {
- this.updated |= ! Objects.equals(this.protocol, protocol);
- this.protocol = protocol;
- }
-
- @Override
- public Map getAuthFlowBindings() {
- return authFlowBindings.stream().collect(Collectors.toMap(HotRodPair::getFirst, HotRodPair::getSecond));
- }
-
- @Override
- public void setAuthFlowBindings(Map authFlowBindings) {
- if (authFlowBindings == null || authFlowBindings.isEmpty()) {
- this.updated |= !this.authFlowBindings.isEmpty();
- this.authFlowBindings.clear();
- return;
- }
-
- this.updated = true;
- this.authFlowBindings.clear();
- this.authFlowBindings.addAll(authFlowBindings.entrySet().stream().map(e -> new HotRodPair<>(e.getKey(), e.getValue())).collect(Collectors.toSet()));
- }
-
- @Override
- public Boolean isPublicClient() {
- return publicClient;
- }
-
- @Override
- public void setPublicClient(Boolean publicClient) {
- this.updated |= ! Objects.equals(this.publicClient, publicClient);
- this.publicClient = publicClient;
- }
-
- @Override
- public void setRealmId(String realmId) {
- this.realmId = realmId;
- }
-
- @Override
- public Boolean isFullScopeAllowed() {
- return fullScopeAllowed;
- }
-
- @Override
- public void setFullScopeAllowed(Boolean fullScopeAllowed) {
- this.updated |= ! Objects.equals(this.fullScopeAllowed, fullScopeAllowed);
- this.fullScopeAllowed = fullScopeAllowed;
- }
-
- @Override
- public Boolean isFrontchannelLogout() {
- return frontchannelLogout;
- }
-
- @Override
- public void setFrontchannelLogout(Boolean frontchannelLogout) {
- this.updated |= ! Objects.equals(this.frontchannelLogout, frontchannelLogout);
- this.frontchannelLogout = frontchannelLogout;
- }
-
- @Override
- public Integer getNotBefore() {
- return notBefore;
- }
+ public Boolean serviceAccountsEnabled;
- @Override
- public void setNotBefore(Integer notBefore) {
- this.updated |= ! Objects.equals(this.notBefore, notBefore);
- this.notBefore = notBefore;
- }
+ @ProtoField(number = 36)
+ public Integer nodeReRegistrationTimeout;
- @Override
- public Set getScope() {
- return scope;
- }
+ public static abstract class AbstractHotRodClientEntityDelegate extends UpdatableEntity.Impl implements HotRodEntityDelegate, MapClientEntity {
- @Override
- public void setScope(Set scope) {
- if (scope == null || scope.isEmpty()) {
- this.updated |= !this.scope.isEmpty();
- this.scope.clear();
- return;
+ @Override
+ public String getId() {
+ return getHotRodEntity().id;
}
- this.updated |= ! Objects.equals(this.scope, scope);
- this.scope.clear();
- this.scope.addAll(scope);
- }
-
- @Override
- public Set getWebOrigins() {
- return webOrigins;
- }
-
- @Override
- public void setWebOrigins(Set webOrigins) {
- if (webOrigins == null || webOrigins.isEmpty()) {
- this.updated |= !this.webOrigins.isEmpty();
- this.webOrigins.clear();
- return;
+ @Override
+ public void setId(String id) {
+ HotRodClientEntity entity = getHotRodEntity();
+ if (entity.id != null) throw new IllegalStateException("Id cannot be changed");
+ entity.id = id;
+ this.updated |= id != null;
}
- this.updated |= ! Objects.equals(this.webOrigins, webOrigins);
- this.webOrigins.clear();
- this.webOrigins.addAll(webOrigins);
- }
-
- @Override
- public Map getProtocolMappers() {
- return protocolMappers.stream().collect(Collectors.toMap(HotRodProtocolMapperEntity::getId, Function.identity()));
- }
-
-
- @Override
- public MapProtocolMapperEntity getProtocolMapper(String id) {
- return protocolMappers.stream().filter(hotRodMapper -> Objects.equals(hotRodMapper.getId(), id)).findFirst().orElse(null);
- }
-
- @Override
- public void setProtocolMapper(String id, MapProtocolMapperEntity mapping) {
- removeProtocolMapper(id);
-
- protocolMappers.add((HotRodProtocolMapperEntity) cloner.from(mapping)); // Workaround, will be replaced by cloners
- this.updated = true;
- }
-
- @Override
- public void removeProtocolMapper(String id) {
- protocolMappers.stream().filter(entity -> Objects.equals(id, entity.id))
- .findFirst()
- .ifPresent(entity -> {
- protocolMappers.remove(entity);
- updated = true;
- });
- }
-
- @Override
- public Boolean isSurrogateAuthRequired() {
- return surrogateAuthRequired;
- }
-
- @Override
- public void setSurrogateAuthRequired(Boolean surrogateAuthRequired) {
- this.updated |= ! Objects.equals(this.surrogateAuthRequired, surrogateAuthRequired);
- this.surrogateAuthRequired = surrogateAuthRequired;
- }
-
- @Override
- public String getManagementUrl() {
- return managementUrl;
- }
-
- @Override
- public void setManagementUrl(String managementUrl) {
- this.updated |= ! Objects.equals(this.managementUrl, managementUrl);
- this.managementUrl = managementUrl;
- }
-
- @Override
- public String getRootUrl() {
- return rootUrl;
- }
-
- @Override
- public void setRootUrl(String rootUrl) {
- this.updated |= ! Objects.equals(this.rootUrl, rootUrl);
- this.rootUrl = rootUrl;
- }
-
- @Override
- public String getBaseUrl() {
- return baseUrl;
- }
-
- @Override
- public void setBaseUrl(String baseUrl) {
- this.updated |= ! Objects.equals(this.baseUrl, baseUrl);
- this.baseUrl = baseUrl;
- }
-
- @Override
- public Boolean isBearerOnly() {
- return bearerOnly;
- }
-
- @Override
- public void setBearerOnly(Boolean bearerOnly) {
- this.updated |= ! Objects.equals(this.bearerOnly, bearerOnly);
- this.bearerOnly = bearerOnly;
- }
-
- @Override
- public Boolean isConsentRequired() {
- return consentRequired;
- }
-
- @Override
- public void setConsentRequired(Boolean consentRequired) {
- this.updated |= ! Objects.equals(this.consentRequired, consentRequired);
- this.consentRequired = consentRequired;
- }
-
- @Override
- public Boolean isStandardFlowEnabled() {
- return standardFlowEnabled;
- }
-
- @Override
- public void setStandardFlowEnabled(Boolean standardFlowEnabled) {
- this.updated |= ! Objects.equals(this.standardFlowEnabled, standardFlowEnabled);
- this.standardFlowEnabled = standardFlowEnabled;
- }
-
- @Override
- public Boolean isImplicitFlowEnabled() {
- return implicitFlowEnabled;
- }
-
- @Override
- public void setImplicitFlowEnabled(Boolean implicitFlowEnabled) {
- this.updated |= ! Objects.equals(this.implicitFlowEnabled, implicitFlowEnabled);
- this.implicitFlowEnabled = implicitFlowEnabled;
- }
-
- @Override
- public Boolean isDirectAccessGrantsEnabled() {
- return directAccessGrantsEnabled;
- }
-
- @Override
- public void setDirectAccessGrantsEnabled(Boolean directAccessGrantsEnabled) {
- this.updated |= ! Objects.equals(this.directAccessGrantsEnabled, directAccessGrantsEnabled);
- this.directAccessGrantsEnabled = directAccessGrantsEnabled;
- }
-
- @Override
- public Boolean isServiceAccountsEnabled() {
- return serviceAccountsEnabled;
- }
-
- @Override
- public void setServiceAccountsEnabled(Boolean serviceAccountsEnabled) {
- this.updated |= ! Objects.equals(this.serviceAccountsEnabled, serviceAccountsEnabled);
- this.serviceAccountsEnabled = serviceAccountsEnabled;
- }
-
- @Override
- public Integer getNodeReRegistrationTimeout() {
- return nodeReRegistrationTimeout;
- }
-
- @Override
- public void setNodeReRegistrationTimeout(Integer nodeReRegistrationTimeout) {
- this.updated |= ! Objects.equals(this.nodeReRegistrationTimeout, nodeReRegistrationTimeout);
- this.nodeReRegistrationTimeout = nodeReRegistrationTimeout;
- }
-
- @Override
- public void addWebOrigin(String webOrigin) {
- updated = true;
- this.webOrigins.add(webOrigin);
- }
-
- @Override
- public void removeWebOrigin(String webOrigin) {
- updated |= this.webOrigins.remove(webOrigin);
- }
-
- @Override
- public void addRedirectUri(String redirectUri) {
- this.updated |= ! this.redirectUris.contains(redirectUri);
- this.redirectUris.add(redirectUri);
- }
-
- @Override
- public void removeRedirectUri(String redirectUri) {
- updated |= this.redirectUris.remove(redirectUri);
- }
-
- @Override
- public String getAuthenticationFlowBindingOverride(String binding) {
- return authFlowBindings.stream().filter(pair -> Objects.equals(pair.getFirst(), binding)).findFirst()
- .map(HotRodPair::getSecond)
- .orElse(null);
- }
-
- @Override
- public Map getAuthenticationFlowBindingOverrides() {
- return this.authFlowBindings.stream().collect(Collectors.toMap(HotRodPair::getFirst, HotRodPair::getSecond));
- }
-
- @Override
- public void removeAuthenticationFlowBindingOverride(String binding) {
- this.authFlowBindings.stream().filter(pair -> Objects.equals(pair.getFirst(), binding)).findFirst()
- .ifPresent(pair -> {
- updated = true;
- authFlowBindings.remove(pair);
- });
- }
-
- @Override
- public void setAuthenticationFlowBindingOverride(String binding, String flowId) {
- this.updated = true;
-
- removeAuthenticationFlowBindingOverride(binding);
-
- this.authFlowBindings.add(new HotRodPair<>(binding, flowId));
- }
-
- @Override
- public Collection getScopeMappings() {
- return scopeMappings;
- }
-
- @Override
- public void addScopeMapping(String id) {
- if (id != null) {
- updated = true;
- scopeMappings.add(id);
+ @Override
+ public void setClientId(String clientId) {
+ HotRodClientEntity entity = getHotRodEntity();
+ this.updated |= ! Objects.equals(entity.clientId, clientId);
+ entity.clientId = clientId;
+ entity.clientIdLowercase = clientId == null ? null : clientId.toLowerCase();
}
- }
- @Override
- public void removeScopeMapping(String id) {
- updated |= scopeMappings.remove(id);
- }
-
- @Override
- public Map getClientScopes() {
- return this.clientScopes.stream().collect(Collectors.toMap(HotRodPair::getFirst, HotRodPair::getSecond));
- }
-
- @Override
- public void setClientScope(String id, Boolean defaultScope) {
- if (id != null) {
- updated = true;
- removeClientScope(id);
-
- this.clientScopes.add(new HotRodPair<>(id, defaultScope));
+ @Override
+ public Stream getClientScopes(boolean defaultScope) {
+ final Map clientScopes = getClientScopes();
+ return clientScopes == null ? Stream.empty() : clientScopes.entrySet().stream()
+ .filter(me -> Objects.equals(me.getValue(), defaultScope))
+ .map(Map.Entry::getKey);
}
}
@Override
- public void removeClientScope(String id) {
- this.clientScopes.stream().filter(pair -> Objects.equals(pair.getFirst(), id)).findFirst()
- .ifPresent(pair -> {
- updated = true;
- clientScopes.remove(pair);
- });
- }
-
- @Override
- public Stream getClientScopes(boolean defaultScope) {
- return this.clientScopes.stream()
- .filter(pair -> Objects.equals(pair.getSecond(), defaultScope))
- .map(HotRodPair::getFirst);
- }
-
- @Override
- public String getRealmId() {
- return this.realmId;
- }
-
- @Override
- public String getId() {
- return id;
- }
-
- @Override
- public void setId(String id) {
- if (this.id != null) throw new IllegalStateException("Id cannot be changed");
- this.id = id;
- this.updated |= id != null;
- }
-
- @Override
- public void clearUpdatedFlag() {
- this.updated = false;
+ public boolean equals(Object o) {
+ return HotRodClientEntityDelegate.entityEquals(this, o);
}
@Override
- public boolean isUpdated() {
- return updated;
+ public int hashCode() {
+ return HotRodClientEntityDelegate.entityHashCode(this);
}
}
diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/client/HotRodProtocolMapperEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/client/HotRodProtocolMapperEntity.java
index 0b7a7e19dca9..3d9aa3285bc4 100644
--- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/client/HotRodProtocolMapperEntity.java
+++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/client/HotRodProtocolMapperEntity.java
@@ -18,16 +18,15 @@
package org.keycloak.models.map.storage.hotRod.client;
import org.infinispan.protostream.annotations.ProtoField;
-import org.keycloak.models.map.client.MapProtocolMapperEntity;
+import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
+import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
import org.keycloak.models.map.storage.hotRod.common.HotRodPair;
-import java.util.LinkedHashSet;
-import java.util.Map;
import java.util.Objects;
import java.util.Set;
-import java.util.stream.Collectors;
-public class HotRodProtocolMapperEntity implements MapProtocolMapperEntity {
+@GenerateHotRodEntityImplementation(implementInterface = "org.keycloak.models.map.client.MapProtocolMapperEntity")
+public class HotRodProtocolMapperEntity implements AbstractHotRodEntity {
@ProtoField(number = 1)
public String id;
@ProtoField(number = 2)
@@ -41,73 +40,15 @@ public class HotRodProtocolMapperEntity implements MapProtocolMapperEntity {
// @ProtoField(number = 5)
// public String consentText;
@ProtoField(number = 5)
- public Set> config = new LinkedHashSet<>();
-
- private boolean updated;
+ public Set> config;
@Override
public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- HotRodProtocolMapperEntity entity = (HotRodProtocolMapperEntity) o;
-
- return id.equals(entity.id);
+ return HotRodProtocolMapperEntityDelegate.entityEquals(this, o);
}
@Override
public int hashCode() {
- return id.hashCode();
- }
-
- @Override
- public String getId() {
- return id;
- }
-
- @Override
- public void setId(String id) {
- updated |= !Objects.equals(this.id, id);
- this.id = id;
- }
-
- @Override
- public String getName() {
- return name;
- }
-
- @Override
- public void setName(String name) {
- updated |= !Objects.equals(this.name, name);
- this.name = name;
- }
-
- @Override
- public String getProtocolMapper() {
- return protocolMapper;
- }
-
- @Override
- public void setProtocolMapper(String protocolMapper) {
- updated |= !Objects.equals(this.protocolMapper, protocolMapper);
- this.protocolMapper = protocolMapper;
- }
-
- @Override
- public Map getConfig() {
- return config.stream().collect(Collectors.toMap(HotRodPair::getFirst, HotRodPair::getSecond));
- }
-
- @Override
- public void setConfig(Map config) {
- updated |= !Objects.equals(this.config, config);
- this.config.clear();
-
- config.entrySet().stream().map(entry -> new HotRodPair<>(entry.getKey(), entry.getValue())).forEach(this.config::add);
- }
-
- @Override
- public boolean isUpdated() {
- return updated;
+ return HotRodProtocolMapperEntityDelegate.entityHashCode(this);
}
}
diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/AbstractHotRodEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/AbstractHotRodEntity.java
new file mode 100644
index 000000000000..85cfbb80365a
--- /dev/null
+++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/AbstractHotRodEntity.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2021 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.map.storage.hotRod.common;
+
+public interface AbstractHotRodEntity {
+}
diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/client/HotRodAttributeEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodAttributeEntity.java
similarity index 87%
rename from model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/client/HotRodAttributeEntity.java
rename to model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodAttributeEntity.java
index ca5ded6c02af..fabacb4f7df4 100644
--- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/client/HotRodAttributeEntity.java
+++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodAttributeEntity.java
@@ -15,18 +15,22 @@
* limitations under the License.
*/
-package org.keycloak.models.map.storage.hotRod.client;
+package org.keycloak.models.map.storage.hotRod.common;
+import org.infinispan.protostream.annotations.ProtoDoc;
import org.infinispan.protostream.annotations.ProtoField;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
+@ProtoDoc("@Indexed")
public class HotRodAttributeEntity {
+ @ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
@ProtoField(number = 1)
public String name;
+ @ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
@ProtoField(number = 2)
public List values = new LinkedList<>();
diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodAttributeEntityNonIndexed.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodAttributeEntityNonIndexed.java
new file mode 100644
index 000000000000..746ab20dda66
--- /dev/null
+++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodAttributeEntityNonIndexed.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2021 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.map.storage.hotRod.common;
+
+import org.infinispan.protostream.annotations.ProtoDoc;
+import org.infinispan.protostream.annotations.ProtoField;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+
+
+public class HotRodAttributeEntityNonIndexed {
+ @ProtoField(number = 1)
+ public String name;
+
+ @ProtoField(number = 2)
+ public List values = new LinkedList<>();
+
+ public HotRodAttributeEntityNonIndexed() {
+ }
+
+ public HotRodAttributeEntityNonIndexed(String name, List values) {
+ this.name = name;
+ this.values.addAll(values);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public List getValues() {
+ return values;
+ }
+
+ public void setValues(List values) {
+ this.values = values;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ HotRodAttributeEntityNonIndexed that = (HotRodAttributeEntityNonIndexed) o;
+ return Objects.equals(name, that.name) && Objects.equals(values, that.values);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, values);
+ }
+}
diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodEntityDelegate.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodEntityDelegate.java
new file mode 100644
index 000000000000..b63d0db9a5cf
--- /dev/null
+++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodEntityDelegate.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2021 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.map.storage.hotRod.common;
+
+import org.keycloak.models.map.common.AbstractEntity;
+import org.keycloak.models.map.common.UpdatableEntity;
+
+public interface HotRodEntityDelegate extends AbstractEntity, UpdatableEntity {
+ E getHotRodEntity();
+}
diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodEntityDescriptor.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodEntityDescriptor.java
index 5fd43f28898d..c4b73777dfc4 100644
--- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodEntityDescriptor.java
+++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodEntityDescriptor.java
@@ -17,35 +17,34 @@
package org.keycloak.models.map.storage.hotRod.common;
-import java.util.List;
-import java.util.stream.Stream;
+import org.keycloak.models.map.storage.ModelEntityUtil;
-public class HotRodEntityDescriptor {
+import java.util.function.Function;
+
+public class HotRodEntityDescriptor> {
private final Class> modelTypeClass;
- private final Class entityTypeClass;
- private final List> hotRodClasses;
- private final String cacheName;
+ private final Class entityTypeClass;
+ private final Function hotRodDelegateProvider;
- public HotRodEntityDescriptor(Class> modelTypeClass, Class entityTypeClass, List> hotRodClasses, String cacheName) {
+ public HotRodEntityDescriptor(Class> modelTypeClass, Class entityTypeClass, Function hotRodDelegateProvider) {
this.modelTypeClass = modelTypeClass;
this.entityTypeClass = entityTypeClass;
- this.hotRodClasses = hotRodClasses;
- this.cacheName = cacheName;
+ this.hotRodDelegateProvider = hotRodDelegateProvider;
}
public Class> getModelTypeClass() {
return modelTypeClass;
}
- public Class getEntityTypeClass() {
+ public Class getEntityTypeClass() {
return entityTypeClass;
}
- public Stream> getHotRodClasses() {
- return hotRodClasses.stream();
+ public String getCacheName() {
+ return ModelEntityUtil.getModelName(modelTypeClass);
}
- public String getCacheName() {
- return cacheName;
+ public Function getHotRodDelegateProvider() {
+ return hotRodDelegateProvider;
}
}
diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodPair.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodPair.java
index d025e0d6d3e7..e893c26cd6eb 100644
--- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodPair.java
+++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodPair.java
@@ -20,33 +20,48 @@
import org.infinispan.protostream.WrappedMessage;
import org.infinispan.protostream.annotations.ProtoField;
+import java.util.Objects;
+
public class HotRodPair {
@ProtoField(number = 1)
- public WrappedMessage firstWrapped;
+ public WrappedMessage key;
@ProtoField(number = 2)
- public WrappedMessage secondWrapped;
+ public WrappedMessage value;
public HotRodPair() {}
public HotRodPair(T first, V second) {
- this.firstWrapped = new WrappedMessage(first);
- this.secondWrapped = new WrappedMessage(second);
+ this.key = new WrappedMessage(first);
+ this.value = new WrappedMessage(second);
+ }
+
+ public T getKey() {
+ return key == null ? null : (T) key.getValue();
+ }
+
+ public V getValue() {
+ return value == null ? null : (V) value.getValue();
}
- public T getFirst() {
- return firstWrapped == null ? null : (T) firstWrapped.getValue();
+ public void setKey(T first) {
+ this.key = new WrappedMessage(first);
}
- public V getSecond() {
- return secondWrapped == null ? null : (V) secondWrapped.getValue();
+ public void setValue(V second) {
+ this.value = new WrappedMessage(second);
}
- public void setFirst(T first) {
- this.firstWrapped = new WrappedMessage(first);
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ HotRodPair, ?> that = (HotRodPair, ?>) o;
+ return Objects.equals(key, that.key) && Objects.equals(value, that.value);
}
- public void setSecond(V second) {
- this.secondWrapped = new WrappedMessage(second);
+ @Override
+ public int hashCode() {
+ return Objects.hash(key, value);
}
}
diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodTypesUtils.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodTypesUtils.java
new file mode 100644
index 000000000000..7421180bbaf9
--- /dev/null
+++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodTypesUtils.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2021 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.map.storage.hotRod.common;
+
+import org.keycloak.models.map.common.AbstractEntity;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+public class HotRodTypesUtils {
+
+ public static Set migrateMapToSet(Map map, Function, SetValue> creator) {
+ return map == null ? null : map.entrySet()
+ .stream()
+ .map(creator)
+ .collect(Collectors.toSet());
+ }
+
+ public static Map migrateSetToMap(Set set, Function keyProducer, Function valueProducer) {
+ return set == null ? null : set.stream().collect(Collectors.toMap(keyProducer, valueProducer));
+ }
+
+ public static HotRodPair createHotRodPairFromMapEntry(Map.Entry entry) {
+ return new HotRodPair<>(entry.getKey(), entry.getValue());
+ }
+
+ public static HotRodAttributeEntity createHotRodAttributeEntityFromMapEntry(Map.Entry> entry) {
+ return new HotRodAttributeEntity(entry.getKey(), entry.getValue());
+ }
+
+ public static HotRodAttributeEntityNonIndexed createHotRodAttributeEntityNonIndexedFromMapEntry(Map.Entry> entry) {
+ return new HotRodAttributeEntityNonIndexed(entry.getKey(), entry.getValue());
+ }
+
+ public static boolean removeFromSetByMapKey(Set set, KeyType key, Function keyGetter) {
+ if (set == null || set.isEmpty()) { return false; }
+ return set.stream()
+ .filter(entry -> Objects.equals(keyGetter.apply(entry), key))
+ .findFirst()
+ .map(set::remove)
+ .orElse(false);
+ }
+
+ public static MapValue getMapValueFromSet(Set set, MapKey key, Function keyGetter, Function valueGetter) {
+ return set == null ? null : set.stream().filter(entry -> Objects.equals(keyGetter.apply(entry), key)).findFirst().map(valueGetter).orElse(null);
+ }
+
+ public static K getKey(HotRodPair hotRodPair) {
+ return hotRodPair.getKey();
+ }
+
+ public static V getValue(HotRodPair hotRodPair) {
+ return hotRodPair.getValue();
+ }
+
+ public static String getKey(HotRodAttributeEntity attributeEntity) {
+ return attributeEntity.name;
+ }
+
+ public static String getKey(HotRodAttributeEntityNonIndexed attributeEntity) {
+ return attributeEntity.name;
+ }
+
+ public static List getValue(HotRodAttributeEntity attributeEntity) {
+ return attributeEntity.values;
+ }
+
+ public static List getValue(HotRodAttributeEntityNonIndexed attributeEntity) {
+ return attributeEntity.values;
+ }
+
+ public static String getKey(AbstractEntity entity) {
+ return entity.getId();
+ }
+}
diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/ProtoSchemaInitializer.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/ProtoSchemaInitializer.java
index 1385d1512507..579bb5065896 100644
--- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/ProtoSchemaInitializer.java
+++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/ProtoSchemaInitializer.java
@@ -19,23 +19,32 @@
import org.infinispan.protostream.GeneratedSchema;
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
-import org.keycloak.models.map.storage.hotRod.client.HotRodAttributeEntity;
import org.keycloak.models.map.storage.hotRod.client.HotRodClientEntity;
import org.keycloak.models.map.storage.hotRod.client.HotRodProtocolMapperEntity;
+import org.keycloak.models.map.storage.hotRod.group.HotRodGroupEntity;
/**
* @author Martin Kanis
*/
@AutoProtoSchemaBuilder(
includeClasses = {
- HotRodAttributeEntity.class,
+ // Clients
HotRodClientEntity.class,
HotRodProtocolMapperEntity.class,
- HotRodPair.class
+
+ // Groups
+ HotRodGroupEntity.class,
+
+ // Common
+ HotRodPair.class,
+ HotRodAttributeEntity.class,
+ HotRodAttributeEntityNonIndexed.class
},
schemaFileName = "KeycloakHotRodMapStorage.proto",
schemaFilePath = "proto/",
- schemaPackageName = "org.keycloak.models.map.storage.hotrod")
+ schemaPackageName = ProtoSchemaInitializer.HOT_ROD_ENTITY_PACKAGE)
public interface ProtoSchemaInitializer extends GeneratedSchema {
+ String HOT_ROD_ENTITY_PACKAGE = "kc";
+
ProtoSchemaInitializer INSTANCE = new ProtoSchemaInitializerImpl();
}
diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/Versioned.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/Versioned.java
index d099e4222f88..e69de29bb2d1 100644
--- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/Versioned.java
+++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/Versioned.java
@@ -1,22 +0,0 @@
-/*
- * Copyright 2021 Red Hat, Inc. and/or its affiliates
- * and other contributors as indicated by the @author tags.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.keycloak.models.map.storage.hotRod.common;
-
-public interface Versioned {
- int getEntityVersion();
-}
diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/connections/DefaultHotRodConnectionProviderFactory.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/connections/DefaultHotRodConnectionProviderFactory.java
index 52301ffc4427..75a98cad9270 100644
--- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/connections/DefaultHotRodConnectionProviderFactory.java
+++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/connections/DefaultHotRodConnectionProviderFactory.java
@@ -18,6 +18,7 @@
import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.client.hotrod.RemoteCacheManager;
+import org.infinispan.client.hotrod.RemoteCacheManagerAdmin;
import org.infinispan.client.hotrod.configuration.ClientIntelligence;
import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
import org.infinispan.commons.marshall.ProtoStreamMarshaller;
@@ -33,6 +34,9 @@
import java.net.URI;
import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
/**
* @author Martin Kanis
@@ -104,14 +108,31 @@ public void lazyInit() {
remoteBuilder.addContextInitializer(ProtoSchemaInitializer.INSTANCE);
remoteCacheManager = new RemoteCacheManager(remoteBuilder.build());
+ Set remoteCaches = HotRodMapStorageProviderFactory.ENTITY_DESCRIPTOR_MAP.values().stream()
+ .map(HotRodEntityDescriptor::getCacheName).collect(Collectors.toSet());
+
if (configureRemoteCaches) {
// access the caches to force their creation
- HotRodMapStorageProviderFactory.ENTITY_DESCRIPTOR_MAP.values().stream()
- .map(HotRodEntityDescriptor::getCacheName)
- .forEach(remoteCacheManager::getCache);
+ remoteCaches.forEach(remoteCacheManager::getCache);
}
registerSchemata(ProtoSchemaInitializer.INSTANCE);
+
+ RemoteCacheManagerAdmin administration = remoteCacheManager.administration();
+ if (config.getBoolean("reindexAllCaches", false)) {
+ LOG.infof("Reindexing all caches. This can take a long time to complete. While the rebuild operation is in progress, queries might return fewer results.");
+ remoteCaches.forEach(administration::reindexCache);
+ } else {
+ String reindexCaches = config.get("reindexCaches", "");
+ if (reindexCaches != null) {
+ Arrays.stream(reindexCaches.split(","))
+ .map(String::trim)
+ .filter(e -> !e.isEmpty())
+ .filter(remoteCaches::contains)
+ .peek(cacheName -> LOG.infof("Reindexing %s cache. This can take a long time to complete. While the rebuild operation is in progress, queries might return fewer results.", cacheName))
+ .forEach(administration::reindexCache);
+ }
+ }
}
private void registerSchemata(GeneratedSchema initializer) {
diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/group/HotRodGroupEntity.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/group/HotRodGroupEntity.java
new file mode 100644
index 000000000000..c3e52f30e77d
--- /dev/null
+++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/group/HotRodGroupEntity.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2021 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.map.storage.hotRod.group;
+
+import org.infinispan.protostream.annotations.ProtoDoc;
+import org.infinispan.protostream.annotations.ProtoField;
+import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
+import org.keycloak.models.map.common.UpdatableEntity;
+import org.keycloak.models.map.group.MapGroupEntity;
+import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
+import org.keycloak.models.map.storage.hotRod.common.HotRodAttributeEntityNonIndexed;
+import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDelegate;
+
+import java.util.Objects;
+import java.util.Set;
+
+@GenerateHotRodEntityImplementation(
+ implementInterface = "org.keycloak.models.map.group.MapGroupEntity",
+ inherits = "org.keycloak.models.map.storage.hotRod.group.HotRodGroupEntity.AbstractHotRodGroupEntityDelegate"
+)
+@ProtoDoc("@Indexed")
+public class HotRodGroupEntity implements AbstractHotRodEntity {
+
+ public static abstract class AbstractHotRodGroupEntityDelegate extends UpdatableEntity.Impl implements HotRodEntityDelegate, MapGroupEntity {
+
+ @Override
+ public String getId() {
+ return getHotRodEntity().id;
+ }
+
+ @Override
+ public void setId(String id) {
+ HotRodGroupEntity entity = getHotRodEntity();
+ if (entity.id != null) throw new IllegalStateException("Id cannot be changed");
+ entity.id = id;
+ this.updated |= id != null;
+ }
+
+ @Override
+ public void setName(String name) {
+ HotRodGroupEntity entity = getHotRodEntity();
+ updated |= ! Objects.equals(entity.name, name);
+ entity.name = name;
+ entity.nameLowercase = name == null ? null : name.toLowerCase();
+ }
+ }
+
+ @ProtoField(number = 1, required = true)
+ public int entityVersion = 1;
+
+ @ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
+ @ProtoField(number = 2, required = true)
+ public String id;
+
+ @ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
+ @ProtoField(number = 3)
+ public String realmId;
+
+ @ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
+ @ProtoField(number = 4)
+ public String name;
+
+ /**
+ * Lowercase interpretation of {@link #name} field. Infinispan doesn't support case-insensitive LIKE for non-analyzed fields.
+ * Search on analyzed fields can be case-insensitive (based on used analyzer) but doesn't support ORDER BY analyzed field.
+ */
+ @ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
+ @ProtoField(number = 5)
+ public String nameLowercase;
+
+ @ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
+ @ProtoField(number = 6)
+ public String parentId;
+
+ @ProtoField(number = 7)
+ public Set attributes;
+
+ @ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
+ @ProtoField(number = 8)
+ public Set grantedRoles;
+}
diff --git a/model/map-hot-rod/src/main/resources/config/cacheConfig.xml b/model/map-hot-rod/src/main/resources/config/cacheConfig.xml
index 351576ee744e..d63b26aa2164 100644
--- a/model/map-hot-rod/src/main/resources/config/cacheConfig.xml
+++ b/model/map-hot-rod/src/main/resources/config/cacheConfig.xml
@@ -4,6 +4,20 @@