From 3a793916a90d2543e4ba9108552b8b45857ce0fa Mon Sep 17 00:00:00 2001 From: Jon Koops Date: Wed, 12 Feb 2025 17:31:21 +0100 Subject: [PATCH] Remove Keycloak JS from repository (#37057) Closes #36645 Signed-off-by: Jon Koops --- .github/CODEOWNERS | 1 - .github/actions/conditional/conditions | 1 - .../downloads/src/main/resources/files | 1 - js/README.md | 3 +- js/apps/account-ui/package.json | 2 +- js/apps/admin-ui/package.json | 2 +- js/eslint.config.js | 2 - js/libs/keycloak-js/README.md | 3 - js/libs/keycloak-js/lib/keycloak-authz.d.ts | 143 -- js/libs/keycloak-js/lib/keycloak-authz.js | 296 --- js/libs/keycloak-js/lib/keycloak.d.ts | 660 ------ js/libs/keycloak-js/lib/keycloak.js | 1873 ----------------- js/libs/keycloak-js/package.json | 36 - js/libs/keycloak-js/pom.xml | 94 - js/libs/keycloak-js/tsconfig.json | 5 - js/libs/ui-shared/package.json | 2 +- js/pnpm-lock.yaml | 21 +- js/pom.xml | 1 - set-version.sh | 1 - .../services/testsuite-providers/pom.xml | 37 - .../rest/TestingResourceProvider.java | 7 - .../rest/resource/TestJavascriptResource.java | 79 - .../src/main/resources/javascript/index.html | 90 - .../resources/javascript/init-in-head.html | 77 - .../main/resources/javascript/keycloak.json | 8 - .../javascript/silent-check-sso.html | 1 - .../client/resources/TestingResource.java | 10 - .../util/javascript/JSObjectBuilder.java | 149 -- .../javascript/JavascriptStateValidator.java | 14 - .../javascript/JavascriptTestExecutor.java | 391 ---- .../util/javascript/ResponseValidator.java | 12 - .../util/javascript/XMLHttpRequest.java | 91 - .../javascript/AbstractJavascriptTest.java | 231 -- .../javascript/JavascriptAdapterTest.java | 969 --------- .../tests/base/testsuites/base-suite | 1 - 35 files changed, 16 insertions(+), 5298 deletions(-) delete mode 100644 js/libs/keycloak-js/README.md delete mode 100644 js/libs/keycloak-js/lib/keycloak-authz.d.ts delete mode 100644 js/libs/keycloak-js/lib/keycloak-authz.js delete mode 100644 js/libs/keycloak-js/lib/keycloak.d.ts delete mode 100755 js/libs/keycloak-js/lib/keycloak.js delete mode 100644 js/libs/keycloak-js/package.json delete mode 100644 js/libs/keycloak-js/pom.xml delete mode 100644 js/libs/keycloak-js/tsconfig.json delete mode 100644 testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestJavascriptResource.java delete mode 100644 testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/javascript/index.html delete mode 100644 testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/javascript/init-in-head.html delete mode 100644 testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/javascript/keycloak.json delete mode 100644 testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/javascript/silent-check-sso.html delete mode 100644 testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/JSObjectBuilder.java delete mode 100644 testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/JavascriptStateValidator.java delete mode 100644 testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/JavascriptTestExecutor.java delete mode 100644 testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/ResponseValidator.java delete mode 100644 testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/XMLHttpRequest.java delete mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/javascript/AbstractJavascriptTest.java delete mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/javascript/JavascriptAdapterTest.java diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 16704cd6fcc..1e62b86e6d8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -30,7 +30,6 @@ # Core Clients (@keycloak/core-clients-maintainers) ################################################################################################### -/js/libs/keycloak-js/ @keycloak/core-clients-maintainers ################################################################################################### # Cloud Native (@keycloak/cloud-native-maintainers) diff --git a/.github/actions/conditional/conditions b/.github/actions/conditional/conditions index a75129ca99f..a233d809d45 100644 --- a/.github/actions/conditional/conditions +++ b/.github/actions/conditional/conditions @@ -37,7 +37,6 @@ rest/admin-ui-ext/ js services/ js js/apps/account-ui/ ci ci-webauthn js/libs/ui-shared/ ci ci-webauthn -js/libs/keycloak-js/ ci ci-quarkus # The sections below contain a sub-set of files existing in the project which are supported languages by CodeQL. # See: https://codeql.github.com/docs/codeql-overview/supported-languages-and-frameworks/ diff --git a/distribution/downloads/src/main/resources/files b/distribution/downloads/src/main/resources/files index 72c0a1ef161..219a5b3a84a 100644 --- a/distribution/downloads/src/main/resources/files +++ b/distribution/downloads/src/main/resources/files @@ -4,7 +4,6 @@ mvn:keycloak-api-docs-dist:keycloak-api-docs mvn:documentation/keycloak-documentation:keycloak-documentation npm:js/libs/keycloak-admin-client/target/keycloak-keycloak-admin-client-$$VERSION$$.tgz:keycloak-admin-client-$$VERSION$$.tgz -npm:js/libs/keycloak-js/target/keycloak-js-$$VERSION$$.tgz:keycloak-js-$$VERSION$$.tgz npm:js/libs/ui-shared/target/keycloak-keycloak-ui-shared-$$VERSION$$.tgz:keycloak-ui-shared-$$VERSION$$.tgz npm:js/apps/account-ui/target/keycloak-keycloak-account-ui-$$VERSION$$.tgz:keycloak-account-ui-$$VERSION$$.tgz npm:js/apps/admin-ui/target/keycloak-keycloak-admin-ui-$$VERSION$$.tgz:keycloak-admin-ui-$$VERSION$$.tgz diff --git a/js/README.md b/js/README.md index ae0f43329f5..27a6500a1b9 100644 --- a/js/README.md +++ b/js/README.md @@ -9,8 +9,7 @@ This directory contains the UIs and related libraries of the Keycloak project wr │ ├── admin-ui # Admin UI for handling login, registration, administration, and account management │ └── keycloak-server # Keycloak server for local development of UIs ├── libs - │ ├── keycloak-admin-client # Keycloak Admin Client library for Keycloak REST API - │ └── keycloak-js # Keycloak JS library for securing HTML5/JavaScript applications + │ └── keycloak-admin-client # Keycloak Admin Client library for Keycloak REST API ├── ... ## Data processing diff --git a/js/apps/account-ui/package.json b/js/apps/account-ui/package.json index 369e9e1e7fc..febc26d9dfe 100644 --- a/js/apps/account-ui/package.json +++ b/js/apps/account-ui/package.json @@ -32,7 +32,7 @@ "@patternfly/react-table": "^5.4.14", "i18next": "^24.2.2", "i18next-http-backend": "^3.0.2", - "keycloak-js": "workspace:*", + "keycloak-js": "^26.1.2", "lodash-es": "^4.17.21", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/js/apps/admin-ui/package.json b/js/apps/admin-ui/package.json index d6f1f8a7c4e..3eab26f78b5 100644 --- a/js/apps/admin-ui/package.json +++ b/js/apps/admin-ui/package.json @@ -103,7 +103,7 @@ "i18next": "^24.2.2", "i18next-http-backend": "^3.0.2", "jszip": "^3.10.1", - "keycloak-js": "workspace:*", + "keycloak-js": "^26.1.2", "lodash-es": "^4.17.21", "p-debounce": "^4.0.0", "react": "^18.3.1", diff --git a/js/eslint.config.js b/js/eslint.config.js index 2aa0fdcf697..d18f3ce75b4 100644 --- a/js/eslint.config.js +++ b/js/eslint.config.js @@ -22,8 +22,6 @@ export default tseslint.config( "**/lib/", "**/target/", "./apps/keycloak-server/server/", - // Keycloak JS follows a completely different and outdated style, so we'll exclude it for now. - "./libs/keycloak-js/", ], }, eslint.configs.recommended, diff --git a/js/libs/keycloak-js/README.md b/js/libs/keycloak-js/README.md deleted file mode 100644 index cfeaf7cb096..00000000000 --- a/js/libs/keycloak-js/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Keycloak JS - -The documentation can be found in the [Keycloak documentation](https://www.keycloak.org/securing-apps/javascript-adapter). diff --git a/js/libs/keycloak-js/lib/keycloak-authz.d.ts b/js/libs/keycloak-js/lib/keycloak-authz.d.ts deleted file mode 100644 index a035cf9b491..00000000000 --- a/js/libs/keycloak-js/lib/keycloak-authz.d.ts +++ /dev/null @@ -1,143 +0,0 @@ -/* - * MIT License - * - * Copyright 2017 Brett Epps - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the - * following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT - * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN - * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -import Keycloak from './keycloak.js'; - -export interface KeycloakAuthorizationPromise { - then(onGrant: (rpt: string) => void, onDeny: () => void, onError: () => void): void; -} - -export interface AuthorizationRequest { - /** - * An array of objects representing the resource and scopes. - */ - permissions?:ResourcePermission[], - - /** - * A permission ticket obtained from a resource server when using UMA authorization protocol. - */ - ticket?:string, - - /** - * A boolean value indicating whether the server should create permission requests to the resources - * and scopes referenced by a permission ticket. This parameter will only take effect when used together - * with the ticket parameter as part of a UMA authorization process. - */ - submitRequest?:boolean, - - /** - * Defines additional information about this authorization request in order to specify how it should be processed - * by the server. - */ - metadata?:AuthorizationRequestMetadata, - - /** - * Defines whether or not this authorization request should include the current RPT. If set to true, the RPT will - * be sent and permissions in the current RPT will be included in the new RPT. Otherwise, only the permissions referenced in this - * authorization request will be granted in the new RPT. - */ - incrementalAuthorization?:boolean -} - -export interface AuthorizationRequestMetadata { - /** - * A boolean value indicating to the server if resource names should be included in the RPT’s permissions. - * If false, only the resource identifier is included. - */ - responseIncludeResourceName?:any, - - /** - * An integer N that defines a limit for the amount of permissions an RPT can have. When used together with - * rpt parameter, only the last N requested permissions will be kept in the RPT. - */ - response_permissions_limit?:number -} - -export interface ResourcePermission { - /** - * The id or name of a resource. - */ - id:string, - - /** - * An array of strings where each value is the name of a scope associated with the resource. - */ - scopes?:string[] -} - -/** - * @deprecated Instead of importing 'KeycloakAuthorizationInstance' you can import 'KeycloakAuthorization' directly as a type. - */ -export type KeycloakAuthorizationInstance = KeycloakAuthorization; - -/** - * @deprecated Construct a KeycloakAuthorization instance using the `new` keyword instead. - */ -declare function KeycloakAuthorization(keycloak: Keycloak): KeycloakAuthorization; - -declare class KeycloakAuthorization { - /** - * Creates a new Keycloak client instance. - * @param config Path to a JSON config file or a plain config object. - */ - constructor(keycloak: Keycloak) - - rpt: any; - config: { rpt_endpoint: string }; - - /** - * Initializes the `KeycloakAuthorization` instance. - * @deprecated Initialization now happens automatically, calling this method is no longer required. - */ - init(): void; - - /** - * A promise that resolves when the `KeycloakAuthorization` instance is initialized. - * @deprecated Initialization now happens automatically, using this property is no longer required. - */ - ready: Promise; - - /** - * 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. - * - * @param authorizationRequest An AuthorizationRequest instance with a valid permission ticket set. - * @returns A promise to set functions to be invoked on grant, deny or error. - */ - authorize(authorizationRequest: AuthorizationRequest): KeycloakAuthorizationPromise; - - /** - * Obtains all entitlements from a Keycloak server based on a given resourceServerId. - * - * @param resourceServerId The id (client id) of the resource server to obtain permissions from. - * @param authorizationRequest An AuthorizationRequest instance. - * @returns A promise to set functions to be invoked on grant, deny or error. - */ - entitlement(resourceServerId: string, authorizationRequest?: AuthorizationRequest): KeycloakAuthorizationPromise; -} - -export default KeycloakAuthorization; - -/** - * @deprecated The 'KeycloakAuthorization' namespace is deprecated, use named imports instead. - */ -export as namespace KeycloakAuthorization; diff --git a/js/libs/keycloak-js/lib/keycloak-authz.js b/js/libs/keycloak-js/lib/keycloak-authz.js deleted file mode 100644 index 8b4f439134c..00000000000 --- a/js/libs/keycloak-js/lib/keycloak-authz.js +++ /dev/null @@ -1,296 +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. - * - */ - -var KeycloakAuthorization = function (keycloak, options) { - var _instance = this; - this.rpt = null; - - // Only here for backwards compatibility, as the configuration is now loaded on demand. - // See: - // - https://github.com/keycloak/keycloak/pull/6619 - // - https://issues.redhat.com/browse/KEYCLOAK-10894 - // TODO: Remove both `ready` property and `init` method in a future version - Object.defineProperty(this, 'ready', { - get() { - console.warn("The 'ready' property is deprecated and will be removed in a future version. Initialization now happens automatically, using this property is no longer required."); - return Promise.resolve(); - }, - }); - - this.init = () => { - console.warn("The 'init()' method is deprecated and will be removed in a future version. Initialization now happens automatically, calling this method is no longer required."); - }; - - /** @type {Promise | undefined} */ - let configPromise; - - /** - * Initializes the configuration or re-uses the existing one if present. - * @returns {Promise} A promise that resolves when the configuration is loaded. - */ - async function initializeConfigIfNeeded() { - if (_instance.config) { - return _instance.config; - } - - if (configPromise) { - return await configPromise; - } - - if (!keycloak.didInitialize) { - throw new Error('The Keycloak instance has not been initialized yet.'); - } - - configPromise = loadConfig(keycloak.authServerUrl, keycloak.realm); - _instance.config = await configPromise; - } - - /** - * 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 = async function (onGrant, onDeny, onError) { - try { - await initializeConfigIfNeeded(); - } catch (error) { - handleError(error, onError); - return; - } - - 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 = async function (onGrant, onDeny, onError) { - try { - await initializeConfigIfNeeded(); - } catch (error) { - handleError(error, onError); - return; - } - - 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 (var 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; - }; - - return this; -}; - -/** - * Obtains the configuration from the server. - * @param {string} serverUrl The URL of the Keycloak server. - * @param {string} realm The realm name. - * @returns {Promise} A promise that resolves when the configuration is loaded. - */ -async function loadConfig(serverUrl, realm) { - const url = `${serverUrl}/realms/${encodeURIComponent(realm)}/.well-known/uma2-configuration`; - - try { - return await fetchJSON(url); - } catch (error) { - throw new Error('Could not obtain configuration from server.', { cause: error }); - } -} - -/** - * Fetches the JSON data from the given URL. - * @param {string} url The URL to fetch the data from. - * @returns {Promise} A promise that resolves when the data is loaded. - */ -async function fetchJSON(url) { - let response; - - try { - response = await fetch(url); - } catch (error) { - throw new Error('Server did not respond.', { cause: error }); - } - - if (!response.ok) { - throw new Error('Server responded with an invalid status.'); - } - - try { - return await response.json(); - } catch (error) { - throw new Error('Server responded with invalid JSON.', { cause: error }); - } -} - -/** - * @param {unknown} error - * @param {((error: unknown) => void) | undefined} handler - */ -function handleError(error, handler) { - if (handler) { - handler(error); - } else { - console.error(message, error); - } -} - -export default KeycloakAuthorization; diff --git a/js/libs/keycloak-js/lib/keycloak.d.ts b/js/libs/keycloak-js/lib/keycloak.d.ts deleted file mode 100644 index 507d31ecaf7..00000000000 --- a/js/libs/keycloak-js/lib/keycloak.d.ts +++ /dev/null @@ -1,660 +0,0 @@ -/* - * MIT License - * - * Copyright 2017 Brett Epps - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the - * following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or substantial - * portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT - * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN - * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -export type KeycloakOnLoad = 'login-required'|'check-sso'; -export type KeycloakResponseMode = 'query'|'fragment'; -export type KeycloakResponseType = 'code'|'id_token token'|'code id_token token'; -export type KeycloakFlow = 'standard'|'implicit'|'hybrid'; -export type KeycloakPkceMethod = 'S256' | false; - -export interface KeycloakConfig { - /** - * URL to the Keycloak server, for example: http://keycloak-server/auth - */ - url: string; - /** - * Name of the realm, for example: 'myrealm' - */ - realm: string; - /** - * Client identifier, example: 'myapp' - */ - clientId: string; -} - -export interface Acr { - /** - * Array of values, which will be used inside ID Token `acr` claim sent inside the `claims` parameter to Keycloak server during login. - * Values should correspond to the ACR levels defined in the ACR to Loa mapping for realm or client or to the numbers (levels) inside defined - * Keycloak authentication flow. See section 5.5.1 of OIDC 1.0 specification for the details. - */ - values: string[]; - /** - * This parameter specifies if ACR claims is considered essential or not. - */ - essential: boolean; -} - -export interface KeycloakInitOptions { - /** - * Adds a [cryptographic nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce) - * to verify that the authentication response matches the request. - * @default true - */ - useNonce?: boolean; - - /** - * - * Allow usage of different types of adapters or a custom adapter to make Keycloak work in different environments. - * - * The following options are supported: - * - `default` - Use default APIs that are available in browsers. - * - `cordova` - Use a WebView in Cordova. - * - `cordova-native` - Use Cordova native APIs, this is recommended over `cordova`. - * - * It's also possible to pass in a custom adapter for the environment you are running Keycloak in. In order to do so extend the `KeycloakAdapter` interface and implement the methods that are defined there. - * - * For example: - * - * ```ts - * // Implement the 'KeycloakAdapter' interface so that all required methods are guaranteed to be present. - * const MyCustomAdapter: KeycloakAdapter = { - * login(options) { - * // Write your own implementation here. - * } - * - * // The other methods go here... - * }; - * - * keycloak.init({ - * adapter: MyCustomAdapter, - * }); - * ``` - */ - adapter?: 'default' | 'cordova' | 'cordova-native' | KeycloakAdapter; - - /** - * Specifies an action to do on load. - */ - onLoad?: KeycloakOnLoad; - - /** - * Set an initial value for the token. - */ - token?: string; - - /** - * Set an initial value for the refresh token. - */ - refreshToken?: string; - - /** - * Set an initial value for the id token (only together with `token` or - * `refreshToken`). - */ - idToken?: string; - - /** - * Set an initial value for skew between local time and Keycloak server in - * seconds (only together with `token` or `refreshToken`). - */ - timeSkew?: number; - - /** - * Set to enable/disable monitoring login state. - * @default true - */ - checkLoginIframe?: boolean; - - /** - * Set the interval to check login state (in seconds). - * @default 5 - */ - checkLoginIframeInterval?: number; - - /** - * Set the OpenID Connect response mode to send to Keycloak upon login. - * @default fragment After successful authentication Keycloak will redirect - * to JavaScript application with OpenID Connect parameters - * added in URL fragment. This is generally safer and - * recommended over query. - */ - responseMode?: KeycloakResponseMode; - - /** - * Specifies a default uri to redirect to after login or logout. - * This is currently supported for adapter 'cordova-native' and 'default' - */ - redirectUri?: string; - - /** - * Specifies an uri to redirect to after silent check-sso. - * Silent check-sso will only happen, when this redirect uri is given and - * the specified uri is available within the application. - */ - silentCheckSsoRedirectUri?: string; - - /** - * Specifies whether the silent check-sso should fallback to "non-silent" - * check-sso when 3rd party cookies are blocked by the browser. Defaults - * to true. - */ - silentCheckSsoFallback?: boolean; - - /** - * Set the OpenID Connect flow. - * @default standard - */ - flow?: KeycloakFlow; - - /** - * Configures the Proof Key for Code Exchange (PKCE) method to use. This will default to 'S256'. - * Can be disabled by passing `false`. - */ - pkceMethod?: KeycloakPkceMethod; - - /** - * Configures the 'acr_values' query param in compliance with section 3.1.2.1 - * of the OIDC 1.0 specification. - * Used to tell Keycloak what level of authentication the user needs. - */ - acrValues?: string; - - /** - * Enables logging messages from Keycloak to the console. - * @default false - */ - enableLogging?: boolean - - /** - * Set the default scope parameter to the login endpoint. Use a space-delimited list of scopes. - * Note that the scope 'openid' will be always be added to the list of scopes by the adapter. - * Note that the default scope specified here is overwritten if the `login()` options specify scope explicitly. - */ - scope?: string - - /** - * Configures how long will Keycloak adapter wait for receiving messages from server in ms. This is used, - * for example, when waiting for response of 3rd party cookies check. - * - * @default 10000 - */ - messageReceiveTimeout?: number - - /** - * When onLoad is 'login-required', sets the 'ui_locales' query param in compliance with section 3.1.2.1 - * of the OIDC 1.0 specification. - */ - locale?: string; - - /** - * HTTP method for calling the end_session endpoint. Defaults to 'GET'. - */ - logoutMethod?: 'GET' | 'POST'; -} - -export interface KeycloakLoginOptions { - /** - * Specifies the scope parameter for the login url - * The scope 'openid' will be added to the scope if it is missing or undefined. - */ - scope?: string; - - /** - * Specifies the uri to redirect to after login. - */ - redirectUri?: string; - - /** - * By default the login screen is displayed if the user is not logged into - * Keycloak. To only authenticate to the application if the user is already - * logged in and not display the login page if the user is not logged in, set - * this option to `'none'`. To always require re-authentication and ignore - * SSO, set this option to `'login'`. To always prompt the user for consent, - * set this option to `'consent'`. This ensures that consent is requested, - * even if it has been given previously. - */ - prompt?: 'none' | 'login' | 'consent'; - - /** - * If value is `'register'` then user is redirected to registration page, - * otherwise to login page. - */ - action?: string; - - /** - * Used just if user is already authenticated. Specifies maximum time since - * the authentication of user happened. If user is already authenticated for - * longer time than `'maxAge'`, the SSO is ignored and he will need to - * authenticate again. - */ - maxAge?: number; - - /** - * Used to pre-fill the username/email field on the login form. - */ - loginHint?: string; - - /** - * Sets the `acr` claim of the ID token sent inside the `claims` parameter. See section 5.5.1 of the OIDC 1.0 specification. - */ - acr?: Acr; - - /** - * Configures the 'acr_values' query param in compliance with section 3.1.2.1 - * of the OIDC 1.0 specification. - * Used to tell Keycloak what level of authentication the user needs. - */ - acrValues?: string; - - /** - * Used to tell Keycloak which IDP the user wants to authenticate with. - */ - idpHint?: string; - - /** - * Sets the 'ui_locales' query param in compliance with section 3.1.2.1 - * of the OIDC 1.0 specification. - */ - locale?: string; - - /** - * Specifies arguments that are passed to the Cordova in-app-browser (if applicable). - * Options 'hidden' and 'location' are not affected by these arguments. - * All available options are defined at https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-inappbrowser/. - * Example of use: { zoom: "no", hardwareback: "yes" } - */ - cordovaOptions?: { [optionName: string]: string }; -} - -export interface KeycloakLogoutOptions { - /** - * Specifies the uri to redirect to after logout. - */ - redirectUri?: string; - - /** - * HTTP method for calling the end_session endpoint. Defaults to 'GET'. - */ - logoutMethod?: 'GET' | 'POST'; -} - -export interface KeycloakRegisterOptions extends Omit { } - -export interface KeycloakAccountOptions { - /** - * Specifies the uri to redirect to when redirecting back to the application. - */ - redirectUri?: string; -} -export interface KeycloakError { - error: string; - error_description: string; -} - -export interface KeycloakAdapter { - login(options?: KeycloakLoginOptions): Promise; - logout(options?: KeycloakLogoutOptions): Promise; - register(options?: KeycloakRegisterOptions): Promise; - accountManagement(): Promise; - redirectUri(options: { redirectUri: string; }, encodeHash: boolean): string; -} - -export interface KeycloakProfile { - id?: string; - username?: string; - email?: string; - firstName?: string; - lastName?: string; - enabled?: boolean; - emailVerified?: boolean; - totp?: boolean; - createdTimestamp?: number; - attributes?: Record; -} - -export interface KeycloakTokenParsed { - iss?: string; - sub?: string; - aud?: string; - exp?: number; - iat?: number; - auth_time?: number; - nonce?: string; - acr?: string; - amr?: string; - azp?: string; - session_state?: string; - realm_access?: KeycloakRoles; - resource_access?: KeycloakResourceAccess; - [key: string]: any; // Add other attributes here. -} - -export interface KeycloakResourceAccess { - [key: string]: KeycloakRoles -} - -export interface KeycloakRoles { - roles: string[]; -} - -/** - * @deprecated Instead of importing 'KeycloakInstance' you can import 'Keycloak' directly as a type. - */ -export type KeycloakInstance = Keycloak; - -/** - * A client for the Keycloak authentication server. - * @see {@link https://keycloak.gitbooks.io/securing-client-applications-guide/content/topics/oidc/javascript-adapter.html|Keycloak JS adapter documentation} - */ -declare class Keycloak { - /** - * Creates a new Keycloak client instance. - * @param config A configuration object or path to a JSON config file. - */ - constructor(config: KeycloakConfig | string) - - /** - * Is true if the user is authenticated, false otherwise. - */ - authenticated?: boolean; - - /** - * The user id. - */ - subject?: string; - - /** - * Response mode passed in init (default value is `'fragment'`). - */ - responseMode?: KeycloakResponseMode; - - /** - * Response type sent to Keycloak with login requests. This is determined - * based on the flow value used during initialization, but can be overridden - * by setting this value. - */ - responseType?: KeycloakResponseType; - - /** - * Flow passed in init. - */ - flow?: KeycloakFlow; - - /** - * The realm roles associated with the token. - */ - realmAccess?: KeycloakRoles; - - /** - * The resource roles associated with the token. - */ - resourceAccess?: KeycloakResourceAccess; - - /** - * The base64 encoded token that can be sent in the Authorization header in - * requests to services. - */ - token?: string; - - /** - * The parsed token as a JavaScript object. - */ - tokenParsed?: KeycloakTokenParsed; - - /** - * The base64 encoded refresh token that can be used to retrieve a new token. - */ - refreshToken?: string; - - /** - * The parsed refresh token as a JavaScript object. - */ - refreshTokenParsed?: KeycloakTokenParsed; - - /** - * The base64 encoded ID token. - */ - idToken?: string; - - /** - * The parsed id token as a JavaScript object. - */ - idTokenParsed?: KeycloakTokenParsed; - - /** - * The estimated time difference between the browser time and the Keycloak - * server in seconds. This value is just an estimation, but is accurate - * enough when determining if a token is expired or not. - */ - timeSkew?: number; - - /** - * Whether the instance has been initialized by calling `.init()`. - */ - didInitialize: boolean; - - /** - * @private Undocumented. - */ - loginRequired?: boolean; - - /** - * @private Undocumented. - */ - authServerUrl?: string; - - /** - * @private Undocumented. - */ - realm?: string; - - /** - * @private Undocumented. - */ - clientId?: string; - - /** - * @private Undocumented. - */ - redirectUri?: string; - - /** - * @private Undocumented. - */ - sessionId?: string; - - /** - * @private Undocumented. - */ - profile?: KeycloakProfile; - - /** - * @private Undocumented. - */ - userInfo?: {}; // KeycloakUserInfo; - - /** - * Called when the adapter is initialized. - */ - onReady?(authenticated?: boolean): void; - - /** - * Called when a user is successfully authenticated. - */ - onAuthSuccess?(): void; - - /** - * Called if there was an error during authentication. - */ - onAuthError?(errorData: KeycloakError): void; - - /** - * Called when the token is refreshed. - */ - onAuthRefreshSuccess?(): void; - - /** - * Called if there was an error while trying to refresh the token. - */ - onAuthRefreshError?(): void; - - /** - * Called if the user is logged out (will only be called if the session - * status iframe is enabled, or in Cordova mode). - */ - onAuthLogout?(): void; - - /** - * Called when the access token is expired. If a refresh token is available - * the token can be refreshed with Keycloak#updateToken, or in cases where - * it's not (ie. with implicit flow) you can redirect to login screen to - * obtain a new access token. - */ - onTokenExpired?(): void; - - /** - * Called when a AIA has been requested by the application. - * @param status the outcome of the required action - * @param action the alias name of the required action, e.g. UPDATE_PASSWORD, CONFIGURE_TOTP etc. - */ - onActionUpdate?(status: 'success'|'cancelled'|'error', action?: string): void; - - /** - * Called to initialize the adapter. - * @param initOptions Initialization options. - * @returns A promise to set functions to be invoked on success or error. - */ - init(initOptions?: KeycloakInitOptions): Promise; - - /** - * Redirects to login form. - * @param options Login options. - */ - login(options?: KeycloakLoginOptions): Promise; - - /** - * Redirects to logout. - * @param options Logout options. - */ - logout(options?: KeycloakLogoutOptions): Promise; - - /** - * Redirects to registration form. - * @param options The options used for the registration. - */ - register(options?: KeycloakRegisterOptions): Promise; - - /** - * Redirects to the Account Management Console. - */ - accountManagement(): Promise; - - /** - * Returns the URL to login form. - * @param options Supports same options as Keycloak#login. - */ - createLoginUrl(options?: KeycloakLoginOptions): Promise; - - /** - * Returns the URL to logout the user. - * @param options Logout options. - */ - createLogoutUrl(options?: KeycloakLogoutOptions): string; - - /** - * Returns the URL to registration page. - * @param options The options used for creating the registration URL. - */ - createRegisterUrl(options?: KeycloakRegisterOptions): Promise; - - /** - * Returns the URL to the Account Management Console. - * @param options The options used for creating the account URL. - */ - createAccountUrl(options?: KeycloakAccountOptions): string; - - /** - * Returns true if the token has less than `minValidity` seconds left before - * it expires. - * @param minValidity If not specified, `0` is used. - */ - isTokenExpired(minValidity?: number): boolean; - - /** - * If the token expires within `minValidity` seconds, the token is refreshed. - * If the session status iframe is enabled, the session status is also - * checked. - * @param minValidity If not specified, `5` is used. - * @returns A promise to set functions that can be invoked if the token is - * still valid, or if the token is no longer valid. - * @example - * ```js - * keycloak.updateToken(5).then(function(refreshed) { - * if (refreshed) { - * alert('Token was successfully refreshed'); - * } else { - * alert('Token is still valid'); - * } - * }).catch(function() { - * alert('Failed to refresh the token, or the session has expired'); - * }); - */ - updateToken(minValidity?: number): Promise; - - /** - * Clears authentication state, including tokens. This can be useful if - * the application has detected the session was expired, for example if - * updating token fails. Invoking this results in Keycloak#onAuthLogout - * callback listener being invoked. - */ - clearToken(): void; - - /** - * Returns true if the token has the given realm role. - * @param role A realm role name. - */ - hasRealmRole(role: string): boolean; - - /** - * Returns true if the token has the given role for the resource. - * @param role A role name. - * @param resource If not specified, `clientId` is used. - */ - hasResourceRole(role: string, resource?: string): boolean; - - /** - * Loads the user's profile. - * @returns A promise to set functions to be invoked on success or error. - */ - loadUserProfile(): Promise; - - /** - * @private Undocumented. - */ - loadUserInfo(): Promise<{}>; -} - -export default Keycloak; - -/** - * @deprecated The 'Keycloak' namespace is deprecated, use named imports instead. - */ -export as namespace Keycloak; diff --git a/js/libs/keycloak-js/lib/keycloak.js b/js/libs/keycloak-js/lib/keycloak.js deleted file mode 100755 index 47225f4dd43..00000000000 --- a/js/libs/keycloak-js/lib/keycloak.js +++ /dev/null @@ -1,1873 +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 Keycloak (config) { - if (!(this instanceof Keycloak)) { - throw new Error("The 'Keycloak' constructor must be invoked with 'new'.") - } - - if (typeof config !== 'string' && !isObject(config)) { - throw new Error("The 'Keycloak' constructor must be provided with a configuration object, or a URL to a JSON configuration file."); - } - - if (isObject(config)) { - const requiredProperties = 'oidcProvider' in config - ? ['clientId'] - : ['url', 'realm', 'clientId']; - - for (const property of requiredProperties) { - if (!config[property]) { - throw new Error(`The configuration object is missing the required '${property}' property.`); - } - } - } - - var kc = this; - var adapter; - var refreshQueue = []; - var callbackStorage; - - var loginIframe = { - enable: true, - callbackList: [], - interval: 5 - }; - - kc.didInitialize = false; - - var useNonce = true; - var logInfo = createLogger(console.info); - var logWarn = createLogger(console.warn); - - if (!globalThis.isSecureContext) { - logWarn( - "[KEYCLOAK] Keycloak JS must be used in a 'secure context' to function properly as it relies on browser APIs that are otherwise not available.\n" + - "Continuing to run your application insecurely will lead to unexpected behavior and breakage.\n\n" + - "For more information see: https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts" - ); - } - - kc.init = function (initOptions = {}) { - if (kc.didInitialize) { - throw new Error("A 'Keycloak' instance can only be initialized once."); - } - - kc.didInitialize = true; - - kc.authenticated = false; - - callbackStorage = createCallbackStorage(); - var adapters = ['default', 'cordova', 'cordova-native']; - - if (adapters.indexOf(initOptions.adapter) > -1) { - adapter = loadAdapter(initOptions.adapter); - } else if (typeof initOptions.adapter === "object") { - adapter = initOptions.adapter; - } else { - if (window.Cordova || window.cordova) { - adapter = loadAdapter('cordova'); - } else { - adapter = loadAdapter(); - } - } - - 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 (typeof initOptions.pkceMethod !== "undefined") { - if (initOptions.pkceMethod !== "S256" && initOptions.pkceMethod !== false) { - throw new TypeError(`Invalid value for pkceMethod', expected 'S256' or false but got ${initOptions.pkceMethod}.`); - } - - kc.pkceMethod = initOptions.pkceMethod; - } else { - kc.pkceMethod = "S256"; - } - - if (typeof initOptions.enableLogging === 'boolean') { - kc.enableLogging = initOptions.enableLogging; - } else { - kc.enableLogging = false; - } - - if (initOptions.logoutMethod === 'POST') { - kc.logoutMethod = 'POST'; - } else { - kc.logoutMethod = 'GET'; - } - - if (typeof initOptions.scope === 'string') { - kc.scope = initOptions.scope; - } - - if (typeof initOptions.acrValues === 'string') { - kc.acrValues = initOptions.acrValues; - } - - 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(); - - function onLoad() { - var doLogin = function(prompt) { - if (!prompt) { - options.prompt = 'none'; - } - - if (initOptions.locale) { - options.locale = initOptions.locale; - } - kc.login(options).then(function () { - initPromise.setSuccess(); - }).catch(function (error) { - initPromise.setError(error); - }); - } - - var checkSsoSilently = async function() { - var ifrm = document.createElement("iframe"); - var src = await kc.createLoginUrl({prompt: 'none', redirectUri: kc.silentCheckSsoRedirectUri}); - ifrm.setAttribute("src", src); - ifrm.setAttribute("sandbox", "allow-storage-access-by-user-activation allow-scripts allow-same-origin"); - 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); - }); - } - - 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(); - } - } - - configPromise.then(function () { - 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) { - if (typeof crypto === "undefined" || typeof crypto.getRandomValues === "undefined") { - throw new Error("Web Crypto API is not available."); - } - - return crypto.getRandomValues(new Uint8Array(len)); - } - - 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); - } - - async function generatePkceChallenge(pkceMethod, codeVerifier) { - if (pkceMethod !== "S256") { - throw new TypeError(`Invalid value for 'pkceMethod', expected 'S256' but got '${pkceMethod}'.`); - } - - // hash codeVerifier, then encode as url-safe base64 without padding - const hashBytes = new Uint8Array(await sha256Digest(codeVerifier)); - const encodedHash = bytesToBase64(hashBytes) - .replace(/\+/g, '-') - .replace(/\//g, '_') - .replace(/\=/g, ''); - - return encodedHash; - } - - function buildClaimsParameter(requestedAcr){ - var claims = { - id_token: { - acr: requestedAcr - } - } - return JSON.stringify(claims); - } - - kc.createLoginUrl = async function(options) { - var state = createUUID(); - var nonce = createUUID(); - - var redirectUri = adapter.redirectUri(options); - - var callbackState = { - state: state, - nonce: nonce, - redirectUri: encodeURIComponent(redirectUri), - loginOptions: options - }; - - 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 && typeof options.maxAge === 'number') { - 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 (options && options.acr) { - var claimsParameter = buildClaimsParameter(options.acr); - url += '&claims=' + encodeURIComponent(claimsParameter); - } - - if ((options && options.acrValues) || kc.acrValues) { - url += '&acr_values=' + encodeURIComponent(options.acrValues || kc.acrValues); - } - - if (kc.pkceMethod) { - try { - const codeVerifier = generateCodeVerifier(96); - const pkceChallenge = await generatePkceChallenge(kc.pkceMethod, codeVerifier); - - callbackState.pkceCodeVerifier = codeVerifier; - - url += '&code_challenge=' + pkceChallenge; - url += '&code_challenge_method=' + kc.pkceMethod; - } catch (error) { - throw new Error("Failed to generate PKCE challenge.", { cause: error }); - } - } - - callbackStorage.add(callbackState); - - return url; - } - - kc.logout = function(options) { - return adapter.logout(options); - } - - kc.createLogoutUrl = function(options) { - - const logoutMethod = options?.logoutMethod ?? kc.logoutMethod; - if (logoutMethod === 'POST') { - return kc.endpoints.logout(); - } - - var url = kc.endpoints.logout() - + '?client_id=' + encodeURIComponent(kc.clientId) - + '&post_logout_redirect_uri=' + encodeURIComponent(adapter.redirectUri(options, false)); - - if (kc.idToken) { - url += '&id_token_hint=' + encodeURIComponent(kc.idToken); - } - - return url; - } - - kc.register = function (options) { - return adapter.register(options); - } - - kc.createRegisterUrl = async function(options) { - if (!options) { - options = {}; - } - options.action = 'register'; - return await 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("Failed to refresh token: An unexpected HTTP error occurred while attempting to refresh the token."); - } - } - } - }; - - 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'], oauth['kc_action']); - } - - if (error) { - if (prompt != 'none') { - if (oauth.error_description && oauth.error_description === "authentication_expired") { - kc.login(oauth.loginOptions); - } else { - 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.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() { - var promise = createPromise(); - var configUrl; - - 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() { - return getRealmUrl() + '/protocol/openid-connect/login-status-iframe.html'; - }, - thirdPartyCookiesIframe: function() { - return getRealmUrl() + '/protocol/openid-connect/3p-cookies/step1.html'; - }, - 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 { - kc.clientId = config.clientId; - - var oidcProvider = config['oidcProvider']; - if (!oidcProvider) { - 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.sid; - 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 createUUID() { - if (typeof crypto === "undefined" || typeof crypto.randomUUID === "undefined") { - throw new Error("Web Crypto API is not available."); - } - - return crypto.randomUUID(); - } - - 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; - oauth.loginOptions = oauthState.loginOptions; - } - - return oauth; - } - - function parseCallbackUrl(url) { - var supportedParams; - switch (kc.flow) { - case 'standard': - supportedParams = ['code', 'state', 'session_state', 'kc_action_status', 'kc_action', 'iss']; - break; - case 'implicit': - supportedParams = ['access_token', 'token_type', 'id_token', 'state', 'session_state', 'expires_in', 'kc_action_status', 'kc_action', 'iss']; - break; - case 'hybrid': - supportedParams = ['access_token', 'token_type', 'id_token', 'code', 'state', 'session_state', 'expires_in', 'kc_action_status', 'kc_action', 'iss']; - 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; - }); - - 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('sandbox', 'allow-storage-access-by-user-activation allow-scripts allow-same-origin'); - 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) && typeof kc.endpoints.thirdPartyCookiesIframe === 'function') { - var iframe = document.createElement('iframe'); - iframe.setAttribute('src', kc.endpoints.thirdPartyCookiesIframe()); - iframe.setAttribute('sandbox', 'allow-storage-access-by-user-activation allow-scripts allow-same-origin'); - 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") { - logWarn( - "[KEYCLOAK] Your browser is blocking access to 3rd-party cookies, this means:\n\n" + - " - It is not possible to retrieve tokens without redirecting to the Keycloak server (a.k.a. no support for silent authentication).\n" + - " - It is not possible to automatically detect changes to the session status (such as the user logging out in another tab).\n\n" + - "For more information see: https://www.keycloak.org/securing-apps/javascript-adapter#_modern_browsers" - ); - - loginIframe.enable = false; - if (kc.silentCheckSsoFallback) { - kc.silentCheckSsoRedirectUri = false; - } - } - - 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: async function(options) { - window.location.assign(await kc.createLoginUrl(options)); - return createPromise().promise; - }, - - logout: async function(options) { - - const logoutMethod = options?.logoutMethod ?? kc.logoutMethod; - if (logoutMethod === "GET") { - window.location.replace(kc.createLogoutUrl(options)); - return; - } - - // Create form to send POST request. - const form = document.createElement("form"); - - form.setAttribute("method", "POST"); - form.setAttribute("action", kc.createLogoutUrl(options)); - form.style.display = "none"; - - // Add data to form as hidden input fields. - const data = { - id_token_hint: kc.idToken, - client_id: kc.clientId, - post_logout_redirect_uri: adapter.redirectUri(options, false) - }; - - for (const [name, value] of Object.entries(data)) { - const input = document.createElement("input"); - - input.setAttribute("type", "hidden"); - input.setAttribute("name", name); - input.setAttribute("value", value); - - form.appendChild(input); - } - - // Append form to page and submit it to perform logout and redirect. - document.body.appendChild(form); - form.submit(); - }, - - register: async function(options) { - window.location.assign(await 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); - }; - - var getCordovaRedirectUri = function() { - return kc.redirectUri || 'http://localhost'; - } - - return { - login: async function(options) { - var promise = createPromise(); - - var cordovaOptions = createCordovaOptions(options); - var loginUrl = await 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(getCordovaRedirectUri()) == 0) { - var callback = parseCallback(event.url); - processCallback(callback, promise); - closeBrowser(); - completed = true; - } - }); - - ref.addEventListener('loaderror', function(event) { - if (!completed) { - if (event.url.indexOf(getCordovaRedirectUri()) == 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(getCordovaRedirectUri()) == 0) { - ref.close(); - } - }); - - ref.addEventListener('loaderror', function(event) { - if (event.url.indexOf(getCordovaRedirectUri()) == 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 : async function(options) { - var promise = createPromise(); - var registerUrl = await kc.createRegisterUrl(); - var cordovaOptions = createCordovaOptions(options); - var ref = cordovaOpenWindowWrapper(registerUrl, '_blank', cordovaOptions); - ref.addEventListener('loadstart', function(event) { - if (event.url.indexOf(getCordovaRedirectUri()) == 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(getCordovaRedirectUri()) == 0) { - ref.close(); - } - }); - } else { - throw "Not supported by the OIDC server"; - } - }, - - redirectUri: function(options) { - return getCordovaRedirectUri(); - } - } - } - - if (type == 'cordova-native') { - loginIframe.enable = false; - - return { - login: async function(options) { - var promise = createPromise(); - var loginUrl = await 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 : async function(options) { - var promise = createPromise(); - var registerUrl = await 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; - } - - const STORAGE_KEY_PREFIX = 'kc-callback-'; - - var LocalStorage = function() { - if (!(this instanceof LocalStorage)) { - return new LocalStorage(); - } - - localStorage.setItem('kc-test', 'test'); - localStorage.removeItem('kc-test'); - - var cs = this; - - /** - * Clears all values from local storage that are no longer valid. - */ - function clearInvalidValues() { - const currentTime = Date.now(); - - for (const [key, value] of getStoredEntries()) { - // Attempt to parse the expiry time from the value. - const expiry = parseExpiry(value); - - // Discard the value if it is malformed or expired. - if (expiry === null || expiry < currentTime) { - localStorage.removeItem(key); - } - } - } - - /** - * Clears all known values from local storage. - */ - function clearAllValues() { - for (const [key] of getStoredEntries()) { - localStorage.removeItem(key); - } - } - - /** - * Gets all entries stored in local storage that are known to be managed by this class. - * @returns {Array<[string, unknown]>} An array of key-value pairs. - */ - function getStoredEntries() { - return Object.entries(localStorage).filter(([key]) => key.startsWith(STORAGE_KEY_PREFIX)); - } - - /** - * Parses the expiry time from a value stored in local storage. - * @param {unknown} value - * @returns {number | null} The expiry time in milliseconds, or `null` if the value is malformed. - */ - function parseExpiry(value) { - let parsedValue; - - // Attempt to parse the value as JSON. - try { - parsedValue = JSON.parse(value); - } catch (error) { - return null; - } - - // Attempt to extract the 'expires' property. - if (isObject(parsedValue) && 'expires' in parsedValue && typeof parsedValue.expires === 'number') { - return parsedValue.expires; - } - - return null; - } - - cs.get = function(state) { - if (!state) { - return; - } - - var key = STORAGE_KEY_PREFIX + state; - var value = localStorage.getItem(key); - if (value) { - localStorage.removeItem(key); - value = JSON.parse(value); - } - - clearInvalidValues(); - return value; - }; - - cs.add = function(state) { - clearInvalidValues(); - - const key = STORAGE_KEY_PREFIX + state.state; - const value = JSON.stringify({ - ...state, - // Set the expiry time to 1 hour from now. - expires: Date.now() + (60 * 60 * 1000) - }); - - try { - localStorage.setItem(key, value); - } catch (error) { - // If the storage is full, clear all known values and try again. - clearAllValues(); - localStorage.setItem(key, value); - } - }; - }; - - var CookieStorage = function() { - if (!(this instanceof CookieStorage)) { - return new CookieStorage(); - } - - var cs = this; - - cs.get = function(state) { - if (!state) { - return; - } - - var value = getCookie(STORAGE_KEY_PREFIX + state); - setCookie(STORAGE_KEY_PREFIX + state, '', cookieExpiration(-100)); - if (value) { - return JSON.parse(value); - } - }; - - cs.add = function(state) { - setCookie(STORAGE_KEY_PREFIX + 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; - -/** - * @param {ArrayBuffer} bytes - * @see https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem - */ -function bytesToBase64(bytes) { - const binString = String.fromCodePoint(...bytes); - return btoa(binString); -} - -/** - * @param {string} message - * @see https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest#basic_example - */ -async function sha256Digest(message) { - const encoder = new TextEncoder(); - const data = encoder.encode(message); - - if (typeof crypto === "undefined" || typeof crypto.subtle === "undefined") { - throw new Error("Web Crypto API is not available."); - } - - return await crypto.subtle.digest("SHA-256", data); -} - -/** - * @param {string} token - */ -function decodeToken(token) { - const [header, payload] = token.split("."); - - if (typeof payload !== "string") { - throw new Error("Unable to decode token, payload not found."); - } - - let decoded; - - try { - decoded = base64UrlDecode(payload); - } catch (error) { - throw new Error("Unable to decode token, payload is not a valid Base64URL value.", { cause: error }); - } - - try { - return JSON.parse(decoded); - } catch (error) { - throw new Error("Unable to decode token, payload is not a valid JSON value.", { cause: error }); - } -} - -/** - * @param {string} input - */ -function base64UrlDecode(input) { - let output = input - .replaceAll("-", "+") - .replaceAll("_", "/"); - - switch (output.length % 4) { - case 0: - break; - case 2: - output += "=="; - break; - case 3: - output += "="; - break; - default: - throw new Error("Input is not of the correct length."); - } - - try { - return b64DecodeUnicode(output); - } catch (error) { - return atob(output); - } -} - -/** - * @param {string} input - */ -function b64DecodeUnicode(input) { - return decodeURIComponent(atob(input).replace(/(.)/g, (m, p) => { - let code = p.charCodeAt(0).toString(16).toUpperCase(); - - if (code.length < 2) { - code = "0" + code; - } - - return "%" + code; - })); -} - -/** - * Check if the input is an object that can be operated on. - * @param {unknown} input - */ -function isObject(input) { - return typeof input === 'object' && input !== null; -} diff --git a/js/libs/keycloak-js/package.json b/js/libs/keycloak-js/package.json deleted file mode 100644 index be564f9fe8f..00000000000 --- a/js/libs/keycloak-js/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "keycloak-js", - "version": "999.0.0-SNAPSHOT", - "type": "module", - "description": "A client-side JavaScript OpenID Connect library that can be used to secure web applications.", - "exports": { - ".": { - "types": "./lib/keycloak.d.ts", - "default": "./lib/keycloak.js" - }, - "./authz": { - "types": "./lib/keycloak-authz.d.ts", - "default": "./lib/keycloak-authz.js" - } - }, - "files": [ - "lib" - ], - "publishConfig": { - "access": "public" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/keycloak/keycloak.git" - }, - "author": "Keycloak", - "license": "Apache-2.0", - "homepage": "https://www.keycloak.org", - "keywords": [ - "keycloak", - "sso", - "oauth", - "oauth2", - "authentication" - ] -} diff --git a/js/libs/keycloak-js/pom.xml b/js/libs/keycloak-js/pom.xml deleted file mode 100644 index 68906bf6a46..00000000000 --- a/js/libs/keycloak-js/pom.xml +++ /dev/null @@ -1,94 +0,0 @@ - - - 4.0.0 - - - keycloak-js-parent - org.keycloak - 999.0.0-SNAPSHOT - ../../pom.xml - - - keycloak-js-adapter - - Keycloak JavaScript Adapter - A client-side JavaScript OpenID Connect library that can be used to secure web applications. - pom - - - - jboss-release - - - - org.apache.maven.plugins - maven-antrun-plugin - - - create-target-dir - prepare-package - - - - - - - run - - - - - - com.github.eirslett - frontend-maven-plugin - - - pnpm-pack - package - - pnpm - - - pack --pack-destination=target - - - - - - org.codehaus.mojo - build-helper-maven-plugin - - - attach-artifacts - package - - attach-artifact - - - - - target/keycloak-js-${project.version.npm}.tgz - tar.gz - - - - - - - - - - - - - - - org.apache.maven.plugins - maven-deploy-plugin - - false - - - - - diff --git a/js/libs/keycloak-js/tsconfig.json b/js/libs/keycloak-js/tsconfig.json deleted file mode 100644 index 34cf7dd5c6b..00000000000 --- a/js/libs/keycloak-js/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "compilerOptions": { - "allowSyntheticDefaultImports": true - } -} diff --git a/js/libs/ui-shared/package.json b/js/libs/ui-shared/package.json index 3637e50dece..4308267d71d 100644 --- a/js/libs/ui-shared/package.json +++ b/js/libs/ui-shared/package.json @@ -49,7 +49,7 @@ "@patternfly/react-styles": "^5.4.1", "@patternfly/react-table": "^5.4.14", "i18next": "^24.2.2", - "keycloak-js": "workspace:*", + "keycloak-js": "^26.1.2", "lodash-es": "^4.17.21", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/js/pnpm-lock.yaml b/js/pnpm-lock.yaml index be4aca9705d..757c4200d34 100644 --- a/js/pnpm-lock.yaml +++ b/js/pnpm-lock.yaml @@ -96,8 +96,8 @@ importers: specifier: ^3.0.2 version: 3.0.2 keycloak-js: - specifier: workspace:* - version: link:../../libs/keycloak-js + specifier: ^26.1.2 + version: 26.1.2 lodash-es: specifier: ^4.17.21 version: 4.17.21 @@ -196,8 +196,8 @@ importers: specifier: ^3.10.1 version: 3.10.1 keycloak-js: - specifier: workspace:* - version: link:../../libs/keycloak-js + specifier: ^26.1.2 + version: 26.1.2 lodash-es: specifier: ^4.17.21 version: 4.17.21 @@ -369,8 +369,6 @@ importers: specifier: ^10.9.2 version: 10.9.2(@swc/core@1.10.15)(@types/node@22.13.1)(typescript@5.7.3) - libs/keycloak-js: {} - libs/ui-shared: dependencies: '@keycloak/keycloak-admin-client': @@ -392,8 +390,8 @@ importers: specifier: ^24.2.2 version: 24.2.2(typescript@5.7.3) keycloak-js: - specifier: workspace:* - version: link:../keycloak-js + specifier: ^26.1.2 + version: 26.1.2 lodash-es: specifier: ^4.17.21 version: 4.17.21 @@ -3747,6 +3745,9 @@ packages: jszip@3.10.1: resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + keycloak-js@26.1.2: + resolution: {integrity: sha512-nZ26zNgZevVSo7bqeljOfFVCQ4HnPTeYIwdfIwg0uSuXgxD+zS0j1uqaypPlqU17Hu8qHlygj0u72TxPlCWmYw==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -6072,7 +6073,7 @@ snapshots: i18next: 24.2.2(typescript@5.7.3) i18next-http-backend: 3.0.2 jszip: 3.10.1 - keycloak-js: link:libs/keycloak-js + keycloak-js: 26.1.2 lodash-es: 4.17.21 p-debounce: 4.0.0 react: 18.3.1 @@ -9031,6 +9032,8 @@ snapshots: readable-stream: 2.3.8 setimmediate: 1.0.5 + keycloak-js@26.1.2: {} + keyv@4.5.4: dependencies: json-buffer: 3.0.1 diff --git a/js/pom.xml b/js/pom.xml index 70bbeeee75d..02a755f6961 100644 --- a/js/pom.xml +++ b/js/pom.xml @@ -20,7 +20,6 @@ apps/admin-ui libs/keycloak-admin-client libs/ui-shared - libs/keycloak-js themes-vendor diff --git a/set-version.sh b/set-version.sh index 2203669c233..0820ba8c9a1 100755 --- a/set-version.sh +++ b/set-version.sh @@ -26,7 +26,6 @@ sed -i 's/:project_versionDoc: .*/:project_versionDoc: '$NEW_VERSION'/' topics/t cd - # NPM publish -echo "$(jq '. += {"version": "'$NEW_NPM_VERSION'"}' js/libs/keycloak-js/package.json)" > js/libs/keycloak-js/package.json echo "$(jq '. += {"version": "'$NEW_NPM_VERSION'"}' js/libs/keycloak-admin-client/package.json)" > js/libs/keycloak-admin-client/package.json echo "$(jq '. += {"version": "'$NEW_NPM_VERSION'"}' js/libs/ui-shared/package.json)" > js/libs/ui-shared/package.json echo "$(jq '. += {"version": "'$NEW_NPM_VERSION'"}' js/apps/account-ui/package.json)" > js/apps/account-ui/package.json diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/pom.xml b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/pom.xml index 05d6bac5555..5a4db9c37c7 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/pom.xml @@ -30,11 +30,6 @@ integration-arquillian-testsuite-providers Auth Server Services - Testsuite Providers - - ${project.basedir}/../../../../../../js/libs/keycloak-js/lib - ${project.basedir}/target/classes/javascript - - @@ -102,37 +97,5 @@ - - - src/main/resources - true - - - - - - maven-resources-plugin - - - copy-keycloak-js - generate-resources - - copy-resources - - - ${js-adapter.target.path} - - - ${js-adapter.dist.path} - - keycloak.js - - - - - - - - diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java index 1c00ccdd073..0eea16e5c23 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java @@ -96,7 +96,6 @@ import org.keycloak.testsuite.forms.PassThroughClientAuthenticator; import org.keycloak.testsuite.model.infinispan.InfinispanTestUtil; import org.keycloak.testsuite.rest.representation.AuthenticatorState; import org.keycloak.testsuite.rest.resource.TestCacheResource; -import org.keycloak.testsuite.rest.resource.TestJavascriptResource; import org.keycloak.testsuite.rest.resource.TestLDAPResource; import org.keycloak.testsuite.rest.resource.TestingExportImportResource; import org.keycloak.testsuite.runonserver.FetchOnServer; @@ -865,12 +864,6 @@ public class TestingResourceProvider implements RealmResourceProvider { } } - - @Path("/javascript") - public TestJavascriptResource getJavascriptResource() { - return new TestJavascriptResource(session); - } - private void setFeatureInProfileFile(File file, Profile.Feature featureProfile, String newState) { doWithProperties(file, props -> { props.setProperty(PropertiesProfileConfigResolver.getPropertyKey(featureProfile), newState); diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestJavascriptResource.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestJavascriptResource.java deleted file mode 100644 index 7c461cffbd5..00000000000 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestJavascriptResource.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.keycloak.testsuite.rest.resource; - -import org.keycloak.headers.SecurityHeadersProvider; -import org.keycloak.models.KeycloakSession; -import org.keycloak.testsuite.rest.TestingResourceProvider; - -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.MediaType; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; - -import static org.keycloak.testsuite.util.ServerURLs.getAuthServerContextRoot; - -/** - * @author mhajas - */ -public class TestJavascriptResource { - - private KeycloakSession session; - - public TestJavascriptResource(KeycloakSession session) { - this.session = session; - } - - @GET - @Path("/js/keycloak.js") - @Produces("application/javascript") - public String getJavascriptAdapter() throws IOException { - return resourceToString("/javascript/keycloak.js"); - } - - @GET - @Path("/index.html") - @Produces(MediaType.TEXT_HTML) - public String getJavascriptTestingEnvironment() throws IOException { - session.getProvider(SecurityHeadersProvider.class).options().skipHeaders(); - return resourceToString("/javascript/index.html"); - } - - @GET - @Path("/init-in-head.html") - @Produces(MediaType.TEXT_HTML) - public String getJavascriptTestingEnvironmentWithInitInHead() throws IOException { - session.getProvider(SecurityHeadersProvider.class).options().skipHeaders(); - return resourceToString("/javascript/init-in-head.html"); - } - - @GET - @Path("/silent-check-sso.html") - @Produces(MediaType.TEXT_HTML) - public String getJavascriptTestingEnvironmentSilentCheckSso() throws IOException { - return resourceToString("/javascript/silent-check-sso.html"); - } - - @GET - @Path("/keycloak.json") - @Produces(MediaType.APPLICATION_JSON) - public String getKeycloakJSON() throws IOException { - return resourceToString("/javascript/keycloak.json"); - } - - private String resourceToString(String path) throws IOException { - try (InputStream is = TestingResourceProvider.class.getResourceAsStream(path); - BufferedReader buf = new BufferedReader(new InputStreamReader(is))) { - String line = buf.readLine(); - StringBuilder sb = new StringBuilder(); - while (line != null) { - sb.append(line).append("\n"); - line = buf.readLine(); - } - - return sb.toString().replace("${js-adapter.auth-server-url}", getAuthServerContextRoot() + "/auth"); - } - } -} diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/javascript/index.html b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/javascript/index.html deleted file mode 100644 index af2e23baeaa..00000000000 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/javascript/index.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - -

Result

-

-
-

Events

-

-
-
-
-
-
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/javascript/init-in-head.html b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/javascript/init-in-head.html
deleted file mode 100644
index d15871a7395..00000000000
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/javascript/init-in-head.html
+++ /dev/null
@@ -1,77 +0,0 @@
-
-
-
-
-    
-    
-
-
-
-

Result

-

-
-

Events

-

-
-
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/javascript/keycloak.json b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/javascript/keycloak.json
deleted file mode 100644
index 328ec601fa5..00000000000
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/javascript/keycloak.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "realm" : "test",
-  "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
-  "auth-server-url" : "${js-adapter.auth-server-url}",
-  "ssl-required" : "external",
-  "resource" : "js-console",
-  "public-client" : true
-}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/javascript/silent-check-sso.html b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/javascript/silent-check-sso.html
deleted file mode 100644
index 60a7af916a9..00000000000
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/javascript/silent-check-sso.html
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java
index d4633e41975..212168401e1 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java
@@ -323,16 +323,6 @@ public interface TestingResource {
     String runModelTestOnServer(@QueryParam("testClassName") String testClassName,
                                 @QueryParam("testMethodName") String testMethodName);
 
-    @GET
-    @Path("js/keycloak.js")
-    @Produces(MediaType.TEXT_HTML_UTF_8)
-    String getJavascriptAdapter();
-
-    @GET
-    @Path("/get-javascript-testing-environment")
-    @Produces(MediaType.TEXT_HTML_UTF_8)
-    String getJavascriptTestingEnvironment();
-
     @GET
     @Path("/list-disabled-features")
     @Produces(MediaType.APPLICATION_JSON)
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/JSObjectBuilder.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/JSObjectBuilder.java
deleted file mode 100644
index 44600ca5129..00000000000
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/JSObjectBuilder.java
+++ /dev/null
@@ -1,149 +0,0 @@
-package org.keycloak.testsuite.util.javascript;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-
-import org.keycloak.util.JsonSerialization;
-
-/**
- * @author mhajas
- */
-public class JSObjectBuilder {
-
-    private Map arguments;
-
-
-    public static JSObjectBuilder create() {
-        return new JSObjectBuilder();
-    }
-
-    private JSObjectBuilder() {
-        arguments = new HashMap<>();
-    }
-
-    public JSObjectBuilder defaultSettings() {
-        standardFlow();
-        fragmentResponse();
-        enableLogging();
-        return this;
-    }
-
-    public JSObjectBuilder standardFlow() {
-        arguments.put("flow", "standard");
-        return this;
-    }
-
-    public JSObjectBuilder implicitFlow() {
-        arguments.put("flow", "implicit");
-        return this;
-    }
-
-    public JSObjectBuilder fragmentResponse() {
-        arguments.put("responseMode", "fragment");
-        return this;
-    }
-
-    public JSObjectBuilder queryResponse() {
-        arguments.put("responseMode", "query");
-        return this;
-    }
-
-    public JSObjectBuilder checkSSOOnLoad() {
-        arguments.put("onLoad", "check-sso");
-        return this;
-    }
-
-    public JSObjectBuilder disableSilentCheckSSOFallback() {
-        arguments.put("silentCheckSsoFallback", false);
-        return this;
-    }
-
-    public JSObjectBuilder disableCheckLoginIframe() {
-        arguments.put("checkLoginIframe", false);
-        return this;
-    }
-
-    public JSObjectBuilder setCheckLoginIframeIntervalTo1() {
-        arguments.put("checkLoginIframeInterval", 1);
-        return this;
-    }
-
-    public JSObjectBuilder loginRequiredOnLoad() {
-        arguments.put("onLoad", "login-required");
-        return this;
-    }
-
-    public JSObjectBuilder enableLogging() {
-        arguments.put("enableLogging", true);
-        return this;
-    }
-
-    public boolean contains(String key, Object value) {
-       return arguments.containsKey(key) && arguments.get(key).equals(value);
-    }
-
-    public JSObjectBuilder add(String key, Object value) {
-        arguments.put(key, value);
-        return this;
-    }
-
-    public boolean isLoginRequired() {
-        return arguments.get("onLoad").equals("login-required");
-    }
-
-
-    public JSObjectBuilder pkceS256() {
-        return pkceMethod("S256");
-    }
-
-    private JSObjectBuilder pkceMethod(String method) {
-        arguments.put("pkceMethod", method);
-        return this;
-    }
-
-    public JSObjectBuilder acrValues(String value) {
-        Objects.requireNonNull(value, "value");
-        arguments.put("acrValues", value);
-        return this;
-    }
-
-    private boolean skipQuotes(Object o) {
-        return (o instanceof Integer || o instanceof Boolean || o instanceof JSObjectBuilder);
-    }
-
-    public String build() {
-        StringBuilder argument = new StringBuilder("{");
-        String comma = "";
-        for (Map.Entry option : arguments.entrySet()) {
-            argument.append(comma)
-                    .append(option.getKey())
-                    .append(" : ");
-
-            if (option.getValue().getClass().isArray()) {
-                try {
-                    argument.append(JsonSerialization.writeValueAsString(option.getValue()));
-                } catch (IOException ioe) {
-                    throw new IllegalArgumentException("Not possible to serialize value of the option " + option.getKey(), ioe);
-                }
-            } else {
-                if (!skipQuotes(option.getValue())) argument.append("\"");
-
-                argument.append(option.getValue());
-
-                if (!skipQuotes(option.getValue())) argument.append("\"");
-            }
-            comma = ",";
-        }
-
-        argument.append("}");
-
-        return argument.toString();
-    }
-
-    @Override
-    public String toString() {
-        return build();
-    }
-}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/JavascriptStateValidator.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/JavascriptStateValidator.java
deleted file mode 100644
index 504faa243f0..00000000000
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/JavascriptStateValidator.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package org.keycloak.testsuite.util.javascript;
-
-import org.openqa.selenium.WebDriver;
-import org.openqa.selenium.WebElement;
-
-import java.io.Serializable;
-
-/**
- * @author mhajas
- */
-public interface JavascriptStateValidator extends Serializable {
-
-    void validate(WebDriver driver, Object output, WebElement events);
-}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/JavascriptTestExecutor.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/JavascriptTestExecutor.java
deleted file mode 100644
index c07f48934a7..00000000000
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/JavascriptTestExecutor.java
+++ /dev/null
@@ -1,391 +0,0 @@
-package org.keycloak.testsuite.util.javascript;
-
-import org.jboss.logging.Logger;
-import org.keycloak.representations.idm.UserRepresentation;
-import org.keycloak.testsuite.auth.page.login.OIDCLogin;
-import org.keycloak.testsuite.pages.LogoutConfirmPage;
-import org.keycloak.testsuite.util.WaitUtils;
-import org.openqa.selenium.By;
-import org.openqa.selenium.JavascriptExecutor;
-import org.openqa.selenium.WebDriver;
-import org.openqa.selenium.WebDriverException;
-import org.openqa.selenium.WebElement;
-
-import java.util.concurrent.TimeUnit;
-
-import static org.junit.Assert.fail;
-import static org.keycloak.testsuite.util.WaitUtils.pause;
-import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
-
-
-/**
- * @author mhajas
- */
-public class JavascriptTestExecutor {
-    protected WebDriver jsDriver;
-    protected JavascriptExecutor jsExecutor;
-    private WebElement output;
-    protected WebElement events;
-    private OIDCLogin loginPage;
-    protected boolean configured;
-
-    private static final Logger logger = Logger.getLogger(JavascriptTestExecutor.class);
-
-    public static JavascriptTestExecutor create(WebDriver driver, OIDCLogin loginPage) {
-        return new JavascriptTestExecutor(driver, loginPage);
-    }
-
-    protected JavascriptTestExecutor(WebDriver driver, OIDCLogin loginPage) {
-        this.jsDriver = driver;
-        driver.manage().timeouts().setScriptTimeout(WaitUtils.PAGELOAD_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
-        jsExecutor = (JavascriptExecutor) driver;
-        events = driver.findElement(By.id("events"));
-        output = driver.findElement(By.id("output"));
-        this.loginPage = loginPage;
-        configured = false;
-    }
-
-    public JavascriptTestExecutor login() {
-        return login((String)null, null);
-    }
-
-    public JavascriptTestExecutor login(JavascriptStateValidator validator) {
-        return login((String)null, validator);
-    }
-
-    /**
-     * Attaches a MutationObserver that sends a message from iframe to main window with incorrect data when the iframe is loaded
-     */
-    public JavascriptTestExecutor attachCheck3pCookiesIframeMutationObserver() {
-        jsExecutor.executeScript("// Select the node that will be observed for mutations\n" +
-                "    const targetNode = document.body;" +
-                "" +
-                "    // Options for the observer (which mutations to observe)\n" +
-                "    const config = {attributes: true, childList: true, subtree: true};" +
-                "" +
-                "    // Callback function to execute when mutations are observed\n" +
-                "    const callback = function (mutationsList, observer) {" +
-                "        console.log(\"Mutation found\");" +
-                "        var iframeNode = mutationsList[0].addedNodes[0];" +
-        "                if (iframeNode && iframeNode.localName === 'iframe') {" +
-        "                    var s = document.createElement('script');" +
-        "                    s.type = 'text/javascript';" +
-        "                    var code = \"window.parent.postMessage('Evil Message', '*');\";" +
-        "                    s.appendChild(document.createTextNode(code));" +
-        "                    iframeNode.contentDocument.body.appendChild(s);" +
-        "                }" +
-                "    }\n" +
-                "" +
-                "    // Create an observer instance linked to the callback function\n" +
-                "    const observer = new MutationObserver(callback);" +
-                "" +
-                "    // Start observing the target node for configured mutations\n" +
-                "    observer.observe(targetNode, config);");
-
-        return this;
-    }
-
-    public JavascriptTestExecutor login(JSObjectBuilder optionsBuilder, JavascriptStateValidator validator) {
-        return login(optionsBuilder.build(), validator);
-    }
-
-    public JavascriptTestExecutor login(String options, JavascriptStateValidator validator) {
-        if (options == null)
-            jsExecutor.executeScript("keycloak.login()");
-        else {
-            jsExecutor.executeScript("keycloak.login(" + options + ")");
-        }
-        waitForPageToLoad();
-
-        if (validator != null) {
-            validator.validate(jsDriver, output, events);
-        }
-
-        configured = false; // Getting out of testApp page => loosing keycloak variable etc.
-
-        return this;
-    }
-
-    public JavascriptTestExecutor loginForm(UserRepresentation user) {
-        return loginForm(user, null);
-    }
-
-    public JavascriptTestExecutor loginForm(UserRepresentation user, JavascriptStateValidator validator) {
-        loginPage.form().login(user);
-        waitForPageToLoad();
-
-        if (validator != null) {
-            validator.validate(jsDriver, null, events);
-        }
-
-        configured = false; // Getting out of testApp page => loosing keycloak variable etc.
-        // this is necessary in case we skipped login button for example in login-required mode
-
-        return this;
-    }
-
-    public JavascriptTestExecutor logout() {
-        return logout(null);
-    }
-
-    public JavascriptTestExecutor logout(JavascriptStateValidator validator) {
-        return logout(validator, null);
-    }
-
-    public JavascriptTestExecutor logout(JavascriptStateValidator validator, LogoutConfirmPage logoutConfirmPage) {
-        return logout(validator, logoutConfirmPage, null);
-    }
-
-    public JavascriptTestExecutor logout(JavascriptStateValidator validator, LogoutConfirmPage logoutConfirmPage, JSObjectBuilder logoutOptions) {
-        String logoutOptionsString = logoutOptions == null ? "" : logoutOptions.toString();
-        jsExecutor.executeScript("keycloak.logout(" + logoutOptionsString + ")");
-
-        try {
-            // simple check if we are at the logout confirm page, if so just click 'Yes'
-            if (logoutConfirmPage != null && logoutConfirmPage.isCurrent(jsDriver)) {
-                logoutConfirmPage.confirmLogout(jsDriver);
-                waitForPageToLoad();
-            }
-        } catch (Exception ex) {
-            // ignore errors when checking logoutConfirm page, if an error tests will also fail
-            logger.error("Exception during checking logout confirmation page", ex);
-        }
-
-        if (validator != null) {
-            validator.validate(jsDriver, output, events);
-        }
-
-        configured = false; // Loosing keycloak variable so we need to create it when init next session
-
-        return this;
-    }
-
-    public JavascriptTestExecutor configure() {
-        return configure(null);
-    }
-
-    public JavascriptTestExecutor configure(JSObjectBuilder argumentsBuilder) {
-        // a nasty hack: redirect console.warn to events
-        // mainly for FF as it doesn't yet support reading console.warn directly through webdriver
-        // see https://github.com/mozilla/geckodriver/issues/284
-        jsExecutor.executeScript("console.warn = event;");
-
-        if (argumentsBuilder == null) {
-            jsExecutor.executeScript("window.keycloak = new Keycloak('./keycloak.json');");
-        } else {
-            String configArguments = argumentsBuilder.build();
-            jsExecutor.executeScript("window.keycloak = new Keycloak(" + configArguments + ");");
-        }
-
-        jsExecutor.executeScript("window.keycloak.onAuthSuccess = function () {event('Auth Success')};"); // event function is declared in index.html
-        jsExecutor.executeScript("window.keycloak.onAuthError = function () {event('Auth Error')}");
-        jsExecutor.executeScript("window.keycloak.onAuthRefreshSuccess = function () {event('Auth Refresh Success')}");
-        jsExecutor.executeScript("window.keycloak.onAuthRefreshError = function () {event('Auth Refresh Error')}");
-        jsExecutor.executeScript("window.keycloak.onAuthLogout = function () {event('Auth Logout')}");
-        jsExecutor.executeScript("window.keycloak.onTokenExpired = function () {event('Access token expired.')}");
-        jsExecutor.executeScript("window.keycloak.onActionUpdate = function (status) {event('AIA status: ' + status)}");
-
-        configured = true;
-
-        return this;
-    }
-
-    public JavascriptTestExecutor init(JSObjectBuilder argumentsBuilder) {
-        return init(argumentsBuilder, null);
-    }
-
-    public JavascriptTestExecutor init(JSObjectBuilder argumentsBuilder, JavascriptStateValidator validator) {
-        return init(argumentsBuilder, validator, false);
-    }
-
-    public JavascriptTestExecutor init(JSObjectBuilder argumentsBuilder, JavascriptStateValidator validator, boolean expectPromptNoneRedirect) {
-        if(!configured) {
-            configure();
-        }
-
-        String arguments = argumentsBuilder != null ? argumentsBuilder.build() : "";
-
-        String script = "var callback = arguments[arguments.length - 1];" +
-                "   window.keycloak.init(" + arguments + ").then(function (authenticated) {" +
-                "       callback(\"Init Success (\" + (authenticated ? \"Authenticated\" : \"Not Authenticated\") + \")\");" +
-                "   }).catch(function (error) {" +
-                "       callback(error);" +
-                "   });";
-
-        Object output;
-
-        if (expectPromptNoneRedirect) {
-            try {
-                output = jsExecutor.executeAsyncScript(script);
-                fail("Redirect to Keycloak was expected");
-            }
-            catch (WebDriverException e) {
-                waitForPageToLoad();
-                configured = false;
-                // the redirect should use prompt=none, that means KC should immediately redirect back to the app (regardless login state)
-                return init(argumentsBuilder, validator, false);
-            }
-        }
-        else {
-            output = jsExecutor.executeAsyncScript(script);
-        }
-
-        if (validator != null) {
-            validator.validate(jsDriver, output, events);
-        }
-
-        return this;
-    }
-
-    public JavascriptTestExecutor logInAndInit(JSObjectBuilder argumentsBuilder,
-                                               UserRepresentation user, JavascriptStateValidator validator) {
-        init(argumentsBuilder);
-        login();
-        loginForm(user);
-        init(argumentsBuilder, validator);
-        return this;
-    }
-
-    public JavascriptTestExecutor refreshToken(int value) {
-        return refreshToken(value, null);
-    }
-
-    public JavascriptTestExecutor refreshToken(int value, JavascriptStateValidator validator) {
-        String script = "var callback = arguments[arguments.length - 1];" +
-                "   window.keycloak.updateToken(" + Integer.toString(value) + ").then(function (refreshed) {" +
-                "       if (refreshed) {" +
-                "            callback(window.keycloak.tokenParsed);" +
-                "       } else {" +
-                "            callback('Token not refreshed, valid for ' + Math.round(window.keycloak.tokenParsed.exp + window.keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds');" +
-                "       }" +
-                "   }).catch(function () {" +
-                "       callback('Failed to refresh token');" +
-                "   });";
-
-        Object output = jsExecutor.executeAsyncScript(script);
-
-        if(validator != null) {
-            validator.validate(jsDriver, output, events);
-        }
-
-        return this;
-    }
-
-    public JavascriptTestExecutor openAccountPage(JavascriptStateValidator validator) {
-        jsExecutor.executeScript("window.keycloak.accountManagement()");
-        waitForPageToLoad();
-
-        // Leaving page -> loosing keycloak variable
-        configured = false;
-
-        if (validator != null) {
-            validator.validate(jsDriver, null, null);
-        }
-
-        return this;
-    }
-
-    public JavascriptTestExecutor getProfile() {
-        return getProfile(null);
-    }
-
-    public JavascriptTestExecutor getProfile(JavascriptStateValidator validator) {
-
-        String script = "var callback = arguments[arguments.length - 1];" +
-                    "   window.keycloak.loadUserProfile().then(function (profile) {" +
-                    "       callback(profile);" +
-                    "   }, function () {" +
-                    "       callback('Failed to load profile');" +
-                    "   });";
-
-        Object output = jsExecutor.executeAsyncScript(script);
-
-        if(validator != null) {
-            validator.validate(jsDriver, output, events);
-        }
-        return this;
-    }
-
-    public JavascriptTestExecutor sendXMLHttpRequest(XMLHttpRequest request, ResponseValidator validator) {
-        validator.validate(request.send(jsExecutor));
-
-        return this;
-    }
-
-    public JavascriptTestExecutor refresh() {
-        jsDriver.navigate().refresh();
-        configured = false; // Refreshing webpage => Loosing window.keycloak variable
-
-        return this;
-    }
-
-    public JavascriptTestExecutor addTimeSkew(int addition) {
-        jsExecutor.executeScript("window.keycloak.timeSkew += " + Integer.toString(addition));
-
-        return this;
-    }
-
-    public JavascriptTestExecutor checkTimeSkew(JavascriptStateValidator validator) {
-        Object timeSkew = jsExecutor.executeScript("return window.keycloak.timeSkew");
-
-        validator.validate(jsDriver, timeSkew, events);
-
-        return this;
-    }
-
-    public JavascriptTestExecutor executeScript(String script) {
-        return executeScript(script, null);
-    }
-
-    public JavascriptTestExecutor executeScript(String script, JavascriptStateValidator validator) {
-        Object output = jsExecutor.executeScript(script);
-
-        if(validator != null) {
-            validator.validate(jsDriver, output, events);
-        }
-
-        return this;
-    }
-
-    public boolean isLoggedIn() {
-        return (boolean) jsExecutor.executeScript("if (typeof keycloak !== 'undefined') {" +
-                "return keycloak.authenticated" +
-                "} else { return false}");
-    }
-
-    public JavascriptTestExecutor executeAsyncScript(String script) {
-        return executeAsyncScript(script, null);
-    }
-
-    public JavascriptTestExecutor executeAsyncScript(String script, JavascriptStateValidator validator) {
-        Object output = jsExecutor.executeAsyncScript(script);
-
-        if(validator != null) {
-            validator.validate(jsDriver, output, events);
-        }
-
-        return this;
-    }
-
-    public JavascriptTestExecutor errorResponse(JavascriptStateValidator validator) {
-        Object output = jsExecutor.executeScript("return \"Error: \" + getParameterByName(\"error\") + \"\\n\" + \"Error description: \" + getParameterByName(\"error_description\")");
-
-        validator.validate(jsDriver, output, events);
-        return this;
-    }
-
-    public JavascriptTestExecutor wait(long millis, JavascriptStateValidator validator) {
-        pause(millis);
-
-        if (validator != null) {
-            validator.validate(jsDriver, null, events);
-        }
-
-        return this;
-    }
-
-    public JavascriptTestExecutor validateOutputField(JavascriptStateValidator validator) {
-        validator.validate(jsDriver, output, events);
-        return this;
-    }
-}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/ResponseValidator.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/ResponseValidator.java
deleted file mode 100644
index b54c11331c1..00000000000
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/ResponseValidator.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.keycloak.testsuite.util.javascript;
-
-import java.io.Serializable;
-import java.util.Map;
-
-/**
- * @author mhajas
- */
-public interface ResponseValidator extends Serializable {
-
-    void validate(Map response);
-}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/XMLHttpRequest.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/XMLHttpRequest.java
deleted file mode 100644
index 8e79e6aaeb8..00000000000
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/javascript/XMLHttpRequest.java
+++ /dev/null
@@ -1,91 +0,0 @@
-package org.keycloak.testsuite.util.javascript;
-
-import org.openqa.selenium.JavascriptExecutor;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * @author mhajas
- */
-public class XMLHttpRequest {
-
-    private String url;
-    private String method;
-    private Map headers;
-    private String content;
-
-    public static XMLHttpRequest create() {
-        return new XMLHttpRequest();
-    }
-
-    private XMLHttpRequest() {}
-
-    public XMLHttpRequest url(String url) {
-        this.url = url;
-        return this;
-    }
-
-    public String getUrl() {
-        return url;
-    }
-
-    public XMLHttpRequest method(String method) {
-        this.method = method;
-        return this;
-    }
-
-    public XMLHttpRequest content(String content) {
-        this.content = content;
-        return this;
-    }
-
-    public XMLHttpRequest addHeader(String key, String value) {
-        if (headers == null) {
-            headers = new HashMap<>();
-        }
-
-        headers.put(key, value);
-
-        return this;
-    }
-
-    public XMLHttpRequest includeBearerToken() {
-        addHeader("Authorization", "Bearer ' + keycloak.token + '");
-        return this;
-    }
-
-    public XMLHttpRequest includeRpt() {
-        addHeader("Authorization", "Bearer ' + authorization.rpt + '");
-        return this;
-    }
-
-    public Map send(JavascriptExecutor jsExecutor) {
-        String requestCode = "var callback = arguments[arguments.length - 1];" +
-                        "var req = new XMLHttpRequest();" +
-                        "        req.open('" + method + "', '" + url + "', true);" +
-                        getHeadersString() +
-                        "        req.onreadystatechange = function () {" +
-                        "            if (req.readyState == 4) {" +
-                        "                callback({\"status\" : req.status, \"responseHeaders\" : req.getAllResponseHeaders(), \"res\" : req.response})" +
-                        "            }" +
-                        "        };" +
-                        "        req.send(" + content + ");";
-
-        return (Map) jsExecutor.executeAsyncScript(requestCode);
-    }
-
-    private String getHeadersString() {
-        StringBuilder builder = new StringBuilder();
-        for (Map.Entry entry : headers.entrySet()) {
-            builder.append("req.setRequestHeader('")
-                    .append(entry.getKey())
-                    .append("', '")
-                    .append(entry.getValue())
-                    .append("');");
-        }
-
-        return builder.toString();
-    }
-
-}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/javascript/AbstractJavascriptTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/javascript/AbstractJavascriptTest.java
deleted file mode 100644
index 1da3453755d..00000000000
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/javascript/AbstractJavascriptTest.java
+++ /dev/null
@@ -1,231 +0,0 @@
-package org.keycloak.testsuite.javascript;
-
-import org.jboss.arquillian.drone.api.annotation.Drone;
-import org.jboss.arquillian.graphene.page.Page;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.keycloak.admin.client.resource.ClientResource;
-import org.keycloak.representations.idm.ClientRepresentation;
-import org.keycloak.representations.idm.RealmRepresentation;
-import org.keycloak.representations.idm.RoleRepresentation;
-import org.keycloak.representations.idm.UserRepresentation;
-import org.keycloak.testsuite.AbstractAuthTest;
-import org.keycloak.testsuite.Assert;
-import org.keycloak.testsuite.admin.ApiUtil;
-import org.keycloak.testsuite.auth.page.login.OIDCLogin;
-import org.keycloak.testsuite.util.ClientBuilder;
-import org.keycloak.testsuite.util.ContainerAssume;
-import org.keycloak.testsuite.util.JavascriptBrowser;
-import org.keycloak.testsuite.util.RealmBuilder;
-import org.keycloak.testsuite.util.RolesBuilder;
-import org.keycloak.testsuite.util.UserBuilder;
-import org.keycloak.testsuite.util.javascript.JavascriptStateValidator;
-import org.keycloak.testsuite.util.javascript.ResponseValidator;
-import org.openqa.selenium.By;
-import org.openqa.selenium.Cookie;
-import org.openqa.selenium.JavascriptExecutor;
-import org.openqa.selenium.WebDriver;
-import org.openqa.selenium.WebDriver.Options;
-import org.openqa.selenium.WebElement;
-import org.openqa.selenium.support.FindBy;
-
-import java.util.List;
-import java.util.Map;
-
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.CoreMatchers.anyOf;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.collection.IsMapContaining.hasEntry;
-import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_HOST;
-import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_HOST2;
-import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
-import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
-import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
-
-/**
- * @author mhajas
- */
-public abstract class AbstractJavascriptTest extends AbstractAuthTest {
-
-    @FunctionalInterface
-    interface QuadFunction {
-        void apply(T a, U b, V c, W d);
-    }
-
-    public static final String JS_APP_HOST = AUTH_SERVER_HOST2;
-    public static final String CLIENT_ID = "js-console";
-    public static final String REALM_NAME = "test";
-    public static final String SPACE_REALM_NAME = "Example realm";
-    public static final String JAVASCRIPT_URL = "/auth/realms/" + REALM_NAME + "/testing/javascript";
-    public static final String JAVASCRIPT_ENCODED_SPACE_URL = "/auth/realms/Example%20realm/testing/javascript";
-    public static final String JAVASCRIPT_SPACE_URL = "/auth/realms/Example realm/testing/javascript";
-    public static int TOKEN_LIFESPAN_LEEWAY = 3; // seconds
-    public static final String USER_PASSWORD = "password";
-
-
-    protected JavascriptExecutor jsExecutor;
-
-    // Javascript browser needed KEYCLOAK-4703
-    @Drone
-    @JavascriptBrowser
-    protected WebDriver jsDriver;
-
-    @Page
-    @JavascriptBrowser
-    protected OIDCLogin jsDriverTestRealmLoginPage;
-
-    @FindBy(id = "output")
-    @JavascriptBrowser
-    protected WebElement outputArea;
-
-    @FindBy(id = "events")
-    @JavascriptBrowser
-    protected WebElement eventsArea;
-
-    public static final UserRepresentation testUser;
-    public static final UserRepresentation unauthorizedUser;
-
-    static {
-        testUser = UserBuilder.create().username("test-user@localhost").password(USER_PASSWORD).build();
-        unauthorizedUser = UserBuilder.create().username("unauthorized").password(USER_PASSWORD).build();
-    }
-
-    @BeforeClass
-    public static void enabledOnlyWithSSL() {
-        ContainerAssume.assumeAuthServerSSL();
-    }
-
-    @Before
-    public void beforeJavascriptTest() {
-        jsExecutor = (JavascriptExecutor) jsDriver;
-    }
-
-    @Override
-    public void addTestRealms(List testRealms) {
-        testRealms.add(updateRealm(RealmBuilder.create()
-                .name(REALM_NAME)
-                .roles(
-                        RolesBuilder.create()
-                                .realmRole(new RoleRepresentation("user", "", false))
-                                .realmRole(new RoleRepresentation("admin", "", false))
-                )
-                .user(
-                        UserBuilder.create()
-                                .username("test-user@localhost").password("password")
-                                .addRoles("user")
-                                .role("realm-management", "view-realm")
-                                .role("realm-management", "manage-users")
-                                .role("account", "view-profile")
-                                .role("account", "manage-account")
-                )
-                .user(
-                        UserBuilder.create()
-                                .username("unauthorized").password("password")
-                )
-                .client(
-                        ClientBuilder.create()
-                                .clientId(CLIENT_ID)
-                                .redirectUris(oauth.SERVER_ROOT.replace(AUTH_SERVER_HOST, JS_APP_HOST) + JAVASCRIPT_URL + "/*", oauth.SERVER_ROOT + JAVASCRIPT_ENCODED_SPACE_URL + "/*")
-                                .addWebOrigin(oauth.SERVER_ROOT.replace(AUTH_SERVER_HOST, JS_APP_HOST))
-                                .publicClient()
-                )
-                .accessTokenLifespan(30 + TOKEN_LIFESPAN_LEEWAY)
-                .testEventListener()
-        ));
-    }
-
-    protected  JavascriptStateValidator buildFunction(QuadFunction f, T x) {
-        return (y,z,w) -> f.apply(x, y, z, w);
-    }
-
-    protected void setImplicitFlowForClient() {
-        ClientResource clientResource = ApiUtil.findClientResourceByClientId(adminClient.realms().realm(REALM_NAME), CLIENT_ID);
-        ClientRepresentation client = clientResource.toRepresentation();
-        client.setImplicitFlowEnabled(true);
-        client.setStandardFlowEnabled(false);
-        clientResource.update(client);
-    }
-
-    protected void setStandardFlowForClient() {
-        ClientResource clientResource = ApiUtil.findClientResourceByClientId(adminClient.realms().realm(REALM_NAME), CLIENT_ID);
-        ClientRepresentation client = clientResource.toRepresentation();
-        client.setImplicitFlowEnabled(false);
-        client.setStandardFlowEnabled(true);
-        clientResource.update(client);
-    }
-
-    protected abstract RealmRepresentation updateRealm(RealmBuilder builder);
-
-    protected void assertInitAuth(WebDriver driver1, Object output, WebElement events) {
-        buildFunction(this::assertOutputContains, "Init Success (Authenticated)").validate(driver1, output, events);
-        waitUntilElement(events).text().contains("Auth Success");
-    }
-
-    protected void assertInitNotAuth(WebDriver driver1, Object output, WebElement events) {
-        buildFunction(this::assertOutputContains, "Init Success (Not Authenticated)").validate(driver1, output, events);
-    }
-
-    protected void assertOnLoginPage(WebDriver driver1, Object output, WebElement events) {
-        waitUntilElement(By.tagName("body")).is().present();
-        assertCurrentUrlStartsWith(jsDriverTestRealmLoginPage, driver1);
-    }
-
-    public void assertOutputWebElementContains(String value, WebDriver driver1, Object output, WebElement events) {
-        waitUntilElement((WebElement) output).text().contains(value);
-    }
-    
-    public void assertLocaleCookie(String locale, WebDriver driver1, Object output, WebElement events) {
-        waitForPageToLoad();
-        Options ops = driver1.manage();
-        Cookie cookie = ops.getCookieNamed("KEYCLOAK_LOCALE");
-        Assert.assertNotNull(cookie);
-        Assert.assertEquals(locale, cookie.getValue());
-    }
-    
-    public JavascriptStateValidator assertLocaleIsSet(String locale) {
-        return buildFunction(this::assertLocaleCookie, locale);
-    }
-
-    public void assertOutputContains(String value, WebDriver driver1, Object output, WebElement events) {
-        if (output instanceof WebElement) {
-            waitUntilElement((WebElement) output).text().contains(value);
-        } else {
-            assertThat((String) output, containsString(value));
-        }
-    }
-
-    public void assertEventsWebElementContains(String value, WebDriver driver1, Object output, WebElement events) {
-        waitUntilElement(events).text().contains(value);
-    }
-
-    public void assertEventsWebElementDoesntContain(String value, WebDriver driver1, Object output, WebElement events) {
-        waitUntilElement(events).text().not().contains(value);
-    }
-
-    public ResponseValidator assertResponseStatus(long status) {
-        return output -> assertThat(output, hasEntry("status", status));
-    }
-
-    public JavascriptStateValidator assertOutputContains(String text) {
-        return buildFunction(this::assertOutputContains, text);
-    }
-
-    public JavascriptStateValidator assertEventsContains(String text) {
-        return buildFunction(this::assertEventsWebElementContains, text);
-    }
-
-    public JavascriptStateValidator assertEventsDoesntContain(String text) {
-        return buildFunction(this::assertEventsWebElementDoesntContain, text);
-    }
-
-    public void assertErrorResponse(String expectedError, WebDriver drv, Object output, WebElement evt) {
-        Assert.assertNotNull("Empty error response", output);
-        Assert.assertTrue("Invalid error response type", output instanceof Map);
-        assertThat((Map) output, anyOf(hasEntry("error", expectedError), hasEntry("error_description", expectedError)));
-    }
-
-    public JavascriptStateValidator assertErrorResponse(String expectedError) {
-        return buildFunction(this::assertErrorResponse, expectedError);
-    }
-
-}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/javascript/JavascriptAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/javascript/JavascriptAdapterTest.java
deleted file mode 100644
index e8650cae9e1..00000000000
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/javascript/JavascriptAdapterTest.java
+++ /dev/null
@@ -1,969 +0,0 @@
-package org.keycloak.testsuite.javascript;
-
-import org.jboss.arquillian.graphene.page.Page;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.keycloak.OAuth2Constants;
-import org.keycloak.admin.client.resource.ClientResource;
-import org.keycloak.common.util.Retry;
-import org.keycloak.common.util.UriUtils;
-import org.keycloak.events.Details;
-import org.keycloak.protocol.oidc.OIDCLoginProtocol;
-import org.keycloak.representations.ClaimsRepresentation;
-import org.keycloak.representations.IDToken;
-import org.keycloak.representations.idm.ClientRepresentation;
-import org.keycloak.representations.idm.EventRepresentation;
-import org.keycloak.representations.idm.RealmRepresentation;
-import org.keycloak.representations.idm.UserRepresentation;
-import org.keycloak.testsuite.Assert;
-import org.keycloak.testsuite.AssertEvents;
-import org.keycloak.testsuite.admin.ApiUtil;
-import org.keycloak.testsuite.arquillian.SuiteContext;
-import org.keycloak.testsuite.auth.page.login.OAuthGrant;
-import org.keycloak.testsuite.auth.page.login.UpdatePassword;
-import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
-import org.keycloak.testsuite.util.JavascriptBrowser;
-import org.keycloak.testsuite.util.AccountHelper;
-import org.keycloak.testsuite.util.RealmBuilder;
-import org.keycloak.testsuite.util.UserBuilder;
-import org.keycloak.testsuite.util.oauth.AccessTokenResponse;
-import org.keycloak.testsuite.util.javascript.JSObjectBuilder;
-import org.keycloak.testsuite.util.javascript.JavascriptStateValidator;
-import org.keycloak.testsuite.util.javascript.JavascriptTestExecutor;
-import org.keycloak.testsuite.util.javascript.XMLHttpRequest;
-import org.keycloak.util.JsonSerialization;
-import org.openqa.selenium.TimeoutException;
-import org.openqa.selenium.WebDriver;
-import org.openqa.selenium.WebDriverException;
-import org.openqa.selenium.WebElement;
-
-import java.io.IOException;
-import java.net.URL;
-import java.util.List;
-import java.util.Map;
-
-import static java.lang.Math.toIntExact;
-import static org.hamcrest.CoreMatchers.anyOf;
-import static org.hamcrest.CoreMatchers.both;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.greaterThan;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.lessThan;
-import static org.hamcrest.collection.IsMapContaining.hasEntry;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_HOST;
-import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlDoesntStartWith;
-import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
-import static org.keycloak.testsuite.util.WaitUtils.pause;
-import static org.keycloak.testsuite.util.WaitUtils.waitForPageToLoad;
-import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
-
-/**
- * @author mhajas
- */
-public class JavascriptAdapterTest extends AbstractJavascriptTest {
-
-    private String testAppUrl;
-    private String testAppWithInitInHeadUrl;
-    protected JavascriptTestExecutor testExecutor;
-    private static int TIME_SKEW_TOLERANCE = 3;
-
-    @Rule
-    public AssertEvents events = new AssertEvents(this);
-
-    @Page
-    @JavascriptBrowser
-    private OAuthGrant oAuthGrantPage;
-
-    @Page
-    @JavascriptBrowser
-    private UpdatePassword updatePasswordPage;
-
-    @Override
-    protected RealmRepresentation updateRealm(RealmBuilder builder) {
-        return builder.accessTokenLifespan(30 + TOKEN_LIFESPAN_LEEWAY).build();
-    }
-
-    @Before
-    public void setDefaultEnvironment() {
-        String testAppRootUrl = authServerContextRootPage.toString().replace(AUTH_SERVER_HOST, JS_APP_HOST) + JAVASCRIPT_URL;
-        testAppUrl = testAppRootUrl + "/index.html";
-        testAppWithInitInHeadUrl = testAppRootUrl + "/init-in-head.html";
-
-        jsDriverTestRealmLoginPage.setAuthRealm(REALM_NAME);
-        oAuthGrantPage.setAuthRealm(REALM_NAME);
-        oauth.realm(REALM_NAME);
-
-        jsDriver.navigate().to(oauth.getLoginFormUrl());
-        waitForPageToLoad();
-        events.poll();
-        jsDriver.manage().deleteAllCookies();
-
-        navigateToTestApp(testAppUrl);
-
-        testExecutor = JavascriptTestExecutor.create(jsDriver, jsDriverTestRealmLoginPage);
-
-        jsDriver.manage().deleteAllCookies();
-
-        setStandardFlowForClient();
-
-        //tests cleanup
-        oauth.setDriver(driver);
-        setTimeOffset(0);
-    }
-
-    protected JSObjectBuilder defaultArguments() {
-        return JSObjectBuilder.create().defaultSettings();
-    }
-
-    private void assertOnTestAppUrl(WebDriver jsDriver, Object output, WebElement events) {
-        assertOnTestAppUrl(jsDriver, output, events, testAppUrl);
-    }
-
-    private void assertOnTestAppWithInitInHeadUrl(WebDriver jsDriver, Object output, WebElement events) {
-        assertOnTestAppUrl(jsDriver, output, events, testAppWithInitInHeadUrl);
-    }
-
-    private void assertOnTestAppUrl(WebDriver jsDriver, Object output, WebElement events, String testAppUrl) {
-        waitForPageToLoad();
-        assertCurrentUrlStartsWith(testAppUrl, jsDriver);
-    }
-
-    @Test
-    public void testJSConsoleAuth() {
-        testExecutor.init(defaultArguments(), this::assertInitNotAuth)
-                .login(this::assertOnLoginPage)
-                .loginForm(UserBuilder.create().username("user").password("invalid-password").build(),
-                        (driver1, output, events) -> assertCurrentUrlDoesntStartWith(testAppUrl, driver1))
-                .loginForm(UserBuilder.create().username("invalid-user").password("password").build(),
-                        (driver1, output, events) -> assertCurrentUrlDoesntStartWith(testAppUrl, driver1))
-                .loginForm(testUser, this::assertOnTestAppUrl)
-                .init(defaultArguments(), this::assertInitAuth)
-                .logout(this::assertOnTestAppUrl)
-                .init(defaultArguments(), this::assertInitNotAuth);
-    }
-
-    @Test
-    public void testLoginWithPkceS256() {
-        JSObjectBuilder pkceS256 = defaultArguments().pkceS256();
-        testExecutor.init(pkceS256, this::assertInitNotAuth)
-                .login(this::assertOnLoginPage)
-                .loginForm(testUser, this::assertOnTestAppUrl)
-                .init(pkceS256, this::assertInitAuth)
-                .logout(this::assertOnTestAppUrl)
-                .init(pkceS256, this::assertInitNotAuth);
-    }
-
-    @Test
-    public void testLogoutWithDefaults() {
-        boolean stillLoggedIn = testExecutor.init(defaultArguments(), this::assertInitNotAuth)
-                .login(this::assertOnLoginPage)
-                .loginForm(testUser, this::assertOnTestAppUrl)
-                .init(defaultArguments(), this::assertInitAuth)
-                .logout(this::assertOnTestAppUrl)
-                .isLoggedIn();
-        assertFalse("still logged in", stillLoggedIn);
-    }
-
-    @Test
-    public void testLogoutWithInitOptionsPostMethod() {
-        boolean stillLoggedIn = testExecutor.init(defaultArguments(), this::assertInitNotAuth)
-                .login(this::assertOnLoginPage)
-                .loginForm(testUser, this::assertOnTestAppUrl)
-                .init(defaultArguments().add("logoutMethod", "POST"), this::assertInitAuth)
-                .logout(this::assertOnTestAppUrl, null)
-                .isLoggedIn();
-        assertFalse("still logged in", stillLoggedIn);
-    }
-
-    @Test
-    public void testLogoutWithOptionsPostMethod() {
-        boolean stillLoggedIn = testExecutor.init(defaultArguments(), this::assertInitNotAuth)
-                .login(this::assertOnLoginPage)
-                .loginForm(testUser, this::assertOnTestAppUrl)
-                .init(defaultArguments(), this::assertInitAuth)
-                .logout(this::assertOnTestAppUrl, null, JSObjectBuilder.create().add("logoutMethod", "POST"))
-                .isLoggedIn();
-        assertFalse("still logged in", stillLoggedIn);
-    }
-
-    @Test
-    public void testSilentCheckSso() {
-        JSObjectBuilder checkSSO = defaultArguments().checkSSOOnLoad()
-                .add("silentCheckSsoRedirectUri", authServerContextRootPage.toString().replace(AUTH_SERVER_HOST, JS_APP_HOST) + JAVASCRIPT_URL + "/silent-check-sso.html");
-
-        // when 3rd party cookies are disabled, the adapter has to do a full redirect to KC to check whether the user
-        // is logged in or not – it can't rely on silent check-sso iframe
-        testExecutor.init(checkSSO, this::assertInitNotAuth, SuiteContext.BROWSER_STRICT_COOKIES)
-                .login(this::assertOnLoginPage)
-                .loginForm(testUser, this::assertOnTestAppUrl)
-                .init(checkSSO, this::assertInitAuth, false)
-                .refresh()
-                .init(checkSSO
-                        , this::assertInitAuth, SuiteContext.BROWSER_STRICT_COOKIES);
-    }
-
-    @Test
-    public void testSilentCheckSsoLoginWithLoginIframeDisabled() {
-        JSObjectBuilder checkSSO = defaultArguments().checkSSOOnLoad()
-                .add("silentCheckSsoRedirectUri", authServerContextRootPage.toString().replace(AUTH_SERVER_HOST, JS_APP_HOST) + JAVASCRIPT_URL + "/silent-check-sso.html");
-
-        testExecutor.init(checkSSO, this::assertInitNotAuth, SuiteContext.BROWSER_STRICT_COOKIES)
-                .login(this::assertOnLoginPage)
-                .loginForm(testUser, this::assertOnTestAppUrl)
-                .init(checkSSO, this::assertInitAuth, false)
-                .refresh()
-                .init(checkSSO
-                        .disableCheckLoginIframe()
-                        , this::assertInitAuth, SuiteContext.BROWSER_STRICT_COOKIES);
-    }
-
-    @Test
-    public void testSilentCheckSsoWithFallbackDisabled() {
-        JSObjectBuilder checkSSO = defaultArguments().checkSSOOnLoad().disableSilentCheckSSOFallback()
-                .add("silentCheckSsoRedirectUri", authServerContextRootPage.toString().replace(AUTH_SERVER_HOST, JS_APP_HOST) + JAVASCRIPT_URL + "/silent-check-sso.html");
-
-        testExecutor.init(checkSSO, this::assertInitNotAuth)
-                .login(this::assertOnLoginPage)
-                .loginForm(testUser, this::assertOnTestAppUrl)
-                .init(checkSSO, this::assertInitAuth)
-                .refresh()
-                .init(checkSSO
-                        // with the fall back disabled, the adapter won't do full redirect to KC
-                        , SuiteContext.BROWSER_STRICT_COOKIES ? this::assertInitNotAuth : this::assertInitAuth);
-    }
-
-    @Test
-    public void testInitNoOptions() {
-        testExecutor.init(null, this::assertInitNotAuth)
-                .login(this::assertOnLoginPage)
-                .loginForm(testUser, this::assertOnTestAppUrl)
-                .init(null, this::assertInitAuth)
-                .logout(this::assertOnTestAppUrl)
-                .init(null, this::assertInitNotAuth);
-    }
-
-    @Test
-    public void testCheckSso() {
-        JSObjectBuilder checkSSO = defaultArguments().checkSSOOnLoad();
-
-        // when 3rd party cookies are disabled, the adapter has to do a full redirect to KC to check whether the user
-        // is logged in or not – it can't rely on the login iframe
-        testExecutor.init(checkSSO, this::assertInitNotAuth, SuiteContext.BROWSER_STRICT_COOKIES)
-                .login(this::assertOnLoginPage)
-                .loginForm(testUser, this::assertOnTestAppUrl)
-                .init(checkSSO, this::assertInitAuth, false)
-                .refresh()
-                .init(checkSSO, this::assertInitAuth, true);
-    }
-
-    @Test
-    public void testSilentCheckSsoNotAuthenticated() {
-        JSObjectBuilder checkSSO = defaultArguments().checkSSOOnLoad()
-                .add("checkLoginIframe", false)
-                .add("silentCheckSsoRedirectUri", authServerContextRootPage.toString().replace(AUTH_SERVER_HOST, JS_APP_HOST) + JAVASCRIPT_URL + "/silent-check-sso.html");
-
-        testExecutor.init(checkSSO
-                , this::assertInitNotAuth, SuiteContext.BROWSER_STRICT_COOKIES);
-    }
-
-    @Test
-    // KEYCLOAK-13206
-    public void testIframeInit() {
-        JSObjectBuilder iframeInterval = defaultArguments().setCheckLoginIframeIntervalTo1(); // to speed up the test a bit
-        testExecutor.init(iframeInterval)
-                .login()
-                .loginForm(testUser)
-                .init(iframeInterval)
-                .wait(2000, (driver1, output, events) -> { // iframe is initialized after ~1 second, 2 seconds is just to be sure
-                    assertAdapterIsLoggedIn(driver1, output, events);
-                    final String logMsg = "Your browser is blocking access to 3rd-party cookies, this means:";
-                    if (SuiteContext.BROWSER_STRICT_COOKIES) {
-                        // this is here not really to test the log but also to make sure the browser is configured properly
-                        // and cookies were blocked
-                        assertEventsWebElementContains(logMsg, driver1, output, events);
-                    }
-                    else {
-                        assertEventsWebElementDoesntContain(logMsg, driver1, output, events);
-                    }
-                });
-    }
-
-    @Test
-    public void testRefreshToken() {
-        testExecutor.init(defaultArguments(), this::assertInitNotAuth)
-                .refreshToken(9999, assertOutputContains("Failed to refresh token"))
-                .login(this::assertOnLoginPage)
-                .loginForm(testUser, this::assertOnTestAppUrl)
-                .init(defaultArguments(), this::assertInitAuth)
-                .refreshToken(9999, assertEventsContains("Auth Refresh Success"));
-    }
-
-    @Test
-    public void testRefreshTokenIfUnder30s() {
-        testExecutor.init(defaultArguments(), this::assertInitNotAuth)
-                .login(this::assertOnLoginPage)
-                .loginForm(testUser, this::assertOnTestAppUrl)
-                .init(defaultArguments(), this::assertInitAuth)
-                .refreshToken(30, assertOutputContains("Token not refreshed, valid for"))
-                .addTimeSkew(-5) // instead of wait move in time
-                .refreshToken(30, assertEventsContains("Auth Refresh Success"));
-    }
-
-    @Test
-    public void testGetProfile() {
-        testExecutor.init(defaultArguments(), this::assertInitNotAuth)
-                .getProfile(assertOutputContains("Failed to load profile"))
-                .login(this::assertOnLoginPage)
-                .loginForm(testUser, this::assertOnTestAppUrl)
-                .init(defaultArguments(), this::assertInitAuth)
-                .getProfile((driver1, output, events) -> assertThat((Map) output, hasEntry("username", testUser.getUsername())));
-    }
-
-    @Test
-    public void grantBrowserBasedApp() {
-        ClientResource clientResource = ApiUtil.findClientResourceByClientId(adminClient.realm(REALM_NAME), CLIENT_ID);
-        ClientRepresentation client = clientResource.toRepresentation();
-        try {
-            client.setConsentRequired(true);
-            clientResource.update(client);
-
-            testExecutor.init(defaultArguments(), this::assertInitNotAuth)
-                  .login(this::assertOnLoginPage)
-                  .loginForm(testUser, (driver1, output, events) -> assertTrue(oAuthGrantPage.isCurrent(driver1))
-                        // I am not sure why is this driver1 argument to isCurrent necessary, but I got exception without it
-                  );
-
-            oAuthGrantPage.accept();
-
-            EventRepresentation loginEvent = events.expectLogin()
-                  .client(CLIENT_ID)
-                  .detail(Details.CONSENT, Details.CONSENT_VALUE_CONSENT_GRANTED)
-                  .detail(Details.REDIRECT_URI, testAppUrl)
-                  .detail(Details.USERNAME, testUser.getUsername())
-                  .assertEvent();
-            String codeId = loginEvent.getDetails().get(Details.CODE_ID);
-
-            testExecutor.init(defaultArguments(), this::assertInitAuth);
-
-            driver.navigate().to(oauth.getLoginFormUrl());
-            events.expectCodeToToken(codeId, loginEvent.getSessionId()).client(CLIENT_ID).assertEvent();
-
-            AccountHelper.revokeConsents(adminClient.realm(REALM_NAME), testUser.getUsername(),CLIENT_ID);
-            Assert.assertTrue(AccountHelper.getUserConsents(adminClient.realm(REALM_NAME), testUser.getUsername()).isEmpty());
-
-            jsDriver.navigate().to(testAppUrl);
-            testExecutor.configure() // need to configure because we refreshed page
-                  .init(defaultArguments(), this::assertInitNotAuth)
-                  .login((driver1, output, events) -> assertTrue(oAuthGrantPage.isCurrent(driver1)));
-        } finally {
-            // Clean
-            client.setConsentRequired(false);
-            clientResource.update(client);
-        }
-    }
-
-    @Test
-    public void implicitFlowTest() {
-        testExecutor.init(defaultArguments().implicitFlow(), this::assertInitNotAuth)
-                .login(this::assertOnTestAppUrl)
-                .errorResponse(assertOutputContains("Implicit flow is disabled for the client"));
-
-        setImplicitFlowForClient();
-        jsDriver.navigate().to(testAppUrl);
-
-        testExecutor.init(defaultArguments(), this::assertInitNotAuth)
-                .login(this::assertOnTestAppUrl)
-                .errorResponse(assertOutputContains("Standard flow is disabled for the client"));
-        jsDriver.navigate().to(testAppUrl);
-
-        testExecutor.init(defaultArguments().implicitFlow(), this::assertInitNotAuth)
-                .login(this::assertOnLoginPage)
-                .loginForm(testUser, this::assertOnTestAppUrl)
-                .init(defaultArguments().implicitFlow(), this::assertInitAuth);
-
-    }
-
-    @Test
-    public void testCertEndpoint() {
-        testExecutor.logInAndInit(defaultArguments(), testUser, this::assertInitAuth)
-                .sendXMLHttpRequest(XMLHttpRequest.create()
-                                .url(authServerContextRootPage + "/auth/realms/" + REALM_NAME + "/protocol/openid-connect/certs")
-                                .method("GET")
-                                .addHeader("Accept", "application/json")
-                                .addHeader("Authorization", "Bearer ' + keycloak.token + '"),
-                        assertResponseStatus(200));
-    }
-
-    @Test
-    public void implicitFlowQueryTest() {
-        setImplicitFlowForClient();
-        testExecutor.init(JSObjectBuilder.create().implicitFlow().queryResponse(), this::assertInitNotAuth)
-                .login((driver1, output, events1) -> Retry.execute(
-                        () -> assertThat(driver1.getCurrentUrl(), containsString("Response_mode+%27query%27+not+allowed")),
-                        20, 50)
-                );
-    }
-
-    @Test
-    public void implicitFlowRefreshTokenTest() {
-        setImplicitFlowForClient();
-        testExecutor.logInAndInit(defaultArguments().implicitFlow(), testUser, this::assertInitAuth)
-            .refreshToken(9999, assertOutputContains("Failed to refresh token"));
-    }
-
-    @Test
-    public void implicitFlowOnTokenExpireTest() {
-        try (RealmAttributeUpdater rau = new RealmAttributeUpdater(adminClient.realms().realm(REALM_NAME))
-                .setAccessTokenLifespanForImplicitFlow(3)
-                .update()
-        ) {
-                setImplicitFlowForClient();
-
-                testExecutor.logInAndInit(defaultArguments().implicitFlow(), testUser, this::assertInitAuth);
-                assertThat(driver.getPageSource(), not(containsString("Access token expired")));
-
-                // Here we can't move in time because we are waiting for onTokenExpired execution which is already
-                //   scheduled by setTimeout method, so we can't make it execute sooner
-                pause(1000);
-
-                waitUntilElement(eventsArea).text().contains("Access token expired");
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    @Test
-    public void implicitFlowCertEndpoint() {
-        setImplicitFlowForClient();
-        testExecutor.logInAndInit(defaultArguments().implicitFlow(), testUser, this::assertInitAuth)
-                .sendXMLHttpRequest(XMLHttpRequest.create()
-                                .url(authServerContextRootPage + "/auth/realms/" + REALM_NAME + "/protocol/openid-connect/certs")
-                                .method("GET")
-                                .addHeader("Accept", "application/json")
-                                .addHeader("Authorization", "Bearer ' + keycloak.token + '"),
-                        assertResponseStatus(200));
-    }
-
-    @Test
-    public void testBearerRequest() {
-        XMLHttpRequest request = XMLHttpRequest.create()
-                .url(authServerContextRootPage + "/auth/admin/realms/" + REALM_NAME + "/roles")
-                .method("GET")
-                .addHeader("Accept", "application/json")
-                .addHeader("Authorization", "Bearer ' + keycloak.token + '");
-
-        testExecutor.init(defaultArguments())
-                // Possibility of 0 and 401 is caused by this issue: https://issues.redhat.com/browse/KEYCLOAK-12686
-                .sendXMLHttpRequest(request, response -> assertThat(response, hasEntry(is("status"), anyOf(is(0L), is(401L)))))
-                .refresh();
-        testExecutor.logInAndInit(defaultArguments(), unauthorizedUser, this::assertInitAuth)
-                .sendXMLHttpRequest(request, output -> assertThat(output, hasEntry("status", 403L)))
-                .logout(this::assertOnTestAppUrl)
-                .refresh();
-        testExecutor.logInAndInit(defaultArguments(), testUser, this::assertInitAuth)
-                .sendXMLHttpRequest(request, assertResponseStatus(200));
-    }
-
-    @Test
-    public void loginRequiredAction() {
-        try {
-            testExecutor.init(defaultArguments().loginRequiredOnLoad());
-            // This throws exception because when JavascriptExecutor waits for AsyncScript to finish
-            // it is redirected to login page and executor gets no response
-
-            throw new RuntimeException("Probably the login-required OnLoad mode doesn't work, because testExecutor should fail with error that page was redirected.");
-        } catch (WebDriverException ex) {
-            // should happen
-        }
-
-        testExecutor.loginForm(testUser, this::assertOnTestAppUrl)
-                .init(defaultArguments(), this::assertInitAuth);
-    }
-
-    /**
-     * Test for scope handling via {@code initOptions}: 
{@code
-     * Keycloak keycloak = new Keycloak(); keycloak.init({.... scope: "profile email phone"})
-     * }
- * See KEYCLOAK-14412 - */ - @Test - public void testScopeInInitOptionsShouldBeConsideredByLoginUrl() { - - JSObjectBuilder initOptions = defaultArguments() - .loginRequiredOnLoad() - // phone is optional client scope - .add("scope", "openid profile email phone"); - - try { - testExecutor.init(initOptions); - // This throws exception because when JavascriptExecutor waits for AsyncScript to finish - // it is redirected to login page and executor gets no response - - throw new RuntimeException("Probably the login-required OnLoad mode doesn't work, because testExecutor should fail with error that page was redirected."); - } catch (WebDriverException ex) { - // should happen - } - - testExecutor.loginForm(testUser, this::assertOnTestAppUrl) - .init(initOptions, this::assertAdapterIsLoggedIn) - .executeScript("return window.keycloak.tokenParsed.scope", assertOutputContains("phone")); - } - - /** - * Test for scope handling via {@code loginOptions}:
{@code
-     * Keycloak keycloak = new Keycloak(); keycloak.login({.... scope: "profile email phone"})
-     * }
- * See KEYCLOAK-14412 - */ - @Test - public void testScopeInLoginOptionsShouldBeConsideredByLoginUrl() { - - testExecutor.configure().init(defaultArguments()); - - JSObjectBuilder loginOptions = JSObjectBuilder.create().add("scope", "profile email phone"); - - testExecutor.login(loginOptions, (JavascriptStateValidator) (driver, output, events) -> { - assertThat(driver.getCurrentUrl(), containsString("&scope=openid%20profile%20email%20phone")); - }); - } - - /** - * Test for acr handling via {@code loginOptions}:
{@code
-     * Keycloak keycloak = new Keycloak(); keycloak.login({.... acr: { values: ["foo", "bar"], essential: false}})
-     * }
- */ - @Test - public void testAcrInLoginOptionsShouldBeConsideredByLoginUrl() { - // Test when no "acr" option given. Claims parameter won't be passed to Keycloak server - testExecutor.configure().init(defaultArguments()); - JSObjectBuilder loginOptions = JSObjectBuilder.create(); - - testExecutor.login(loginOptions, (JavascriptStateValidator) (driver, output, events) -> { - try { - String queryString = new URL(driver.getCurrentUrl()).getQuery(); - String claimsParam = UriUtils.decodeQueryString(queryString).getFirst(OIDCLoginProtocol.CLAIMS_PARAM); - Assert.assertNull(claimsParam); - } catch (IOException ioe) { - throw new AssertionError(ioe); - } - }); - - // Test given "acr" option will be translated into the "claims" parameter passed to Keycloak server - jsDriver.navigate().to(testAppUrl); - testExecutor.configure().init(defaultArguments()); - - JSObjectBuilder acr1 = JSObjectBuilder.create() - .add("values", new String[] {"foo", "bar"}) - .add("essential", false); - loginOptions = JSObjectBuilder.create().add("acr", acr1); - - testExecutor.login(loginOptions, (JavascriptStateValidator) (driver, output, events) -> { - try { - String queryString = new URL(driver.getCurrentUrl()).getQuery(); - String claimsParam = UriUtils.decodeQueryString(queryString).getFirst(OIDCLoginProtocol.CLAIMS_PARAM); - Assert.assertNotNull(claimsParam); - - ClaimsRepresentation claimsRep = JsonSerialization.readValue(claimsParam, ClaimsRepresentation.class); - ClaimsRepresentation.ClaimValue claimValue = claimsRep.getClaimValue(IDToken.ACR, ClaimsRepresentation.ClaimContext.ID_TOKEN, String.class); - Assert.assertNames(claimValue.getValues(), "foo", "bar"); - assertThat(claimValue.isEssential(), is(false)); - } catch (IOException ioe) { - throw new AssertionError(ioe); - } - }); - } - - /** - * Test for {@code acr_values} handling via {@code loginOptions}:
{@code
-     * Keycloak keycloak = new Keycloak(); keycloak.login({...., acrValues: "1"})
-     * }
- */ - @Test - public void testAcrValuesInLoginOptionsShouldBeConsideredByLoginUrl() { - - testExecutor.configure().init(defaultArguments()); - JSObjectBuilder loginOptions = JSObjectBuilder.create(); - - testExecutor.login(loginOptions, (JavascriptStateValidator) (driver, output, events) -> { - try { - String queryString = new URL(driver.getCurrentUrl()).getQuery(); - String acrValues = UriUtils.decodeQueryString(queryString).getFirst(OIDCLoginProtocol.ACR_PARAM); - Assert.assertNull(acrValues); - } catch (IOException ioe) { - throw new AssertionError(ioe); - } - }); - - // Test given "acrValues" option will be translated into the "acr_values" parameter passed to Keycloak server - jsDriver.navigate().to(testAppUrl); - testExecutor.configure().init(defaultArguments()); - - loginOptions = JSObjectBuilder.create().acrValues("2fa"); - - testExecutor.login(loginOptions, (JavascriptStateValidator) (driver, output, events) -> { - try { - String queryString = new URL(driver.getCurrentUrl()).getQuery(); - String acrValuesParam = UriUtils.decodeQueryString(queryString).getFirst(OIDCLoginProtocol.ACR_PARAM); - Assert.assertNotNull(acrValuesParam); - assertThat(acrValuesParam, is("2fa")); - } catch (IOException ioe) { - throw new AssertionError(ioe); - } - }); - } - - @Test - public void testUpdateToken() { - XMLHttpRequest request = XMLHttpRequest.create() - .url(authServerContextRootPage + "/auth/admin/realms/" + REALM_NAME + "/roles") - .method("GET") - .addHeader("Accept", "application/json") - .addHeader("Authorization", "Bearer ' + keycloak.token + '"); - - testExecutor.logInAndInit(defaultArguments(), testUser, this::assertInitAuth) - .addTimeSkew(-33); - setTimeOffset(33); - testExecutor.refreshToken(5, assertEventsContains("Auth Refresh Success")); - - setTimeOffset(67); - testExecutor.addTimeSkew(-34) - // Possibility of 0 and 401 is caused by this issue: https://issues.redhat.com/browse/KEYCLOAK-12686 - .sendXMLHttpRequest(request, response -> assertThat(response, hasEntry(is("status"), anyOf(is(0L), is(401L))))) - .refreshToken(5, assertEventsContains("Auth Refresh Success")) - .sendXMLHttpRequest(request, assertResponseStatus(200)); - } - - @Test - public void timeSkewTest() { - testExecutor.logInAndInit(defaultArguments(), testUser, this::assertInitAuth) - .checkTimeSkew((driver1, output, events) -> assertThat(toIntExact((long) output), - is( - both(greaterThan(0 - TIME_SKEW_TOLERANCE)) - .and(lessThan(TIME_SKEW_TOLERANCE)) - ) - )); - - setTimeOffset(40); - - testExecutor.refreshToken(9999, assertEventsContains("Auth Refresh Success")) - .checkTimeSkew((driver1, output, events) -> assertThat(toIntExact((long) output), - is( - both(greaterThan(-40 - TIME_SKEW_TOLERANCE)) - .and(lessThan(-40 + TIME_SKEW_TOLERANCE)) - ) - )); - } - - @Test - public void testOneSecondTimeSkewTokenUpdate() { - setTimeOffset(1); - - testExecutor.logInAndInit(defaultArguments(), testUser, this::assertInitAuth) - .refreshToken(9999, assertEventsContains("Auth Refresh Success")); - - try { - // The events element should contain "Auth logout" but we need to wait for it - // and text().not().contains() doesn't wait. With KEYCLOAK-4179 it took some time for "Auth Logout" to be present - waitUntilElement(eventsArea).text().contains("Auth Logout"); - - throw new RuntimeException("The events element shouldn't contain \"Auth Logout\" text"); - } catch (TimeoutException e) { - // OK - } - } - - @Test - public void testLocationHeaderInResponse() { - XMLHttpRequest request = XMLHttpRequest.create() - .url(authServerContextRootPage + "/auth/admin/realms/" + REALM_NAME + "/users") - .method("POST") - .content("JSON.stringify(JSON.parse('{\"emailVerified\" : false, \"enabled\" : true, \"username\": \"mhajas\", \"firstName\" :\"First\", \"lastName\":\"Last\",\"email\":\"email@redhat.com\", \"attributes\": {}}'))") - .addHeader("Accept", "application/json") - .addHeader("Authorization", "Bearer ' + keycloak.token + '") - .addHeader("Content-Type", "application/json; charset=UTF-8"); - - testExecutor.logInAndInit(defaultArguments(), testUser, this::assertInitAuth) - .sendXMLHttpRequest(request, response -> { - List users = adminClient.realm(REALM_NAME).users().search("mhajas", 0, 1); - assertEquals("There should be created user mhajas", 1, users.size()); - - assertThat(((String) response.get("responseHeaders")).toLowerCase(), containsString("location: " + authServerContextRootPage.toString() + "/auth/admin/realms/" + REALM_NAME + "/users/" + users.get(0).getId())); - }); - } - - @Test - public void equalsSignInRedirectUrl() { - testAppUrl = authServerContextRootPage.toString().replace(AUTH_SERVER_HOST, JS_APP_HOST) + JAVASCRIPT_URL + "/index.html?test=bla=bla&super=man"; - jsDriver.navigate().to(testAppUrl); - - JSObjectBuilder arguments = defaultArguments(); - - testExecutor.init(arguments, this::assertInitNotAuth) - .login(this::assertOnLoginPage) - .loginForm(testUser, this::assertOnTestAppUrl) - .init(arguments, (driver1, output1, events2) -> { - assertTrue(driver1.getCurrentUrl().contains("bla=bla")); - assertInitAuth(driver1, output1, events2); - }); - } - - @Test - public void spaceInRealmNameTest() { - try { - adminClient.realm(REALM_NAME).update(RealmBuilder.edit(adminClient.realm(REALM_NAME).toRepresentation()).name(SPACE_REALM_NAME).build()); - - JSObjectBuilder configuration = JSObjectBuilder.create() - .add("url", authServerContextRootPage + "/auth") - .add("realm", SPACE_REALM_NAME) - .add("clientId", CLIENT_ID); - - testAppUrl = authServerContextRootPage + JAVASCRIPT_ENCODED_SPACE_URL + "/index.html"; - jsDriver.navigate().to(testAppUrl); - jsDriverTestRealmLoginPage.setAuthRealm(SPACE_REALM_NAME); - - testExecutor.configure(configuration) - .init(defaultArguments(), this::assertInitNotAuth) - .login(this::assertOnLoginPage) - .loginForm(testUser, this::assertOnTestAppUrl) - .configure(configuration) - .init(defaultArguments(), this::assertInitAuth); - } finally { - adminClient.realm(SPACE_REALM_NAME).update(RealmBuilder.edit(adminClient.realm(SPACE_REALM_NAME).toRepresentation()).name(REALM_NAME).build()); - jsDriverTestRealmLoginPage.setAuthRealm(REALM_NAME); - } - } - - @Test - public void initializeWithTokenTest() { - oauth.setDriver(jsDriver); - - oauth.realm(REALM_NAME); - oauth.clientId(CLIENT_ID); - oauth.redirectUri(testAppUrl); - oauth.doLogin(testUser); - - String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); - AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password"); - String token = tokenResponse.getAccessToken(); - String refreshToken = tokenResponse.getRefreshToken(); - - testExecutor.init(JSObjectBuilder.create() - .add("token", token) - .add("refreshToken", refreshToken) - , (driver1, output, events) -> { - assertInitAuth(driver1, output, events); - if (SuiteContext.BROWSER_STRICT_COOKIES) { - // iframe is unsupported so a token refresh had to be performed - assertEventsContains("Auth Refresh Success").validate(driver1, output, events); - } - else { - assertEventsDoesntContain("Auth Refresh Success").validate(driver1, output, events); - } - }) - .refreshToken(9999, assertEventsContains("Auth Refresh Success")); - } - - @Test - public void initializeWithTimeSkew() { - oauth.setDriver(jsDriver); // Oauth need to login with jsDriver - - // Get access token and refresh token to initialize with - setTimeOffset(600); - oauth.realm(REALM_NAME); - oauth.clientId(CLIENT_ID); - oauth.redirectUri(testAppUrl); - oauth.doLogin(testUser); - - String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); - AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password"); - String token = tokenResponse.getAccessToken(); - String refreshToken = tokenResponse.getRefreshToken(); - - // Perform test - testExecutor.init(JSObjectBuilder.create() - .add("token", token) - .add("refreshToken", refreshToken) - .add("timeSkew", -600) - , this::assertInitAuth) - .checkTimeSkew((driver1, output, events) -> assertThat((Long) output, is( - both(greaterThan(-600L - TIME_SKEW_TOLERANCE)) - .and(lessThan(-600L + TIME_SKEW_TOLERANCE)) - ))) - .refreshToken(9999, assertEventsContains("Auth Refresh Success")) - .checkTimeSkew((driver1, output, events) -> assertThat((Long) output, is( - both(greaterThan(-600L - TIME_SKEW_TOLERANCE)) - .and(lessThan(-600L + TIME_SKEW_TOLERANCE)) - ))); - } - - @Test - // KEYCLOAK-4503 - public void initializeWithRefreshToken() { - - oauth.setDriver(jsDriver); // Oauth need to login with jsDriver - - oauth.realm(REALM_NAME); - oauth.clientId(CLIENT_ID); - oauth.redirectUri(testAppUrl); - oauth.doLogin(testUser); - - String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); - AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password"); - String token = tokenResponse.getAccessToken(); - String refreshToken = tokenResponse.getRefreshToken(); - - testExecutor.init(JSObjectBuilder.create() - .add("refreshToken", refreshToken) - , (driver1, output, events) -> { - assertInitNotAuth(driver1, output, events); - waitUntilElement(events).text().not().contains("Auth Success"); - }); - } - - @Test - public void reentrancyCallbackTest() { - testExecutor.logInAndInit(defaultArguments(), testUser, this::assertInitAuth) - .executeAsyncScript( - "var callback = arguments[arguments.length - 1];" + - "keycloak.updateToken(60).then(function () {" + - " event(\"First callback\");" + - " keycloak.updateToken(60).then(function () {" + - " event(\"Second callback\");" + - " callback(\"Success\");" + - " });" + - " }" + - ");" - , (driver1, output, events) -> { - waitUntilElement(events).text().contains("First callback"); - waitUntilElement(events).text().contains("Second callback"); - waitUntilElement(events).text().not().contains("Auth Logout"); - } - ); - } - - @Test - public void fragmentInURLTest() { - jsDriver.navigate().to(testAppUrl + "#fragmentPart"); - testExecutor.init(defaultArguments(), this::assertInitNotAuth) - .login(this::assertOnLoginPage) - .loginForm(testUser, this::assertOnTestAppUrl) - .init(defaultArguments(), (driver1, output, events1) -> { - assertInitAuth(driver1, output, events1); - assertThat(driver1.getCurrentUrl(), containsString("#fragmentPart")); - }); - } - - @Test - public void fragmentInLoginFunction() { - testExecutor.init(defaultArguments(), this::assertInitNotAuth) - .login(JSObjectBuilder.create() - .add("redirectUri", testAppUrl + "#fragmentPart") - .build(), this::assertOnLoginPage) - .loginForm(testUser, this::assertOnTestAppUrl) - .init(defaultArguments(), (driver1, output, events1) -> { - assertInitAuth(driver1, output, events1); - assertThat(driver1.getCurrentUrl(), containsString("#fragmentPart")); - }); - } - - @Test - public void testAIAFromJavascriptAdapterSuccess() { - testExecutor.init(defaultArguments(), this::assertInitNotAuth) - .login(JSObjectBuilder.create() - .add("action", "UPDATE_PASSWORD") - .build(), this::assertOnLoginPage) - .loginForm(testUser); - - updatePasswordPage.updatePasswords(USER_PASSWORD, USER_PASSWORD); - - testExecutor.init(defaultArguments(), (driver1, output, events1) -> { - assertInitAuth(driver1, output, events1); - waitUntilElement(events1).text().contains("AIA status: success"); - }); - } - - @Test - public void testAIAFromJavascriptAdapterCancelled() { - testExecutor.init(defaultArguments(), this::assertInitNotAuth) - .login(JSObjectBuilder.create() - .add("action", "UPDATE_PASSWORD") - .build(), this::assertOnLoginPage) - .loginForm(testUser); - - updatePasswordPage.cancel(); - - testExecutor.init(defaultArguments(), (driver1, output, events1) -> { - assertInitAuth(driver1, output, events1); - waitUntilElement(events1).text().contains("AIA status: cancelled"); - }); - } - - @Test - // KEYCLOAK-15158 - public void testInitInHead() { - navigateToTestApp(testAppWithInitInHeadUrl); - - testExecutor.validateOutputField(this::assertInitNotAuth) - .login(this::assertOnLoginPage) - .loginForm(testUser, this::assertOnTestAppWithInitInHeadUrl) - .validateOutputField(this::assertInitAuth); - } - - @Test - public void check3pCookiesMessageCallbackTest() { - testExecutor.attachCheck3pCookiesIframeMutationObserver() - .init(defaultArguments(), this::assertInitNotAuth); - } - - // In case of incorrect/unavailable realm provided in KeycloakConfig, - // JavaScript Adapter init() should fail-fast and reject Promise with KeycloakError. - @Test - public void checkInitWithInvalidRealm() { - - JSObjectBuilder keycloakConfig = JSObjectBuilder.create() - .add("url", authServerContextRootPage + "/auth") - .add("realm", "invalid-realm-name") - .add("clientId", CLIENT_ID); - - JSObjectBuilder initOptions = defaultArguments().add("messageReceiveTimeout", 5000); - - testExecutor - .configure(keycloakConfig) - .init(initOptions, assertErrorResponse("Timeout when waiting for 3rd party check iframe message.")); - - } - - // In case of unavailable Authorization Server due to network or other kind of problems, - // JavaScript Adapter init() should fail-fast and reject Promise with KeycloakError. - @Test - public void checkInitWithUnavailableAuthServer() { - - JSObjectBuilder keycloakConfig = JSObjectBuilder.create() - .add("url", "https://localhost:12345/auth") - .add("realm", REALM_NAME) - .add("clientId", CLIENT_ID); - - JSObjectBuilder initOptions = defaultArguments().add("messageReceiveTimeout", 5000); - - testExecutor - .configure(keycloakConfig) - .init(initOptions, assertErrorResponse("Timeout when waiting for 3rd party check iframe message.")); - - } - - protected void assertAdapterIsLoggedIn(WebDriver driver1, Object output, WebElement events) { - assertTrue(testExecutor.isLoggedIn()); - } - - protected void navigateToTestApp(final String testAppUrl) { - jsDriver.navigate().to(testAppUrl); - waitUntilElement(outputArea).is().present(); - assertCurrentUrlStartsWith(testAppUrl, jsDriver); - } -} diff --git a/testsuite/integration-arquillian/tests/base/testsuites/base-suite b/testsuite/integration-arquillian/tests/base/testsuites/base-suite index 25c0a733237..0ae3c33035c 100644 --- a/testsuite/integration-arquillian/tests/base/testsuites/base-suite +++ b/testsuite/integration-arquillian/tests/base/testsuites/base-suite @@ -19,7 +19,6 @@ feature,4 federation,5 forms,5 i18n,5 -javascript,5 keys,4 login,4 metrics,4