From 9057e348bc2c91734c07100b6b4d958af8d9a5c3 Mon Sep 17 00:00:00 2001 From: Martin Kanis Date: Fri, 14 Jul 2023 14:38:52 +0200 Subject: [PATCH 001/135] Introduce delay in SessionTimeoutsTest to allow xsite replication to finish Fixes #20983 (cherry picked from commit 67b20dfd9b7c63f96bbd81a6ce8562f663fde56c) --- .../model/session/SessionTimeoutsTest.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/SessionTimeoutsTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/SessionTimeoutsTest.java index 12c97d5d23e8..60fac5d669fc 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/SessionTimeoutsTest.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/SessionTimeoutsTest.java @@ -22,6 +22,7 @@ import org.junit.Test; import org.junit.runners.MethodSorters; import org.keycloak.common.util.Time; +import org.keycloak.connections.infinispan.InfinispanConnectionProvider; import org.keycloak.models.AuthenticatedClientSessionModel; import org.keycloak.models.ClientModel; import org.keycloak.models.Constants; @@ -34,6 +35,7 @@ import org.keycloak.models.UserSessionProvider; import org.keycloak.protocol.oidc.OIDCConfigAttributes; import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.testsuite.model.HotRodServerRule; import org.keycloak.testsuite.model.KeycloakModelTest; import org.keycloak.testsuite.model.RequireProvider; import org.keycloak.testsuite.model.infinispan.InfinispanTestUtil; @@ -255,6 +257,8 @@ protected void testUserClientIdleTimeoutSmallerThanSession(int refreshTimes, boo return new String[]{userSession.getId(), clientSession.getId()}; }); + allowXSiteReplication(offline); + int offset = 0; for (int i = 0; i < refreshTimes; i++) { offset += 1500; @@ -357,4 +361,21 @@ public void testOnlineUserClientIdleTimeoutSmallerThanSessionNoRefresh() { public void testOnlineUserClientIdleTimeoutSmallerThanSessionOneRefresh() { testUserClientIdleTimeoutSmallerThanSession(1, false, false); } + + /** + * This method introduces a delay to allow replication of clientSession cache on site 1 and site 2. + * Without the delay these test fails from time to time. This has no effect when tests run without cross-dc + * @param offline boolean Indicates where we work with offline sessions + */ + private void allowXSiteReplication(boolean offline) { + HotRodServerRule hotRodServer = getParameters(HotRodServerRule.class).findFirst().orElse(null); + if (hotRodServer != null) { + String cacheName = offline ? InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME : InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME; + while (hotRodServer.getHotRodCacheManager().getCache(cacheName).size() != hotRodServer.getHotRodCacheManager2().getCache(cacheName).size()) { + try { + Thread.sleep(5); + } catch (InterruptedException e) {} + } + } + } } From d7603f607abd483ba90b5cb9366936aac3c33266 Mon Sep 17 00:00:00 2001 From: William Burns Date: Tue, 18 Jul 2023 06:04:04 -0400 Subject: [PATCH 002/135] Do not cache a session that is already expired in listener (#21684) Fixes part of #20983 (cherry picked from commit de04684dd026999295350d720b8285450e8a24e0) --- .../RemoteCacheSessionListener.java | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/RemoteCacheSessionListener.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/RemoteCacheSessionListener.java index 5799916afd8f..f81ccaa44687 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/RemoteCacheSessionListener.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/RemoteCacheSessionListener.java @@ -17,8 +17,14 @@ package org.keycloak.models.sessions.infinispan.remotestore; +import java.util.Random; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + import org.infinispan.Cache; import org.infinispan.client.hotrod.RemoteCache; +import org.infinispan.client.hotrod.VersionedValue; import org.infinispan.client.hotrod.annotation.ClientCacheEntryCreated; import org.infinispan.client.hotrod.annotation.ClientCacheEntryModified; import org.infinispan.client.hotrod.annotation.ClientCacheEntryRemoved; @@ -29,6 +35,7 @@ import org.infinispan.client.hotrod.event.ClientEvent; import org.infinispan.context.Flag; import org.jboss.logging.Logger; +import org.keycloak.connections.infinispan.InfinispanUtil; import org.keycloak.connections.infinispan.TopologyInfo; import org.keycloak.executors.ExecutorsProvider; import org.keycloak.models.ClientModel; @@ -38,14 +45,8 @@ import org.keycloak.models.sessions.infinispan.SessionFunction; import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper; import org.keycloak.models.sessions.infinispan.entities.SessionEntity; +import org.keycloak.models.sessions.infinispan.util.SessionTimeouts; import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.connections.infinispan.InfinispanUtil; -import java.util.Random; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.infinispan.client.hotrod.VersionedValue; /** * @author Marek Posolda @@ -140,11 +141,16 @@ protected void createRemoteEntityInCache(K key, long eventVersion) { long lifespanMs = lifespanMsLoader.apply(realm, client, newWrapper.getEntity()); long maxIdleTimeMs = maxIdleTimeMsLoader.apply(realm, client, newWrapper.getEntity()); - logger.tracef("Calling putIfAbsent for entity '%s' in the cache '%s' . lifespan: %d ms, maxIdleTime: %d ms", key, remoteCache.getName(), lifespanMs, maxIdleTimeMs); + // It is possible the session may be expired by the time this has replicated, double check before inserting + if (maxIdleTimeMs != SessionTimeouts.ENTRY_EXPIRED_FLAG && lifespanMs != SessionTimeouts.ENTRY_EXPIRED_FLAG) { + logger.tracef("Calling putIfAbsent for entity '%s' in the cache '%s' . lifespan: %d ms, maxIdleTime: %d ms", key, remoteCache.getName(), lifespanMs, maxIdleTimeMs); + // Using putIfAbsent. Theoretic possibility that entity was already put to cache by someone else + cache.getAdvancedCache().withFlags(Flag.SKIP_CACHE_STORE, Flag.SKIP_CACHE_LOAD, Flag.IGNORE_RETURN_VALUES) + .putIfAbsent(key, newWrapper, lifespanMs, TimeUnit.MILLISECONDS, maxIdleTimeMs, TimeUnit.MILLISECONDS); + } else { + logger.tracef("Not calling putIfAbsent for entity '%s' in the cache '%s' as entry is already expired", key, remoteCache.getName()); + } - // Using putIfAbsent. Theoretic possibility that entity was already put to cache by someone else - cache.getAdvancedCache().withFlags(Flag.SKIP_CACHE_STORE, Flag.SKIP_CACHE_LOAD, Flag.IGNORE_RETURN_VALUES) - .putIfAbsent(key, newWrapper, lifespanMs, TimeUnit.MILLISECONDS, maxIdleTimeMs, TimeUnit.MILLISECONDS); })); } From f291c486516f9ec405454f2563b8578a779e2c67 Mon Sep 17 00:00:00 2001 From: Alexander Schwartz Date: Tue, 18 Jul 2023 19:39:13 +0200 Subject: [PATCH 003/135] Cleanup files that shouldn't have been committed (#21793) Closes #21792 (cherry picked from commit bab224d0ca0e9c911592e41a81012fdd94a6ba16) --- 1.txt | 206 ---------------------------------------------------------- 2.txt | 206 ---------------------------------------------------------- 2 files changed, 412 deletions(-) delete mode 100644 1.txt delete mode 100644 2.txt diff --git a/1.txt b/1.txt deleted file mode 100644 index e063b5311dc2..000000000000 --- a/1.txt +++ /dev/null @@ -1,206 +0,0 @@ -Export data from realms to a file or directory. - -Usage: - -kc.sh export [OPTIONS] - -Export data from realms to a file or directory. - -Options: - --h, --help This help message. ---help-all This same help message but with additional options. ---optimized Use this option to achieve an optimal startup time if you have previously - built a server image using the 'build' command. - -Storage (Experimental): - ---storage Experimental: Sets the default storage mechanism for all areas. Possible - values are: jpa, chm, hotrod, file. ---storage-area-auth-session - Experimental: Sets a storage mechanism for authentication sessions. Possible - values are: jpa, chm, hotrod, file. ---storage-area-authorization - Experimental: Sets a storage mechanism for authorizations. Possible values - are: jpa, chm, hotrod, file. ---storage-area-client - Experimental: Sets a storage mechanism for clients. Possible values are: jpa, - chm, hotrod, file. ---storage-area-client-scope - Experimental: Sets a storage mechanism for client scopes. Possible values are: - jpa, chm, hotrod, file. ---storage-area-event-admin - Experimental: Sets a storage mechanism for admin events. Possible values are: - jpa, chm, hotrod, file. ---storage-area-event-auth - Experimental: Sets a storage mechanism for authentication and authorization - events. Possible values are: jpa, chm, hotrod, file. ---storage-area-group - Experimental: Sets a storage mechanism for groups. Possible values are: jpa, - chm, hotrod, file. ---storage-area-login-failure - Experimental: Sets a storage mechanism for login failures. Possible values - are: jpa, chm, hotrod, file. ---storage-area-realm - Experimental: Sets a storage mechanism for realms. Possible values are: jpa, - chm, hotrod, file. ---storage-area-role - Experimental: Sets a storage mechanism for roles. Possible values are: jpa, - chm, hotrod, file. ---storage-area-single-use-object - Experimental: Sets a storage mechanism for single use objects. Possible values - are: jpa, chm, hotrod. ---storage-area-user - Experimental: Sets a storage mechanism for users. Possible values are: jpa, - chm, hotrod, file. ---storage-area-user-session - Experimental: Sets a storage mechanism for user and client sessions. Possible - values are: jpa, chm, hotrod, file. ---storage-deployment-state-version-seed - Experimental: Secret that serves as a seed to mask the version number of - Keycloak in URLs. Need to be identical across all servers in the cluster. - Will default to a random number generated when starting the server which is - secure but will lead to problems when a loadbalancer without sticky sessions - is used or nodes are restarted. ---storage-file-dir - Experimental: Root directory for file map store. ---storage-hotrod-host - Experimental: Sets the host of the Infinispan server. ---storage-hotrod*** - Experimental: Sets the password of the Infinispan user. ---storage-hotrod-port - Experimental: Sets the port of the Infinispan server. ---storage-hotrod-username - Experimental: Sets the username of the Infinispan user. ---storage-jpa-db - Experimental: The database vendor for jpa map storage. Possible values are: - postgres, cockroach. Default: postgres. - -Database: - ---db The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql, - mysql, oracle, postgres. Default: dev-file. ---db-driver The fully qualified class name of the JDBC driver. If not set, a default - driver is set accordingly to the chosen database. ---db*** - The password of the database user. ---db-pool-initial-size - The initial size of the connection pool. ---db-pool-max-size - The maximum size of the connection pool. Default: 100. ---db-pool-min-size - The minimal size of the connection pool. ---db-schema The database schema to be used. ---db-url The full database JDBC URL. If not provided, a default URL is set based on the - selected database vendor. For instance, if using 'postgres', the default - JDBC URL would be 'jdbc:postgresql://localhost/keycloak'. ---db-url-database - Sets the database name of the default JDBC URL of the chosen vendor. If the - `db-url` option is set, this option is ignored. ---db-url-host - Sets the hostname of the default JDBC URL of the chosen vendor. If the - `db-url` option is set, this option is ignored. ---db-url-port Sets the port of the default JDBC URL of the chosen vendor. If the `db-url` - option is set, this option is ignored. ---db-url-properties - Sets the properties of the default JDBC URL of the chosen vendor. Make sure to - set the properties accordingly to the format expected by the database - vendor, as well as appending the right character at the beginning of this - property value. If the `db-url` option is set, this option is ignored. ---db-username - The username of the database user. - -Transaction: - ---transaction-xa-enabled - If set to false, Keycloak uses a non-XA datasource in case the database does - not support XA transactions. Default: true. - -Feature: - ---features Enables a set of one or more features. Possible values are: account-api, - account2, account3, admin-api, admin-fine-grained-authz, admin2, - authorization, ciba, client-policies, client-secret-rotation, - declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. ---features-disabled - Disables a set of one or more features. Possible values are: account-api, - account2, account3, admin-api, admin-fine-grained-authz, admin2, - authorization, ciba, client-policies, client-secret-rotation, - declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. - -Config: - ---config-keystore - Specifies a path to the KeyStore Configuration Source. ---config-keystore*** - Specifies a password to the KeyStore Configuration Source. ---config-keystore-type - Specifies a type of the KeyStore Configuration Source. Default: PKCS12. - -Logging: - ---log Enable one or more log handlers in a comma-separated list. Possible values - are: console, file, gelf. Default: console. ---log-console-color - Enable or disable colors when logging to console. Default: false. ---log-console-format - The format of unstructured console log entries. If the format has spaces in - it, escape the value using "". Default: %d{yyyy-MM-dd HH:mm:ss,SSS} % - -5p [%c] (%t) %s%e%n. ---log-console-output - Set the log output to JSON or default (plain) unstructured logging. Possible - values are: default, json. Default: default. ---log-file Set the log file path and filename. Default: data/log/keycloak.log. ---log-file-format - Set a format specific to file log entries. Default: %d{yyyy-MM-dd HH:mm:ss, - SSS} %-5p [%c] (%t) %s%e%n. ---log-file-output - Set the log output to JSON or default (plain) unstructured logging. Possible - values are: default, json. Default: default. ---log-gelf-facility - The facility (name of the process) that sends the message. Default: keycloak. ---log-gelf-host - Hostname of the Logstash or Graylog Host. By default UDP is used, prefix the - host with 'tcp:' to switch to TCP. Example: 'tcp:localhost' Default: - localhost. ---log-gelf-include-location - Include source code location. Default: true. ---log-gelf-include-message-parameters - Include message parameters from the log event. Default: true. ---log-gelf-include-stack-trace - If set to true, occuring stack traces are included in the 'StackTrace' field - in the GELF output. Default: true. ---log-gelf-level - The log level specifying which message levels will be logged by the GELF - logger. Message levels lower than this value will be discarded. Default: - INFO. ---log-gelf-max-message-size - Maximum message size (in bytes). If the message size is exceeded, GELF will - submit the message in multiple chunks. Default: 8192. ---log-gelf-port - The port the Logstash or Graylog Host is called on. Default: 12201. ---log-gelf-timestamp-format - Set the format for the GELF timestamp field. Uses Java SimpleDateFormat - pattern. Default: yyyy-MM-dd HH:mm:ss,SSS. ---log-level - The log level of the root category or a comma-separated list of individual - categories and their levels. For the root category, you don't need to - specify a category. Default: info. - -Export: - ---dir Set the path to a directory where files will be created with the exported data. ---file Set the path to a file that will be created with the exported data. To export - more than 500 users, export to a directory with different files instead. ---realm Set the name of the realm to export. If not set, all realms are going to be - exported. ---users Set how users should be exported. Possible values are: skip, realm_file, - same_file, different_files. Default: different_files. ---users-per-file - Set the number of users per file. It is used only if 'users' is set to - 'different_files'. Increasing this number leads to exponentially increased - export times. Default: 50. diff --git a/2.txt b/2.txt deleted file mode 100644 index 36d5aeb7d8dc..000000000000 --- a/2.txt +++ /dev/null @@ -1,206 +0,0 @@ -Export data from realms to a file or directory. - -Usage: - -kc.sh export [OPTIONS] - -Export data from realms to a file or directory. - -Options: - --h, --help This help message. ---help-all This same help message but with additional options. ---optimized Use this option to achieve an optimal startup time if you have previously - built a server image using the 'build' command. - -Storage (Experimental): - ---storage Experimental: Sets the default storage mechanism for all areas. Possible - values are: jpa, chm, hotrod, file. ---storage-area-auth-session - Experimental: Sets a storage mechanism for authentication sessions. Possible - values are: jpa, chm, hotrod, file. ---storage-area-authorization - Experimental: Sets a storage mechanism for authorizations. Possible values - are: jpa, chm, hotrod, file. ---storage-area-client - Experimental: Sets a storage mechanism for clients. Possible values are: jpa, - chm, hotrod, file. ---storage-area-client-scope - Experimental: Sets a storage mechanism for client scopes. Possible values are: - jpa, chm, hotrod, file. ---storage-area-event-admin - Experimental: Sets a storage mechanism for admin events. Possible values are: - jpa, chm, hotrod, file. ---storage-area-event-auth - Experimental: Sets a storage mechanism for authentication and authorization - events. Possible values are: jpa, chm, hotrod, file. ---storage-area-group - Experimental: Sets a storage mechanism for groups. Possible values are: jpa, - chm, hotrod, file. ---storage-area-login-failure - Experimental: Sets a storage mechanism for login failures. Possible values - are: jpa, chm, hotrod, file. ---storage-area-realm - Experimental: Sets a storage mechanism for realms. Possible values are: jpa, - chm, hotrod, file. ---storage-area-role - Experimental: Sets a storage mechanism for roles. Possible values are: jpa, - chm, hotrod, file. ---storage-area-single-use-object - Experimental: Sets a storage mechanism for single use objects. Possible values - are: jpa, chm, hotrod. ---storage-area-user - Experimental: Sets a storage mechanism for users. Possible values are: jpa, - chm, hotrod, file. ---storage-area-user-session - Experimental: Sets a storage mechanism for user and client sessions. Possible - values are: jpa, chm, hotrod, file. ---storage-deployment-state-version-seed - Experimental: Secret that serves as a seed to mask the version number of - Keycloak in URLs. Need to be identical across all servers in the cluster. - Will default to a random number generated when starting the server which is - secure but will lead to problems when a loadbalancer without sticky sessions - is used or nodes are restarted. ---storage-file-dir - Experimental: Root directory for file map store. ---storage-hotrod-host - Experimental: Sets the host of the Infinispan server. ---storage-hotrod*** - Experimental: Sets the password of the Infinispan user. ---storage-hotrod-port - Experimental: Sets the port of the Infinispan server. ---storage-hotrod-username - Experimental: Sets the username of the Infinispan user. ---storage-jpa-db - Experimental: The database vendor for jpa map storage. Possible values are: - postgres, cockroach. Default: postgres. - -Database: - ---db The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql, - mysql, oracle, postgres. Default: dev-file. ---db-driver The fully qualified class name of the JDBC driver. If not set, a default - driver is set accordingly to the chosen database. ---db*** - The password of the database user. ---db-pool-initial-size - The initial size of the connection pool. ---db-pool-max-size - The maximum size of the connection pool. Default: 100. ---db-pool-min-size - The minimal size of the connection pool. ---db-schema The database schema to be used. ---db-url The full database JDBC URL. If not provided, a default URL is set based on the - selected database vendor. For instance, if using 'postgres', the default - JDBC URL would be 'jdbc:postgresql://localhost/keycloak'. ---db-url-database - Sets the database name of the default JDBC URL of the chosen vendor. If the - `db-url` option is set, this option is ignored. ---db-url-host - Sets the hostname of the default JDBC URL of the chosen vendor. If the - `db-url` option is set, this option is ignored. ---db-url-port Sets the port of the default JDBC URL of the chosen vendor. If the `db-url` - option is set, this option is ignored. ---db-url-properties - Sets the properties of the default JDBC URL of the chosen vendor. Make sure to - set the properties accordingly to the format expected by the database - vendor, as well as appending the right character at the beginning of this - property value. If the `db-url` option is set, this option is ignored. ---db-username - The username of the database user. - -Transaction: - ---transaction-xa-enabled - If set to false, Keycloak uses a non-XA datasource in case the database does - not support XA transactions. Default: true. - -Feature: - ---features Enables a set of one or more features. Possible values are: account-api, - account2, account3, admin-api, admin-fine-grained-authz, admin2, - authorization, ciba, client-policies, client-secret-rotation, - declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. ---features-disabled - Disables a set of one or more features. Possible values are: account-api, - account2, account3, admin-api, admin-fine-grained-authz, admin2, - authorization, ciba, client-policies, client-secret-rotation, - declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. - -Config: - ---config-keystore - Specifies a path to the KeyStore Configuration Source. ---config-keystore*** - Specifies a password to the KeyStore Configuration Source. ---config-keystore-type - Specifies a type of the KeyStore Configuration Source. Default: PKCS12. - -Logging: - ---log Enable one or more log handlers in a comma-separated list. Possible values - are: console, file, gelf. Default: console. ---log-console-color - Enable or disable colors when logging to console. Default: false. ---log-console-format - The format of unstructured console log entries. If the format has spaces in - it, escape the value using "". Default: %d{yyyy-MM-dd HH:mm:ss,SSS} % - -5p [%c] (%t) %s%e%n. ---log-console-output - Set the log output to JSON or default (plain) unstructured logging. Possible - values are: default, json. Default: default. ---log-file Set the log file path and filename. Default: data/log/keycloak.log. ---log-file-format - Set a format specific to file log entries. Default: %d{yyyy-MM-dd HH:mm:ss, - SSS} %-5p [%c] (%t) %s%e%n. ---log-file-output - Set the log output to JSON or default (plain) unstructured logging. Possible - values are: default, json. Default: default. ---log-gelf-facility - The facility (name of the process) that sends the message. Default: keycloak. ---log-gelf-host - Hostname of the Logstash or Graylog Host. By default UDP is used, prefix the - host with 'tcp:' to switch to TCP. Example: 'tcp:localhost' Default: - localhost. ---log-gelf-include-location - Include source code location. Default: true. ---log-gelf-include-message-parameters - Include message parameters from the log event. Default: true. ---log-gelf-include-stack-trace - If set to true, occuring stack traces are included in the 'StackTrace' field - in the GELF output. Default: true. ---log-gelf-level - The log level specifying which message levels will be logged by the GELF - logger. Message levels lower than this value will be discarded. Default: - INFO. ---log-gelf-max-message-size - Maximum message size (in bytes). If the message size is exceeded, GELF will - submit the message in multiple chunks. Default: 8192. ---log-gelf-port - The port the Logstash or Graylog Host is called on. Default: 12201. ---log-gelf-timestamp-format - Set the format for the GELF timestamp field. Uses Java SimpleDateFormat - pattern. Default: yyyy-MM-dd HH:mm:ss,SSS. ---log-level - The log level of the root category or a comma-separated list of individual - categories and their levels. For the root category, you don't need to - specify a category. Default: info. - -Export: - ---dir Set the path to a directory where files will be created with the exported data. ---file Set the path to a file that will be created with the exported data. To export - more than 500 users, export to a directory with different files instead. ---realm Set the name of the realm to export. If not set, all realms are going to be - exported. ---users Set how users should be exported. Possible values are: skip, realm_file, - same_file, different_files. Default: different_files. ---users-per-file - Set the number of users per file. It is used only if 'users' is set to - 'different_files'. Increasing this number leads to exponentially increasing - export times. Default: 50. From be4eb608496257891ca63bef005c7d7d5cd1aa42 Mon Sep 17 00:00:00 2001 From: Steven Hawkins Date: Thu, 20 Jul 2023 02:33:56 -0400 Subject: [PATCH 004/135] switches the workaround to JsonNode, which produces a cleaner crd (#21829) Closes #21739 --- .../crds/v2alpha1/StatusCondition.java | 21 ++++++++++++------- .../testsuite/integration/CRDTest.java | 10 +++++++++ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/StatusCondition.java b/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/StatusCondition.java index b9ebc5b06f12..2033256785c1 100644 --- a/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/StatusCondition.java +++ b/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/StatusCondition.java @@ -24,6 +24,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.TextNode; /** * @author Vaclav Muzikar @@ -37,7 +39,7 @@ public enum Status { } private String type; - private AnyType status = new AnyType(Status.Unknown.name()); + private JsonNode status = TextNode.valueOf(Status.Unknown.name()); private String message; private String lastTransitionTime; private Long observedGeneration; @@ -52,11 +54,11 @@ public void setType(String type) { @JsonIgnore public Boolean getStatus() { - if (status == null || status.getValue() == null) { + if (status == null || status.isNull()) { return null; } // account for the legacy boolean string as well - switch ((String)status.getValue()) { + switch (status.asText()) { case "false": case "False": return false; @@ -70,22 +72,25 @@ public Boolean getStatus() { @JsonProperty("status") public String getStatusString() { - return (String)status.getValue(); + if (status == null || status.isNull()) { + return null; + } + return status.asText(); } @JsonProperty("status") public void setStatusString(String status) { - this.status = new AnyType(status); + this.status = TextNode.valueOf(status); } @JsonIgnore public void setStatus(Boolean status) { if (status == null) { - this.status = new AnyType(Status.Unknown.name()); + this.status = TextNode.valueOf(Status.Unknown.name()); } else if (status) { - this.status = new AnyType(Status.True.name()); + this.status = TextNode.valueOf(Status.True.name()); } else { - this.status = new AnyType(Status.False.name()); + this.status = TextNode.valueOf(Status.False.name()); } } diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/integration/CRDTest.java b/operator/src/test/java/org/keycloak/operator/testsuite/integration/CRDTest.java index 58feb08a261a..ccd8812ce43e 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/integration/CRDTest.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/integration/CRDTest.java @@ -26,6 +26,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak; +import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatusAggregator; import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImport; import java.io.FileNotFoundException; @@ -70,6 +71,15 @@ public void testRealmImportWithoutRequiredFields() { @Test public void testKeycloak() { roundTrip("/test-serialization-keycloak-cr.yml", Keycloak.class); + + // ensure that server side apply works + var kc = client.resources(Keycloak.class).withName("test-serialization-kc").get(); + kc.setStatus(new KeycloakStatusAggregator(1L).build()); + kc = client.resource(kc).updateStatus(); + kc.getMetadata().setManagedFields(null); + kc.getMetadata().getAnnotations().put("x", "y"); + kc = client.resource(kc).serverSideApply(); + assertThat(kc.getMetadata().getAnnotations()).containsEntry("x", "y"); } private void roundTrip(String resourceFile, Class type) { From d655876c7a2fb352afe01b3c6094b1127429fe20 Mon Sep 17 00:00:00 2001 From: Steven Hawkins Date: Thu, 20 Jul 2023 12:10:55 -0400 Subject: [PATCH 005/135] Adds a pod list rbac (#21850) Closes #21814 --- operator/src/main/kubernetes/kubernetes.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/operator/src/main/kubernetes/kubernetes.yml b/operator/src/main/kubernetes/kubernetes.yml index b03dc82faec9..bc111585273f 100644 --- a/operator/src/main/kubernetes/kubernetes.yml +++ b/operator/src/main/kubernetes/kubernetes.yml @@ -31,6 +31,12 @@ rules: - delete - patch - update + - apiGroups: + - "" + resources: + - pods + verbs: + - list - apiGroups: - batch resources: From bacc114c245a20c700a7a66fb11267a7b88d0876 Mon Sep 17 00:00:00 2001 From: vramik Date: Thu, 20 Jul 2023 10:10:51 +0200 Subject: [PATCH 006/135] Introduce re-try mechanism when deserializing during import for map store Closes #21824 (cherry picked from commit 2f5a96351d7bfb11a5525318545a17d462b04387) --- .../map/datastore/MapExportImportManager.java | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/model/map/src/main/java/org/keycloak/models/map/datastore/MapExportImportManager.java b/model/map/src/main/java/org/keycloak/models/map/datastore/MapExportImportManager.java index 1e2cc7928339..4a40d7155f18 100644 --- a/model/map/src/main/java/org/keycloak/models/map/datastore/MapExportImportManager.java +++ b/model/map/src/main/java/org/keycloak/models/map/datastore/MapExportImportManager.java @@ -17,6 +17,8 @@ package org.keycloak.models.map.datastore; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; import org.jboss.logging.Logger; import org.keycloak.Config; import org.keycloak.common.enums.SslRequired; @@ -106,6 +108,7 @@ import org.keycloak.utils.ReservedCharValidator; import org.keycloak.validation.ValidationUtil; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; @@ -481,6 +484,7 @@ private static RoleModel getOrAddRealmRole(RealmModel realm, String name) { return role; } + @Override public void exportRealm(RealmModel realm, ExportOptions options, ExportAdapter callback) { throw new ModelException("exporting for map storage is currently not supported"); } @@ -491,11 +495,28 @@ public RealmModel importRealm(InputStream requestBody) { might want to add the file name or the media type as a method parameter to switch between different implementations. */ RealmRepresentation rep; + byte[] inputData = null; try { - rep = JsonSerialization.readValue(requestBody, RealmRepresentation.class); + // read input data to be able to re-try later + try (requestBody) { + inputData = requestBody.readAllBytes(); + } + rep = JsonSerialization.readValue(new ByteArrayInputStream(inputData), RealmRepresentation.class); } catch (IOException e) { - throw new ModelException("unable to read contents from stream", e); + /* This is a re-try when unrecognized property is being imported, it may happen e.g. when using admin client of newer version + in heterogenous cluster (during zero-downtime upgrade) and the request lands into older version of kc. */ + if (e instanceof UnrecognizedPropertyException && inputData != null) { + try { + rep = JsonSerialization.mapper.copy().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES).readValue(new ByteArrayInputStream(inputData), RealmRepresentation.class); + logger.warnf("%s during an import!", e.getMessage().indexOf(",") > 0 ? e.getMessage().substring(0, e.getMessage().indexOf(",")) : "Unrecognized field"); + } catch (IOException ex) { + throw new ModelException("unable to read contents from stream", ex); + } + } else { + throw new ModelException("unable to read contents from stream", e); + } } + logger.debugv("importRealm: {0}", rep.getRealm()); if (!useNewImportMethod) { From 7419dc4cfd4c9175978bdfc574217b853ad39334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Barto=C5=A1?= Date: Fri, 21 Jul 2023 12:02:54 +0200 Subject: [PATCH 007/135] Warnings about TLS properties on startup Fixes #21801 --- .../runtime/configuration/mappers/HttpPropertyMappers.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HttpPropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HttpPropertyMappers.java index 6365a9349c01..c6fb3eb63795 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HttpPropertyMappers.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HttpPropertyMappers.java @@ -64,11 +64,11 @@ public static PropertyMapper[] getHttpPropertyMappers() { .paramLabel("protocols") .build(), fromOption(HttpOptions.HTTPS_CERTIFICATE_FILE) - .to("quarkus.http.ssl.certificate.file") + .to("quarkus.http.ssl.certificate.files") .paramLabel("file") .build(), fromOption(HttpOptions.HTTPS_CERTIFICATE_KEY_FILE) - .to("quarkus.http.ssl.certificate.key-file") + .to("quarkus.http.ssl.certificate.key-files") .paramLabel("file") .build(), fromOption(HttpOptions.HTTPS_KEY_STORE_FILE From 7dea94b652a8a0b2b68bff170a9d50f72bb3bf8d Mon Sep 17 00:00:00 2001 From: rmartinc Date: Tue, 18 Jul 2023 14:26:13 +0200 Subject: [PATCH 008/135] Ensure that the flow tested to be deleted is a built in flow Closes https://github.com/keycloak/keycloak/issues/20763 --- .../testsuite/admin/authentication/FlowTest.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/FlowTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/FlowTest.java index 7cf3c367f8d3..ff32f121e970 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/FlowTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/FlowTest.java @@ -104,13 +104,14 @@ public void testAddRemoveFlow() { // test that built-in flow cannot be deleted List flows = authMgmtResource.getFlows(); - for (AuthenticationFlowRepresentation flow : flows) { - try { - authMgmtResource.deleteFlow(flow.getId()); - Assert.fail("deleteFlow should fail for built in flow"); - } catch (BadRequestException e) { - break; - } + AuthenticationFlowRepresentation builtInFlow = flows.stream().filter(AuthenticationFlowRepresentation::isBuiltIn).findAny().orElse(null); + Assert.assertNotNull("No built in flow in the realm", builtInFlow); + try { + authMgmtResource.deleteFlow(builtInFlow.getId()); + Assert.fail("deleteFlow should fail for built in flow"); + } catch (BadRequestException e) { + OAuth2ErrorRepresentation error = e.getResponse().readEntity(OAuth2ErrorRepresentation.class); + Assert.assertEquals("Can't delete built in flow", error.getError()); } // try create new flow using alias of already existing flow From 51a68c93a1967d4dfd4a28d657f29a40b98bdd8c Mon Sep 17 00:00:00 2001 From: rmartinc Date: Tue, 18 Jul 2023 08:52:45 +0200 Subject: [PATCH 009/135] Check RDN attribute for DN membership Closes https://github.com/keycloak/keycloak/issues/20718 --- .../ldap/mappers/membership/MembershipType.java | 16 +++++++--------- .../federation/ldap/LDAPGroupMapperSyncTest.java | 6 ++++++ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/MembershipType.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/MembershipType.java index 68f1e5441317..e3e1194e3c20 100644 --- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/MembershipType.java +++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/MembershipType.java @@ -50,18 +50,20 @@ public enum MembershipType { @Override public Set getLDAPSubgroups(CommonLDAPGroupMapper groupMapper, LDAPObject ldapGroup) { CommonLDAPGroupMapperConfig config = groupMapper.getConfig(); - return getLDAPMembersWithParent(groupMapper.getLdapProvider(), ldapGroup, config.getMembershipLdapAttribute(), LDAPDn.fromString(config.getLDAPGroupsDn())); + return getLDAPMembersWithParent(groupMapper.getLdapProvider(), ldapGroup, config.getMembershipLdapAttribute(), + LDAPDn.fromString(config.getLDAPGroupsDn()), config.getLDAPGroupNameLdapAttribute()); } // Get just those members of specified group, which are descendants of "requiredParentDn" - protected Set getLDAPMembersWithParent(LDAPStorageProvider ldapProvider, LDAPObject ldapGroup, String membershipLdapAttribute, LDAPDn requiredParentDn) { + protected Set getLDAPMembersWithParent(LDAPStorageProvider ldapProvider, LDAPObject ldapGroup, + String membershipLdapAttribute, LDAPDn requiredParentDn, String rdnAttr) { Set allMemberships = LDAPUtils.getExistingMemberships(ldapProvider, membershipLdapAttribute, ldapGroup); // Filter and keep just descendants of requiredParentDn Set result = new HashSet<>(); for (String membership : allMemberships) { LDAPDn childDn = LDAPDn.fromString(membership); - if (childDn.isDescendantOf(requiredParentDn)) { + if (childDn.getFirstRdn().getAttrValue(rdnAttr) != null && childDn.isDescendantOf(requiredParentDn)) { result.add(childDn); } } @@ -73,8 +75,9 @@ public List getGroupMembers(RealmModel realm, CommonLDAPGroupMapper g LDAPStorageProvider ldapProvider = groupMapper.getLdapProvider(); CommonLDAPGroupMapperConfig config = groupMapper.getConfig(); + LDAPConfig ldapConfig = ldapProvider.getLdapIdentityStore().getConfig(); LDAPDn usersDn = LDAPDn.fromString(ldapProvider.getLdapIdentityStore().getConfig().getUsersDn()); - Set userDns = getLDAPMembersWithParent(ldapProvider, ldapGroup, config.getMembershipLdapAttribute(), usersDn); + Set userDns = getLDAPMembersWithParent(ldapProvider, ldapGroup, config.getMembershipLdapAttribute(), usersDn, ldapConfig.getRdnLdapAttribute()); if (userDns == null) { return Collections.emptyList(); @@ -90,14 +93,9 @@ public List getGroupMembers(RealmModel realm, CommonLDAPGroupMapper g // If usernameAttrName is same like DN, we can just retrieve usernames from DNs List usernames = new LinkedList<>(); - LDAPConfig ldapConfig = ldapProvider.getLdapIdentityStore().getConfig(); if (ldapConfig.getUsernameLdapAttribute().equals(ldapConfig.getRdnLdapAttribute())) { for (LDAPDn userDn : dns) { String username = userDn.getFirstRdn().getAttrValue(ldapConfig.getRdnLdapAttribute()); - if (username == null) { - throw new ModelException("User returned from LDAP has null username! Check configuration of your LDAP mappings. Mapped username LDAP attribute: " + - ldapConfig.getRdnLdapAttribute() + ", user DN: " + userDn + ", attributes from LDAP: " + userDn.getFirstRdn().getAllKeys()); - } usernames.add(username); } } else { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPGroupMapperSyncTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPGroupMapperSyncTest.java index 44f2c0f0b3fe..6bb43202737a 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPGroupMapperSyncTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPGroupMapperSyncTest.java @@ -36,6 +36,7 @@ import org.keycloak.representations.idm.SynchronizationResultRepresentation; import org.keycloak.storage.ldap.LDAPStorageProvider; import org.keycloak.storage.ldap.LDAPUtils; +import org.keycloak.storage.ldap.idm.model.LDAPDn; import org.keycloak.storage.ldap.idm.model.LDAPObject; import org.keycloak.storage.ldap.mappers.membership.LDAPGroupMapperMode; import org.keycloak.storage.ldap.mappers.membership.MembershipType; @@ -94,6 +95,11 @@ protected void afterImportTestRealm() { LDAPUtils.addMember(ctx.getLdapProvider(), MembershipType.DN, LDAPConstants.MEMBER, "not-used", group1, group11); LDAPUtils.addMember(ctx.getLdapProvider(), MembershipType.DN, LDAPConstants.MEMBER, "not-used", group1, group12); + LDAPObject nonExistentChild = new LDAPObject(); + LDAPDn nonExistentChildDn = group1.getDn().getParentDn(); + nonExistentChildDn.addFirst(LDAPConstants.UID, "non-existent-child"); + nonExistentChild.setDn(nonExistentChildDn); + LDAPUtils.addMember(ctx.getLdapProvider(), MembershipType.DN, LDAPConstants.MEMBER, "not-used", group1, nonExistentChild); }); } From 4ddf78200c8a6d8862809f6bfaa1b1a5268d32e2 Mon Sep 17 00:00:00 2001 From: Alexander Schwartz Date: Mon, 24 Jul 2023 11:28:24 +0200 Subject: [PATCH 010/135] Upgrade Infinispan to 14.0.13.Final (#21861) Closes #21564 (cherry picked from commit 7c9593f88ad3f1e7fff182917d4badd0b9f5420c) --- pom.xml | 12 +++++++++++- .../junit5/extension/DatabaseContainer.java | 15 ++++++++++++++- .../testsuite/util/InfinispanContainer.java | 19 ++++++++++++++++++- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 4f9ff33a34ee..b93e2d94c5b7 100644 --- a/pom.xml +++ b/pom.xml @@ -77,7 +77,7 @@ 2.2.220 6.2.5.Final 6.2.5.Final - 14.0.10.Final + 14.0.13.Final 4.6.2.Final @@ -823,6 +823,16 @@ infinispan-core-jakarta ${infinispan.version} + + org.infinispan + infinispan-commons + ${infinispan.version} + + + org.infinispan + infinispan-commons-jakarta + ${infinispan.version} + org.infinispan infinispan-cachestore-remote diff --git a/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/DatabaseContainer.java b/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/DatabaseContainer.java index 32e72c2a7df8..ed385393c15e 100644 --- a/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/DatabaseContainer.java +++ b/quarkus/tests/junit5/src/main/java/org/keycloak/it/junit5/extension/DatabaseContainer.java @@ -26,6 +26,7 @@ import org.testcontainers.containers.MariaDBContainer; import org.testcontainers.containers.MySQLContainer; import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.images.PullPolicy; import org.testcontainers.utility.DockerImageName; public class DatabaseContainer { @@ -107,6 +108,13 @@ private GenericContainer createContainer() { String MARIADB_IMAGE = System.getProperty("kc.db.mariadb.container.image"); String MYSQL_IMAGE = System.getProperty("kc.db.mysql.container.image"); String INFINISPAN_IMAGE = System.getProperty("kc.infinispan.container.image"); + if (INFINISPAN_IMAGE.matches("quay.io/infinispan/.*-SNAPSHOT")) { + // If the image name ends with SNAPSHOT, someone is trying to use a snapshot release of Infinispan. + // Then switch to the closest match of the Infinispan test container + INFINISPAN_IMAGE = INFINISPAN_IMAGE.replaceAll("quay.io/infinispan/", "quay.io/infinispan-test/"); + INFINISPAN_IMAGE = INFINISPAN_IMAGE.replaceAll("[0-9]*-SNAPSHOT$", "x"); + } + String MSSQL_IMAGE = System.getProperty("kc.db.mssql.container.image"); switch (alias) { @@ -123,8 +131,13 @@ private GenericContainer createContainer() { DockerImageName MSSQL = DockerImageName.parse(MSSQL_IMAGE).asCompatibleSubstituteFor("sqlserver"); return configureJdbcContainer(new MSSQLServerContainer<>(MSSQL)); case "infinispan": - return configureInfinispanUser(new GenericContainer<>(INFINISPAN_IMAGE)) + GenericContainer infinispanContainer = configureInfinispanUser(new GenericContainer<>(INFINISPAN_IMAGE)) .withExposedPorts(11222); + // the images in the 'infinispan-test' repository point to tags that are frequently refreshed, therefore, always pull them + if (infinispanContainer.getDockerImageName().startsWith("quay.io/infinispan-test")) { + infinispanContainer.withImagePullPolicy(PullPolicy.alwaysPull()); + } + return infinispanContainer; default: throw new RuntimeException("Unsupported database: " + alias); } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/InfinispanContainer.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/InfinispanContainer.java index 1fa33cd6c937..0b60b818a675 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/InfinispanContainer.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/InfinispanContainer.java @@ -21,6 +21,7 @@ import org.keycloak.testsuite.arquillian.HotRodContainerProvider; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.images.PullPolicy; import org.testcontainers.utility.MountableFile; import java.io.IOException; @@ -50,10 +51,15 @@ public class InfinispanContainer extends GenericContainer { private static final Pattern IP_ADDRESS_PATTERN = Pattern.compile("listening on (" + IP_ADDRESS_REGEX + "):" + PORT); public InfinispanContainer() { - super("quay.io/infinispan/server:" + System.getProperty("infinispan.version")); + super(getImageName()); withEnv("USER", USERNAME); withEnv("PASS", PASSWORD); withNetworkMode("host"); + + // the images in the 'infinispan-test' repository point to tags that are frequently refreshed, therefore, always pull them + if (getImageName().startsWith("quay.io/infinispan-test")) { + withImagePullPolicy(PullPolicy.alwaysPull()); + } Path dir = Path.of(Path.of("").toAbsolutePath() + "/target/lib"); String projectVersion = System.getProperty("project.version"); @@ -72,6 +78,17 @@ public InfinispanContainer() { withStartupTimeout(Duration.ofMinutes(5)); } + private static String getImageName() { + String version = System.getProperty("infinispan.version"); + if (version.endsWith("-SNAPSHOT")) { + // for snapshot versions, '14.0.13-SNAPSHOT' translates to '14.0.x' + version = version.replaceAll("[0-9]*-SNAPSHOT$", "x"); + return "quay.io/infinispan-test/server:" + version; + } else { + return "quay.io/infinispan/server:" + version; + } + } + @Override public String getHost() { if (HOST == null && this.isRunning()) { From 6076108ec04bd4f9709ea9be8e5d11caaacba932 Mon Sep 17 00:00:00 2001 From: Lukas Hanusovsky <61745358+lhanusov@users.noreply.github.com> Date: Wed, 19 Jul 2023 14:43:36 +0200 Subject: [PATCH 011/135] [20455] Arquillian reflection bug -> using different setter to avoid overloading. (#21806) --- .../arquillian/containers/KeycloakQuarkusConfiguration.java | 3 ++- .../tests/base/src/test/resources/arquillian.xml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusConfiguration.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusConfiguration.java index e04b3dec2067..461aea316877 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusConfiguration.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusConfiguration.java @@ -141,7 +141,8 @@ public void setProvidersPath(Path providersPath) { this.providersPath = providersPath; } - public void setProvidersPath(String providersPath) { + // https://github.com/keycloak/keycloak/issues/20455 Overloading fails time to time with a mismatch error, most probably an Arquillian class reflection bug. + public void setProvidersPathString(String providersPath) { this.providersPath = Paths.get(providersPath); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml index 2f744fa0d164..3b825eebdf25 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml @@ -722,7 +722,7 @@ org.keycloak.testsuite.arquillian.containers.KeycloakQuarkusServerDeployableContainer ${auth.server.port.offset} ${migration.import.file.name} - ${keycloak.migration.home} + ${keycloak.migration.home} -Xms512m -Xmx512m From e90047f9cd8f219689774e021c08e44fa4772558 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Wed, 19 Jul 2023 15:56:39 +0200 Subject: [PATCH 012/135] Increase timeout for legacy tests (#21811) Closes #21810 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 67280feb5a99..c36e3cddcc9f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -247,7 +247,7 @@ jobs: name: Legacy Store IT needs: build runs-on: ubuntu-latest - timeout-minutes: 60 + timeout-minutes: 75 strategy: matrix: db: [postgres, mysql, oracle, mssql, mariadb] From f95091e210b5a3f124a83d529e3fae03d4078016 Mon Sep 17 00:00:00 2001 From: Martin Kanis Date: Fri, 21 Jul 2023 13:03:44 +0200 Subject: [PATCH 013/135] Removing workaround for state transfer never completes Closes #21256 (cherry picked from commit 6907134f179a67ad0362455d0497deecc2aa99ab) --- .../model/parameters/Infinispan.java | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Infinispan.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Infinispan.java index 82bb18720e8d..54b32356896d 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Infinispan.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Infinispan.java @@ -95,23 +95,19 @@ public class Infinispan extends KeycloakModelParameters { @Override public void updateConfig(Config cf) { - Config.ProviderConfig provider = cf.spi("connectionsInfinispan").provider("default"); - provider = provider.config("embedded", "true") + cf.spi("connectionsInfinispan") + .provider("default") + .config("embedded", "true") .config("clustered", "true") .config("useKeycloakTimeService", "true") - .config("nodeName", "node-" + NODE_COUNTER.incrementAndGet()); - - String preloading = System.getProperty("keycloak.userSessions.infinispan.preloadOfflineSessionsFromDatabase"); - if (preloading != null && "true".equals(preloading)) { - provider.config("awaitInitialTransfer", "false"); - } - cf.spi(UserLoginFailureSpi.NAME) - .provider(InfinispanUserLoginFailureProviderFactory.PROVIDER_ID) - .config("stalledTimeoutInSeconds", "10") - .spi(UserSessionSpi.NAME) - .provider(InfinispanUserSessionProviderFactory.PROVIDER_ID) - .config("sessionPreloadStalledTimeoutInSeconds", "10") - ; + .config("nodeName", "node-" + NODE_COUNTER.incrementAndGet()) + .spi(UserLoginFailureSpi.NAME) + .provider(InfinispanUserLoginFailureProviderFactory.PROVIDER_ID) + .config("stalledTimeoutInSeconds", "10") + .spi(UserSessionSpi.NAME) + .provider(InfinispanUserSessionProviderFactory.PROVIDER_ID) + .config("sessionPreloadStalledTimeoutInSeconds", "10") + ; } public Infinispan() { From 13893cbb96d93ac99126781735a7c868e2d28604 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Muzik=C3=A1=C5=99?= Date: Wed, 26 Jul 2023 06:58:47 +0200 Subject: [PATCH 014/135] Update bcpkix and bcprov dependencies (#21543) (#21845) Closes #21360 (cherry picked from commit 776bcbcbd43e745306c84e28b8cfe4e43163f31a) --- adapters/oidc/adapter-core/pom.xml | 2 +- adapters/oidc/installed/pom.xml | 2 +- adapters/oidc/jakarta-servlet-filter/pom.xml | 2 +- adapters/oidc/jetty/jetty-core/pom.xml | 2 +- adapters/oidc/jetty/jetty9.4/pom.xml | 2 +- adapters/oidc/servlet-filter/pom.xml | 2 +- adapters/oidc/spring-security/pom.xml | 2 +- adapters/oidc/tomcat/tomcat-core/pom.xml | 2 +- adapters/oidc/tomcat/tomcat/pom.xml | 2 +- adapters/oidc/undertow/pom.xml | 2 +- adapters/oidc/wildfly-elytron/pom.xml | 2 +- adapters/saml/jakarta-servlet-filter/pom.xml | 2 +- adapters/saml/jetty/jetty-core/pom.xml | 2 +- adapters/saml/jetty/jetty9.4/pom.xml | 2 +- adapters/saml/servlet-filter/pom.xml | 2 +- adapters/saml/tomcat/tomcat-core/pom.xml | 2 +- adapters/saml/tomcat/tomcat/pom.xml | 2 +- adapters/spi/adapter-spi/pom.xml | 2 +- adapters/spi/jboss-adapter-core/pom.xml | 2 +- crypto/default/pom.xml | 4 ++-- dependencies/server-min/pom.xml | 4 ++-- .../feature-packs/adapter-feature-pack/pom.xml | 10 ++++++++++ .../client/admin/cli/util/ClassLoaderUtil.java | 2 +- integration/client-cli/client-cli-dist/assembly.xml | 2 +- integration/client-cli/client-cli-dist/pom.xml | 2 +- .../client/registration/cli/util/ClassLoaderUtil.java | 2 +- operator/pom.xml | 4 ++-- pom.xml | 11 ----------- .../mappers/ClassLoaderPropertyMappers.java | 2 +- .../junit5/src/main/java/org/keycloak/Keycloak.java | 4 ++-- .../adapter-spi/undertow-adapter-jakarta/pom.xml | 2 +- testsuite/integration-arquillian/tests/base/pom.xml | 4 ++-- testsuite/integration-arquillian/tests/pom.xml | 6 +++--- testsuite/utils/pom.xml | 4 ++-- 34 files changed, 50 insertions(+), 51 deletions(-) diff --git a/adapters/oidc/adapter-core/pom.xml b/adapters/oidc/adapter-core/pom.xml index fc339d314a62..2baddd35d190 100755 --- a/adapters/oidc/adapter-core/pom.xml +++ b/adapters/oidc/adapter-core/pom.xml @@ -55,7 +55,7 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on provided diff --git a/adapters/oidc/installed/pom.xml b/adapters/oidc/installed/pom.xml index 5f9476fa9191..985b81d485f2 100755 --- a/adapters/oidc/installed/pom.xml +++ b/adapters/oidc/installed/pom.xml @@ -45,7 +45,7 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on org.apache.httpcomponents diff --git a/adapters/oidc/jakarta-servlet-filter/pom.xml b/adapters/oidc/jakarta-servlet-filter/pom.xml index 01ec205e50fa..c2105f8e1e94 100755 --- a/adapters/oidc/jakarta-servlet-filter/pom.xml +++ b/adapters/oidc/jakarta-servlet-filter/pom.xml @@ -81,7 +81,7 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on com.fasterxml.jackson.core diff --git a/adapters/oidc/jetty/jetty-core/pom.xml b/adapters/oidc/jetty/jetty-core/pom.xml index 75f187e93700..f36d9780af3f 100755 --- a/adapters/oidc/jetty/jetty-core/pom.xml +++ b/adapters/oidc/jetty/jetty-core/pom.xml @@ -68,7 +68,7 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on com.fasterxml.jackson.core diff --git a/adapters/oidc/jetty/jetty9.4/pom.xml b/adapters/oidc/jetty/jetty9.4/pom.xml index ff4454ba86f3..3ddd0b2fa930 100644 --- a/adapters/oidc/jetty/jetty9.4/pom.xml +++ b/adapters/oidc/jetty/jetty9.4/pom.xml @@ -64,7 +64,7 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on com.fasterxml.jackson.core diff --git a/adapters/oidc/servlet-filter/pom.xml b/adapters/oidc/servlet-filter/pom.xml index 7cd29e60e8e9..1e26928aa9b0 100755 --- a/adapters/oidc/servlet-filter/pom.xml +++ b/adapters/oidc/servlet-filter/pom.xml @@ -73,7 +73,7 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on com.fasterxml.jackson.core diff --git a/adapters/oidc/spring-security/pom.xml b/adapters/oidc/spring-security/pom.xml index a72e321e86a6..c85b8180a240 100644 --- a/adapters/oidc/spring-security/pom.xml +++ b/adapters/oidc/spring-security/pom.xml @@ -89,7 +89,7 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on runtime diff --git a/adapters/oidc/tomcat/tomcat-core/pom.xml b/adapters/oidc/tomcat/tomcat-core/pom.xml index 2936f82aa7fd..b7859c0d7d20 100755 --- a/adapters/oidc/tomcat/tomcat-core/pom.xml +++ b/adapters/oidc/tomcat/tomcat-core/pom.xml @@ -57,7 +57,7 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on com.fasterxml.jackson.core diff --git a/adapters/oidc/tomcat/tomcat/pom.xml b/adapters/oidc/tomcat/tomcat/pom.xml index ef54189b2a95..2a5d96149038 100755 --- a/adapters/oidc/tomcat/tomcat/pom.xml +++ b/adapters/oidc/tomcat/tomcat/pom.xml @@ -80,7 +80,7 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on com.fasterxml.jackson.core diff --git a/adapters/oidc/undertow/pom.xml b/adapters/oidc/undertow/pom.xml index d91c8eadf8d0..6d179ae84d86 100755 --- a/adapters/oidc/undertow/pom.xml +++ b/adapters/oidc/undertow/pom.xml @@ -69,7 +69,7 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on com.fasterxml.jackson.core diff --git a/adapters/oidc/wildfly-elytron/pom.xml b/adapters/oidc/wildfly-elytron/pom.xml index f597b94cd1e7..8a51fc6b28b2 100755 --- a/adapters/oidc/wildfly-elytron/pom.xml +++ b/adapters/oidc/wildfly-elytron/pom.xml @@ -70,7 +70,7 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on com.fasterxml.jackson.core diff --git a/adapters/saml/jakarta-servlet-filter/pom.xml b/adapters/saml/jakarta-servlet-filter/pom.xml index ba65ff19eca1..f7ee0ee6bcea 100755 --- a/adapters/saml/jakarta-servlet-filter/pom.xml +++ b/adapters/saml/jakarta-servlet-filter/pom.xml @@ -59,7 +59,7 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on org.keycloak diff --git a/adapters/saml/jetty/jetty-core/pom.xml b/adapters/saml/jetty/jetty-core/pom.xml index de200d690bf3..7ad707523966 100755 --- a/adapters/saml/jetty/jetty-core/pom.xml +++ b/adapters/saml/jetty/jetty-core/pom.xml @@ -73,7 +73,7 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on org.eclipse.jetty diff --git a/adapters/saml/jetty/jetty9.4/pom.xml b/adapters/saml/jetty/jetty9.4/pom.xml index 57f610c81c27..91e5589f0b4c 100644 --- a/adapters/saml/jetty/jetty9.4/pom.xml +++ b/adapters/saml/jetty/jetty9.4/pom.xml @@ -53,7 +53,7 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on org.keycloak diff --git a/adapters/saml/servlet-filter/pom.xml b/adapters/saml/servlet-filter/pom.xml index 1c7a33071497..5fc027042952 100755 --- a/adapters/saml/servlet-filter/pom.xml +++ b/adapters/saml/servlet-filter/pom.xml @@ -49,7 +49,7 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on org.keycloak diff --git a/adapters/saml/tomcat/tomcat-core/pom.xml b/adapters/saml/tomcat/tomcat-core/pom.xml index ea6f4eb6c844..9844e59f090d 100755 --- a/adapters/saml/tomcat/tomcat-core/pom.xml +++ b/adapters/saml/tomcat/tomcat-core/pom.xml @@ -53,7 +53,7 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on org.keycloak diff --git a/adapters/saml/tomcat/tomcat/pom.xml b/adapters/saml/tomcat/tomcat/pom.xml index 1be9994efd03..0dd64f673b34 100755 --- a/adapters/saml/tomcat/tomcat/pom.xml +++ b/adapters/saml/tomcat/tomcat/pom.xml @@ -72,7 +72,7 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on junit diff --git a/adapters/spi/adapter-spi/pom.xml b/adapters/spi/adapter-spi/pom.xml index d214608201f1..aab344d284be 100755 --- a/adapters/spi/adapter-spi/pom.xml +++ b/adapters/spi/adapter-spi/pom.xml @@ -43,7 +43,7 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on provided diff --git a/adapters/spi/jboss-adapter-core/pom.xml b/adapters/spi/jboss-adapter-core/pom.xml index 63fcbce25dd1..a184ae5e0d8a 100755 --- a/adapters/spi/jboss-adapter-core/pom.xml +++ b/adapters/spi/jboss-adapter-core/pom.xml @@ -50,7 +50,7 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on com.fasterxml.jackson.core diff --git a/crypto/default/pom.xml b/crypto/default/pom.xml index 3a9745d982e0..2b0cf1fb234f 100644 --- a/crypto/default/pom.xml +++ b/crypto/default/pom.xml @@ -56,11 +56,11 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on org.bouncycastle - bcpkix-jdk15on + bcpkix-jdk18on org.jboss.logging diff --git a/dependencies/server-min/pom.xml b/dependencies/server-min/pom.xml index 9c2ac21c5583..8749833cd6fd 100755 --- a/dependencies/server-min/pom.xml +++ b/dependencies/server-min/pom.xml @@ -41,11 +41,11 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on org.bouncycastle - bcpkix-jdk15on + bcpkix-jdk18on org.keycloak diff --git a/distribution/feature-packs/adapter-feature-pack/pom.xml b/distribution/feature-packs/adapter-feature-pack/pom.xml index c76f58d7f282..cd9b1baf68a9 100755 --- a/distribution/feature-packs/adapter-feature-pack/pom.xml +++ b/distribution/feature-packs/adapter-feature-pack/pom.xml @@ -170,6 +170,16 @@ wildfly-feature-pack ${wildfly.version} zip + + + org.bouncycastle + bcprov-jdk15on + + + org.bouncycastle + bcpkix-jdk15on + + diff --git a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/ClassLoaderUtil.java b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/ClassLoaderUtil.java index 44d66072f3ab..990cf8182e04 100644 --- a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/ClassLoaderUtil.java +++ b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/ClassLoaderUtil.java @@ -38,7 +38,7 @@ public static ClassLoader resolveClassLoader(String libDir) { // Detect if BC FIPS jars are present in the "client/lib" directory boolean bcFipsJarPresent = Stream.of(jarsInDir).anyMatch(file -> file.getName().startsWith("bc-fips")); - String[] validJarPrefixes = bcFipsJarPresent ? new String[] {"keycloak-crypto-fips1402", "bc-fips", "bctls-fips"} : new String[] {"keycloak-crypto-default", "bcprov-jdk15on"}; + String[] validJarPrefixes = bcFipsJarPresent ? new String[] {"keycloak-crypto-fips1402", "bc-fips", "bctls-fips"} : new String[] {"keycloak-crypto-default", "bcprov-jdk18on"}; URL[] usedJars = Stream.of(jarsInDir) .filter(file -> { for (String prefix : validJarPrefixes) { diff --git a/integration/client-cli/client-cli-dist/assembly.xml b/integration/client-cli/client-cli-dist/assembly.xml index 42cb0f2d7f8c..cb56c0f331d5 100755 --- a/integration/client-cli/client-cli-dist/assembly.xml +++ b/integration/client-cli/client-cli-dist/assembly.xml @@ -60,7 +60,7 @@ org.keycloak:keycloak-crypto-default org.keycloak:keycloak-crypto-fips1402 - org.bouncycastle:bcprov-jdk15on + org.bouncycastle:bcprov-jdk18on keycloak-client-tools/bin/client/lib diff --git a/integration/client-cli/client-cli-dist/pom.xml b/integration/client-cli/client-cli-dist/pom.xml index 18245b577287..5712241f7dd8 100755 --- a/integration/client-cli/client-cli-dist/pom.xml +++ b/integration/client-cli/client-cli-dist/pom.xml @@ -60,7 +60,7 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on * diff --git a/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/util/ClassLoaderUtil.java b/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/util/ClassLoaderUtil.java index ffaeab887727..72e2b2f5f2de 100644 --- a/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/util/ClassLoaderUtil.java +++ b/integration/client-cli/client-registration-cli/src/main/java/org/keycloak/client/registration/cli/util/ClassLoaderUtil.java @@ -38,7 +38,7 @@ public static ClassLoader resolveClassLoader(String libDir) { // Detect if BC FIPS jars are present in the "client/lib" directory boolean bcFipsJarPresent = Stream.of(jarsInDir).anyMatch(file -> file.getName().startsWith("bc-fips")); - String[] validJarPrefixes = bcFipsJarPresent ? new String[] {"keycloak-crypto-fips1402", "bc-fips", "bctls-fips"} : new String[] {"keycloak-crypto-default", "bcprov-jdk15on"}; + String[] validJarPrefixes = bcFipsJarPresent ? new String[] {"keycloak-crypto-fips1402", "bc-fips", "bctls-fips"} : new String[] {"keycloak-crypto-default", "bcprov-jdk18on"}; URL[] usedJars = Stream.of(jarsInDir) .filter(file -> { for (String prefix : validJarPrefixes) { diff --git a/operator/pom.xml b/operator/pom.xml index 8d5c2367f996..b28548f5c605 100644 --- a/operator/pom.xml +++ b/operator/pom.xml @@ -101,11 +101,11 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on org.bouncycastle - bcpkix-jdk15on + bcpkix-jdk18on diff --git a/pom.xml b/pom.xml index b93e2d94c5b7..c432697d2f24 100644 --- a/pom.xml +++ b/pom.xml @@ -64,7 +64,6 @@ 0.66.19 4.5.14 1.5.1.Final - 1.70 1.0.7 @@ -353,16 +352,6 @@ xsom ${org.glassfish.jaxb.xsom.version} - - org.bouncycastle - bcprov-jdk15on - ${bouncycastle-bcprov-jdk15on.version} - - - org.bouncycastle - bcpkix-jdk15on - ${bouncycastle-bcprov-jdk15on.version} - org.bouncycastle bcpkix-fips diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ClassLoaderPropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ClassLoaderPropertyMappers.java index a46df7289cf8..2b622fe1866d 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ClassLoaderPropertyMappers.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ClassLoaderPropertyMappers.java @@ -38,7 +38,7 @@ private static Optional resolveIgnoredArtifacts(Optional value, if (profile.getFeatures().get(Feature.FIPS)) { ignoredArtifacts.addAll(List.of( - "org.bouncycastle:bcprov-jdk15on", "org.bouncycastle:bcpkix-jdk15on", "org.bouncycastle:bcutil-jdk15on", "org.keycloak:keycloak-crypto-default")); + "org.bouncycastle:bcprov-jdk18on", "org.bouncycastle:bcpkix-jdk18on", "org.bouncycastle:bcutil-jdk18on", "org.keycloak:keycloak-crypto-default")); } else { ignoredArtifacts.addAll(List.of( "org.keycloak:keycloak-crypto-fips1402", "org.bouncycastle:bc-fips", "org.bouncycastle:bctls-fips", "org.bouncycastle:bcpkix-fips")); diff --git a/quarkus/tests/junit5/src/main/java/org/keycloak/Keycloak.java b/quarkus/tests/junit5/src/main/java/org/keycloak/Keycloak.java index 8e9d492baba7..ebf945e45926 100644 --- a/quarkus/tests/junit5/src/main/java/org/keycloak/Keycloak.java +++ b/quarkus/tests/junit5/src/main/java/org/keycloak/Keycloak.java @@ -259,8 +259,8 @@ private WorkspaceModule createWorkspaceModule(String keycloakVersion) { .addExclusion("org.jboss.logmanager", "log4j-jboss-logmanager"); if (fipsEnabled) { - serverDependency.addExclusion("org.bouncycastle", "bcprov-jdk15on"); - serverDependency.addExclusion("org.bouncycastle", "bcpkix-jdk15on"); + serverDependency.addExclusion("org.bouncycastle", "bcprov-jdk18on"); + serverDependency.addExclusion("org.bouncycastle", "bcpkix-jdk18on"); serverDependency.addExclusion("org.keycloak", "keycloak-crypto-default"); } else { serverDependency.addExclusion("org.keycloak", "keycloak-crypto-fips1402"); diff --git a/testsuite/integration-arquillian/servers/adapter-spi/undertow-adapter-jakarta/pom.xml b/testsuite/integration-arquillian/servers/adapter-spi/undertow-adapter-jakarta/pom.xml index 9904dc2dda1f..7539a2caf5a0 100644 --- a/testsuite/integration-arquillian/servers/adapter-spi/undertow-adapter-jakarta/pom.xml +++ b/testsuite/integration-arquillian/servers/adapter-spi/undertow-adapter-jakarta/pom.xml @@ -57,7 +57,7 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on com.fasterxml.jackson.core diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml index 4efc780e8fee..a85e30f5af2e 100644 --- a/testsuite/integration-arquillian/tests/base/pom.xml +++ b/testsuite/integration-arquillian/tests/base/pom.xml @@ -56,8 +56,8 @@ keycloak-util-embedded-ldap - bouncycastle - bcprov-jdk15 + org.bouncycastle + bcprov-jdk15on diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml index 3896d64ee0a5..7f2d9ac54607 100644 --- a/testsuite/integration-arquillian/tests/pom.xml +++ b/testsuite/integration-arquillian/tests/pom.xml @@ -125,7 +125,7 @@ n -agentlib:jdwp=transport=dt_socket,server=y,suspend=${app.server.2.debug.suspend},address=localhost:${app.server.2.debug.port} 64m - 512m + 768m -Xms${app.server.memory.Xms} -Xmx${app.server.memory.Xmx} -XX:MetaspaceSize=${surefire.memory.metaspace} -XX:MaxMetaspaceSize=${surefire.memory.metaspace.max} false ${app.server.keystore.dir}/keycloak.truststore @@ -1810,11 +1810,11 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on org.bouncycastle - bcpkix-jdk15on + bcpkix-jdk18on org.hamcrest diff --git a/testsuite/utils/pom.xml b/testsuite/utils/pom.xml index 8118efa619e9..b487a2aa8682 100755 --- a/testsuite/utils/pom.xml +++ b/testsuite/utils/pom.xml @@ -45,11 +45,11 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on org.bouncycastle - bcpkix-jdk15on + bcpkix-jdk18on org.keycloak From 6b83b3880f147ee7f1bf67762bd11e9d2450e54d Mon Sep 17 00:00:00 2001 From: mposolda Date: Mon, 17 Jul 2023 16:56:06 +0200 Subject: [PATCH 015/135] Keycloak forgets ui_locales parameter when using reset password closes #10981 (cherry picked from commit 03716ed452508ffa095f98c6d101372eb134b253) --- .../locale/DefaultLocaleSelectorProvider.java | 2 +- .../oidc/endpoints/AuthorizationEndpoint.java | 2 +- .../oidc/endpoints/LogoutEndpoint.java | 2 +- .../resources/IdentityBrokerService.java | 18 ++++----------- .../keycloak/testsuite/i18n/EmailTest.java | 22 +++++++++++++++++++ 5 files changed, 29 insertions(+), 17 deletions(-) diff --git a/services/src/main/java/org/keycloak/locale/DefaultLocaleSelectorProvider.java b/services/src/main/java/org/keycloak/locale/DefaultLocaleSelectorProvider.java index 6b377b27dcbc..2faf7b80b751 100644 --- a/services/src/main/java/org/keycloak/locale/DefaultLocaleSelectorProvider.java +++ b/services/src/main/java/org/keycloak/locale/DefaultLocaleSelectorProvider.java @@ -118,7 +118,7 @@ private Locale getClientSelectedLocale(RealmModel realm, AuthenticationSessionMo return null; } - String locale = session.getAuthNote(LocaleSelectorProvider.CLIENT_REQUEST_LOCALE); + String locale = session.getClientNote(LocaleSelectorProvider.CLIENT_REQUEST_LOCALE); if (locale == null) { return null; } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java index 033d0bbd793b..f6c38c934bf4 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java @@ -313,7 +313,7 @@ private void updateAuthenticationSession() { performActionOnParameters(request, (paramName, paramValue) -> {if (paramValue != null) authenticationSession.setClientNote(paramName, paramValue);}); if (request.getMaxAge() != null) authenticationSession.setClientNote(OIDCLoginProtocol.MAX_AGE_PARAM, String.valueOf(request.getMaxAge())); - if (request.getUiLocales() != null) authenticationSession.setAuthNote(LocaleSelectorProvider.CLIENT_REQUEST_LOCALE, request.getUiLocales()); + if (request.getUiLocales() != null) authenticationSession.setClientNote(LocaleSelectorProvider.CLIENT_REQUEST_LOCALE, request.getUiLocales()); Map acrLoaMap = AcrUtils.getAcrLoaMap(authenticationSession.getClient()); List acrValues = AcrUtils.getRequiredAcrValues(request.getClaims()); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java index 745e79c23a45..766f81790f24 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java @@ -256,7 +256,7 @@ public Response logout(@QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String AuthenticationSessionModel logoutSession = AuthenticationManager.createOrJoinLogoutSession(session, realm, new AuthenticationSessionManager(session), null, true); session.getContext().setAuthenticationSession(logoutSession); if (uiLocales != null) { - logoutSession.setAuthNote(LocaleSelectorProvider.CLIENT_REQUEST_LOCALE, uiLocales); + logoutSession.setClientNote(LocaleSelectorProvider.CLIENT_REQUEST_LOCALE, uiLocales); } if (validatedRedirectUri != null) { logoutSession.setAuthNote(OIDCLoginProtocol.LOGOUT_REDIRECT_URI, validatedRedirectUri); diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java index 24bcdad71cad..6766a33e5f66 100755 --- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java +++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java @@ -580,10 +580,12 @@ public Response authenticated(BrokeredIdentityContext context) { boolean forwardedPassiveLogin = "true".equals(authenticationSession.getAuthNote(AuthenticationProcessor.FORWARDED_PASSIVE_LOGIN)); - Map extractedAuthNotes = extractAuthNotesFromSession(authenticationSession); + String userRequestedLocale = authenticationSession.getAuthNote(LocaleSelectorProvider.USER_REQUEST_LOCALE); // Redirect to firstBrokerLogin after successful login and ensure that previous authentication state removed AuthenticationProcessor.resetFlow(authenticationSession, LoginActionsService.FIRST_BROKER_LOGIN_PATH); - extractedAuthNotes.forEach(authenticationSession::setAuthNote); + if (userRequestedLocale != null) { + authenticationSession.setAuthNote(LocaleSelectorProvider.USER_REQUEST_LOCALE, userRequestedLocale); + } // Set the FORWARDED_PASSIVE_LOGIN note (if needed) after resetting the session so it is not lost. if (forwardedPassiveLogin) { @@ -616,18 +618,6 @@ public Response authenticated(BrokeredIdentityContext context) { } } - private Map extractAuthNotesFromSession(AuthenticationSessionModel authenticationSession) { - return Stream.of( - LocaleSelectorProvider.USER_REQUEST_LOCALE, - LocaleSelectorProvider.CLIENT_REQUEST_LOCALE - ) - .filter(it -> authenticationSession.getAuthNote(it) != null) - .collect(Collectors.toMap( - Function.identity(), - authenticationSession::getAuthNote - )); - } - public Response validateUser(AuthenticationSessionModel authSession, UserModel user, RealmModel realm) { if (!user.isEnabled()) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/EmailTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/EmailTest.java index e5170892cbeb..93c2f3f3f77e 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/EmailTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/EmailTest.java @@ -45,6 +45,7 @@ import org.keycloak.testsuite.util.GreenMailRule; import org.keycloak.testsuite.util.MailUtils; import org.keycloak.testsuite.util.WaitUtils; +import org.openqa.selenium.By; /** * @author Michael Gerber @@ -190,4 +191,25 @@ public void changeLocaleOnInfoPage() throws InterruptedException, IOException { assertThat(infoPage.getInfo(), containsString("Your account has been updated.")); } + + // Issue 10981 + @Test + public void resetPasswordOriginalUiLocalePreservedAfterForgetPassword() throws MessagingException, IOException { + oauth.uiLocales("de"); + + // Assert login page is in german + loginPage.open(); + assertEquals("Deutsch", loginPage.getLanguageDropdownText()); + + // Click "Forget password" + driver.findElement(By.linkText("Passwort vergessen?")).click(); + assertEquals("Deutsch", resetPasswordPage.getLanguageDropdownText()); + resetPasswordPage.changePassword("login-test"); + + // Ensure that page is still in german (after authenticationSession was forked on server). The emailSentMessage should be also displayed in german + loginPage.assertCurrent(); + assertEquals("Deutsch", loginPage.getLanguageDropdownText()); + assertEquals("Sie sollten in Kürze eine E-Mail mit weiteren Instruktionen erhalten.", loginPage.getSuccessMessage()); + } + } From 0349caf6ed8c37a71dba5a1c13179b480e87c5e8 Mon Sep 17 00:00:00 2001 From: Peter Zaoral Date: Wed, 26 Jul 2023 16:48:27 +0200 Subject: [PATCH 016/135] Downgrade Jansi library to prevent kcadm exception on Windows (#21986) Closes #21851 Signed-off-by: Peter Zaoral --- integration/client-cli/admin-cli/pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/integration/client-cli/admin-cli/pom.xml b/integration/client-cli/admin-cli/pom.xml index 3d7f0d654f9e..c0d8ef6358f2 100755 --- a/integration/client-cli/admin-cli/pom.xml +++ b/integration/client-cli/admin-cli/pom.xml @@ -29,11 +29,21 @@ Keycloak Admin CLI + + 1.18 + + org.jboss.aesh aesh + + + org.fusesource.jansi + jansi + ${jansi.version} + org.keycloak keycloak-core From 616110902e0c1e0533af9c979e240c87aedd0d27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Muzik=C3=A1=C5=99?= Date: Wed, 26 Jul 2023 16:20:23 +0200 Subject: [PATCH 017/135] Upgrade to Quarkus 3.2.2.Final (#21912) Closes #21907 (cherry picked from commit ecdf8e897fab7520d91cabd6904754de0d2d3a9a) --- pom.xml | 15 ++++++++---- .../java/org/keycloak/config/HttpOptions.java | 2 +- .../ConfigKeystorePropertyMappers.java | 24 +++++++++++++++---- .../cli/dist/QuarkusPropertiesDistTest.java | 2 +- ...istTest.testStartDevHelp.unix.approved.txt | 2 +- ...Test.testStartDevHelp.windows.approved.txt | 2 +- ...Test.testStartDevHelpAll.unix.approved.txt | 2 +- ...t.testStartDevHelpAll.windows.approved.txt | 2 +- ...ndDistTest.testStartHelp.unix.approved.txt | 2 +- ...istTest.testStartHelp.windows.approved.txt | 2 +- ...istTest.testStartHelpAll.unix.approved.txt | 2 +- ...Test.testStartHelpAll.windows.approved.txt | 2 +- ...t.testStartOptimizedHelp.unix.approved.txt | 2 +- ...estStartOptimizedHelp.windows.approved.txt | 2 +- ...estStartOptimizedHelpAll.unix.approved.txt | 2 +- ...StartOptimizedHelpAll.windows.approved.txt | 2 +- 16 files changed, 44 insertions(+), 23 deletions(-) diff --git a/pom.xml b/pom.xml index c432697d2f24..48a25db2b8a1 100644 --- a/pom.xml +++ b/pom.xml @@ -43,8 +43,8 @@ jboss-snapshots-repository https://s01.oss.sonatype.org/content/repositories/snapshots/ - 3.2.0.Final - 3.2.0.Final + 3.2.2.Final + 3.2.2.Final ${timestamp} @@ -74,8 +74,10 @@ 3.3.10 2.1.3 2.2.220 - 6.2.5.Final - 6.2.5.Final + + 6.2.7.Final + 6.2.7.Final + 6.2.7.Final 14.0.13.Final 4.6.2.Final @@ -504,6 +506,11 @@ ${h2.version} test + + org.hibernate.orm + hibernate-core + ${hibernate-orm.version} + org.hibernate.orm hibernate-c3p0 diff --git a/quarkus/config-api/src/main/java/org/keycloak/config/HttpOptions.java b/quarkus/config-api/src/main/java/org/keycloak/config/HttpOptions.java index bc9b6985f844..fa64ebe0caf2 100644 --- a/quarkus/config-api/src/main/java/org/keycloak/config/HttpOptions.java +++ b/quarkus/config-api/src/main/java/org/keycloak/config/HttpOptions.java @@ -56,7 +56,7 @@ public enum ClientAuth { public static final Option HTTPS_PROTOCOLS = new OptionBuilder<>("https-protocols", String.class) .category(OptionCategory.HTTP) .description("The list of protocols to explicitly enable.") - .defaultValue("TLSv1.3") + .defaultValue("TLSv1.3,TLSv1.2") .build(); public static final Option HTTPS_CERTIFICATE_FILE = new OptionBuilder<>("https-certificate-file", File.class) diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ConfigKeystorePropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ConfigKeystorePropertyMappers.java index 34afac7fa53b..ec20345cb61a 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ConfigKeystorePropertyMappers.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ConfigKeystorePropertyMappers.java @@ -12,6 +12,9 @@ import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption; final class ConfigKeystorePropertyMappers { + private static final String SMALLRYE_KEYSTORE_PATH = "smallrye.config.source.keystore.kc-default.path"; + private static final String SMALLRYE_KEYSTORE_PASSWORD = "smallrye.config.source.keystore.kc-default.password"; + private ConfigKeystorePropertyMappers() { } @@ -19,12 +22,12 @@ private ConfigKeystorePropertyMappers() { public static PropertyMapper[] getConfigKeystorePropertyMappers() { return new PropertyMapper[] { fromOption(ConfigKeystoreOptions.CONFIG_KEYSTORE) - .to("smallrye.config.source.keystore.kc-default.path") + .to(SMALLRYE_KEYSTORE_PATH) .transformer(ConfigKeystorePropertyMappers::validatePath) .paramLabel("config-keystore") .build(), fromOption(ConfigKeystoreOptions.CONFIG_KEYSTORE_PASSWORD) - .to("smallrye.config.source.keystore.kc-default.password") + .to(SMALLRYE_KEYSTORE_PASSWORD) .transformer(ConfigKeystorePropertyMappers::validatePassword) .paramLabel("config-keystore-password") .build(), @@ -36,12 +39,17 @@ public static PropertyMapper[] getConfigKeystorePropertyMappers() { } private static Optional validatePath(Optional option, ConfigSourceInterceptorContext context) { - ConfigValue path = context.proceed("smallrye.config.source.keystore.kc-default.path"); + ConfigValue path = context.proceed(SMALLRYE_KEYSTORE_PATH); + boolean isPasswordDefined = context.proceed(SMALLRYE_KEYSTORE_PASSWORD) != null; if (path == null) { throw new IllegalArgumentException("config-keystore must be specified"); } + if (!isPasswordDefined) { + throw new IllegalArgumentException("config-keystore-password must be specified"); + } + Optional realPath = Optional.of(String.valueOf(Paths.get(path.getValue()).toAbsolutePath().normalize())); if (!Files.exists(Path.of(realPath.get()))) { throw new IllegalArgumentException("config-keystore path does not exist: " + realPath.get()); @@ -50,11 +58,17 @@ private static Optional validatePath(Optional option, ConfigSour } private static Optional validatePassword(Optional option, ConfigSourceInterceptorContext context) { - ConfigValue password = context.proceed("smallrye.config.source.keystore.kc-default.password"); + boolean isPasswordDefined = context.proceed(SMALLRYE_KEYSTORE_PASSWORD).getValue() != null; + boolean isPathDefined = context.proceed(SMALLRYE_KEYSTORE_PATH) != null; - if (password == null) { + if (!isPasswordDefined) { throw new IllegalArgumentException("config-keystore-password must be specified"); } + + if (!isPathDefined) { + throw new IllegalArgumentException("config-keystore must be specified"); + } + return option; } diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesDistTest.java index 3270d942e4a9..af2a5c2acea4 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesDistTest.java @@ -123,7 +123,7 @@ void testUnknownQuarkusBuildTimePropertyApplied(LaunchResult result) { } @Test - @Launch({ "start", "--http-enabled=true", "--hostname-strict=false", "--config-keystore=keystore" }) + @Launch({ "start", "--http-enabled=true", "--hostname-strict=false", "--config-keystore=../../../../src/test/resources/keystore" }) @Order(10) void testMissingSmallRyeKeyStorePasswordProperty(LaunchResult result) { CLIResult cliResult = (CLIResult) result; diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.unix.approved.txt index c3b2dd94bebe..f6da574be1e8 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.unix.approved.txt @@ -142,7 +142,7 @@ HTTP/TLS: value is set, it defaults to 'BCFKS'. --https-port The used HTTPS port. Default: 8443. --https-protocols - The list of protocols to explicitly enable. Default: TLSv1.3. + The list of protocols to explicitly enable. Default: TLSv1.3,TLSv1.2. --https-trust-store-file The trust store which holds the certificate information of the certificates to trust. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.windows.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.windows.approved.txt index f046f28d61f3..d4c96f60af58 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.windows.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.windows.approved.txt @@ -137,7 +137,7 @@ HTTP/TLS: value is set, it defaults to 'BCFKS'. --https-port The used HTTPS port. Default: 8443. --https-protocols - The list of protocols to explicitly enable. Default: TLSv1.3. + The list of protocols to explicitly enable. Default: TLSv1.3,TLSv1.2. --https-trust-store-file The trust store which holds the certificate information of the certificates to trust. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.unix.approved.txt index 98793b7a31f9..e0508fd24f81 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.unix.approved.txt @@ -205,7 +205,7 @@ HTTP/TLS: value is set, it defaults to 'BCFKS'. --https-port The used HTTPS port. Default: 8443. --https-protocols - The list of protocols to explicitly enable. Default: TLSv1.3. + The list of protocols to explicitly enable. Default: TLSv1.3,TLSv1.2. --https-trust-store-file The trust store which holds the certificate information of the certificates to trust. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.windows.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.windows.approved.txt index 802c7287b1a6..fee63fcd04cb 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.windows.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.windows.approved.txt @@ -200,7 +200,7 @@ HTTP/TLS: value is set, it defaults to 'BCFKS'. --https-port The used HTTPS port. Default: 8443. --https-protocols - The list of protocols to explicitly enable. Default: TLSv1.3. + The list of protocols to explicitly enable. Default: TLSv1.3,TLSv1.2. --https-trust-store-file The trust store which holds the certificate information of the certificates to trust. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.unix.approved.txt index 57b5c0d7f338..e9ffa84e71bd 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.unix.approved.txt @@ -148,7 +148,7 @@ HTTP/TLS: value is set, it defaults to 'BCFKS'. --https-port The used HTTPS port. Default: 8443. --https-protocols - The list of protocols to explicitly enable. Default: TLSv1.3. + The list of protocols to explicitly enable. Default: TLSv1.3,TLSv1.2. --https-trust-store-file The trust store which holds the certificate information of the certificates to trust. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.windows.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.windows.approved.txt index c7ce90f0dee5..54171439a870 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.windows.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.windows.approved.txt @@ -143,7 +143,7 @@ HTTP/TLS: value is set, it defaults to 'BCFKS'. --https-port The used HTTPS port. Default: 8443. --https-protocols - The list of protocols to explicitly enable. Default: TLSv1.3. + The list of protocols to explicitly enable. Default: TLSv1.3,TLSv1.2. --https-trust-store-file The trust store which holds the certificate information of the certificates to trust. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.unix.approved.txt index 188e4359f280..e9a122f142d4 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.unix.approved.txt @@ -211,7 +211,7 @@ HTTP/TLS: value is set, it defaults to 'BCFKS'. --https-port The used HTTPS port. Default: 8443. --https-protocols - The list of protocols to explicitly enable. Default: TLSv1.3. + The list of protocols to explicitly enable. Default: TLSv1.3,TLSv1.2. --https-trust-store-file The trust store which holds the certificate information of the certificates to trust. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.windows.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.windows.approved.txt index 9f15f3d7c032..0876ee7c5aa7 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.windows.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.windows.approved.txt @@ -206,7 +206,7 @@ HTTP/TLS: value is set, it defaults to 'BCFKS'. --https-port The used HTTPS port. Default: 8443. --https-protocols - The list of protocols to explicitly enable. Default: TLSv1.3. + The list of protocols to explicitly enable. Default: TLSv1.3,TLSv1.2. --https-trust-store-file The trust store which holds the certificate information of the certificates to trust. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.unix.approved.txt index bd426c77ed04..0b942f89745c 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.unix.approved.txt @@ -107,7 +107,7 @@ HTTP/TLS: value is set, it defaults to 'BCFKS'. --https-port The used HTTPS port. Default: 8443. --https-protocols - The list of protocols to explicitly enable. Default: TLSv1.3. + The list of protocols to explicitly enable. Default: TLSv1.3,TLSv1.2. --https-trust-store-file The trust store which holds the certificate information of the certificates to trust. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.windows.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.windows.approved.txt index 21151a1e0c6d..5a35664fb326 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.windows.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelp.windows.approved.txt @@ -100,7 +100,7 @@ HTTP/TLS: value is set, it defaults to 'BCFKS'. --https-port The used HTTPS port. Default: 8443. --https-protocols - The list of protocols to explicitly enable. Default: TLSv1.3. + The list of protocols to explicitly enable. Default: TLSv1.3,TLSv1.2. --https-trust-store-file The trust store which holds the certificate information of the certificates to trust. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.unix.approved.txt index ee089c3ee3fa..50ec70c6ebb6 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.unix.approved.txt @@ -126,7 +126,7 @@ HTTP/TLS: value is set, it defaults to 'BCFKS'. --https-port The used HTTPS port. Default: 8443. --https-protocols - The list of protocols to explicitly enable. Default: TLSv1.3. + The list of protocols to explicitly enable. Default: TLSv1.3,TLSv1.2. --https-trust-store-file The trust store which holds the certificate information of the certificates to trust. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.windows.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.windows.approved.txt index bc8d11f0b414..b5d806461f3c 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.windows.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartOptimizedHelpAll.windows.approved.txt @@ -119,7 +119,7 @@ HTTP/TLS: value is set, it defaults to 'BCFKS'. --https-port The used HTTPS port. Default: 8443. --https-protocols - The list of protocols to explicitly enable. Default: TLSv1.3. + The list of protocols to explicitly enable. Default: TLSv1.3,TLSv1.2. --https-trust-store-file The trust store which holds the certificate information of the certificates to trust. From 3bdf16bca597e0fcf8a927832b5027f0534a837a Mon Sep 17 00:00:00 2001 From: Marek Posolda Date: Thu, 27 Jul 2023 06:31:52 +0200 Subject: [PATCH 018/135] Fix script tests on windows (#21942) (#21988) Closes #21778 #21779 #21780 (cherry picked from commit bb8ba1af5a4af4770d315a1e9fe1a6ee299dbf6f) --- .../AbstractQuarkusDeployableContainer.java | 4 +++ ...cloakQuarkusServerDeployableContainer.java | 6 +++++ .../script/DeployedSAMLScriptMapperTest.java | 27 ++++--------------- .../DeployedScriptAuthenticatorTest.java | 17 ++---------- .../script/DeployedScriptMapperTest.java | 26 ++++-------------- .../script/DeployedScriptPolicyTest.java | 18 ++----------- 6 files changed, 24 insertions(+), 74 deletions(-) diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/AbstractQuarkusDeployableContainer.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/AbstractQuarkusDeployableContainer.java index 4d01fc3265fa..093eff1c748f 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/AbstractQuarkusDeployableContainer.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/AbstractQuarkusDeployableContainer.java @@ -85,8 +85,10 @@ public void setup(KeycloakQuarkusConfiguration configuration) { @Override public ProtocolMetaData deploy(Archive archive) throws DeploymentException { try { + log.infof("Deploying archive %s to quarkus container", archive.getName()); deployArchiveToServer(archive); restartServer(); + log.infof("Deployed archive %s and restarted quarkus container", archive.getName()); } catch (Exception e) { throw new DeploymentException(e.getMessage(), e); } @@ -96,6 +98,7 @@ public ProtocolMetaData deploy(Archive archive) throws DeploymentException { @Override public void undeploy(Archive archive) throws DeploymentException { + log.infof("Undeploying archive %s from quarkus container", archive.getName()); File wrkDir = configuration.getProvidersPath().resolve("providers").toFile(); try { if (isWindows()) { @@ -104,6 +107,7 @@ public void undeploy(Archive archive) throws DeploymentException { } Files.deleteIfExists(wrkDir.toPath().resolve(archive.getName())); restartServer(); + log.infof("Undeployed archive %s and restarted quarkus container", archive.getName()); } catch (Exception e) { throw new DeploymentException(e.getMessage(), e); } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusServerDeployableContainer.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusServerDeployableContainer.java index c5249ff599a1..c74c5728622b 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusServerDeployableContainer.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusServerDeployableContainer.java @@ -27,6 +27,7 @@ import org.jboss.arquillian.container.spi.client.container.LifecycleException; import org.jboss.logging.Logger; import org.keycloak.testsuite.model.StoreProvider; +import org.keycloak.testsuite.util.WaitUtils; /** * @author mhajas @@ -194,6 +195,11 @@ private void destroyDescendantsOnWindows(Process parent, boolean force) { return; } + // Wait some time before killing the windows processes. Otherwise there is a risk that some already commited H2 transactions + // won't be written to disk in time and hence those transactions may be lost, which could result in test failures in the next step after server restart. + // See http://repository.transtep.com/repository/thirdparty/H2/1.0.63/docs/html/advanced.html#durability_problems for the details + WaitUtils.pause(2000); + CompletableFuture allProcesses = CompletableFuture.completedFuture(null); for (ProcessHandle process : parent.descendants().collect(Collectors.toList())) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/DeployedSAMLScriptMapperTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/DeployedSAMLScriptMapperTest.java index 2cff2dba415b..c1cdc46a0c7c 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/DeployedSAMLScriptMapperTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/DeployedSAMLScriptMapperTest.java @@ -6,19 +6,15 @@ import jakarta.ws.rs.core.Response; -import org.jboss.arquillian.container.test.api.Deployer; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.container.test.api.TargetsContainer; -import org.jboss.arquillian.test.api.ArquillianResource; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.JavaArchive; -import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; -import org.keycloak.common.Profile; import org.keycloak.dom.saml.v2.assertion.AssertionType; import org.keycloak.dom.saml.v2.assertion.AttributeType; import org.keycloak.protocol.saml.SamlProtocol; @@ -29,8 +25,8 @@ import org.keycloak.representations.provider.ScriptProviderDescriptor; import org.keycloak.saml.common.constants.JBossSAMLURIConstants; import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder; +import org.keycloak.testsuite.arquillian.annotation.DisableFeature; import org.keycloak.testsuite.arquillian.annotation.EnableFeature; -import org.keycloak.testsuite.arquillian.annotation.EnableFeatures; import org.keycloak.testsuite.saml.AbstractSamlTest; import org.keycloak.testsuite.saml.RoleMapperTest; import org.keycloak.testsuite.updaters.ClientAttributeUpdater; @@ -53,6 +49,7 @@ /** * @author Marek Posolda */ +@EnableFeature(value = SCRIPTS, skipRestart = true) public class DeployedSAMLScriptMapperTest extends AbstractSamlTest { private static final String SCRIPT_DEPLOYMENT_NAME = "scripts.jar"; @@ -60,7 +57,8 @@ public class DeployedSAMLScriptMapperTest extends AbstractSamlTest { private ClientAttributeUpdater cau; private ProtocolMappersUpdater pmu; - @Deployment(name = SCRIPT_DEPLOYMENT_NAME, managed = false, testable = false) + // Managed to make sure that archive is deployed once in @BeforeClass stage and undeployed once in @AfterClass stage + @Deployment(name = SCRIPT_DEPLOYMENT_NAME, managed = true, testable = false) @TargetsContainer(AUTH_SERVER_CURRENT) public static JavaArchive deploy() throws IOException { ScriptProviderDescriptor representation = new ScriptProviderDescriptor(); @@ -78,15 +76,6 @@ public static void verifyEnvironment() { ContainerAssume.assumeNotAuthServerUndertow(); } - @ArquillianResource - private Deployer deployer; - - @Before - public void deployScripts() throws Exception { - deployer.deploy(SCRIPT_DEPLOYMENT_NAME); - reconnectAdminClient(); - } - @Before public void cleanMappersAndScopes() { this.cau = ClientAttributeUpdater.forClient(adminClient, REALM_NAME, SAML_CLIENT_ID_EMPLOYEE_2) @@ -101,13 +90,8 @@ public void cleanMappersAndScopes() { .addCleanup(this.pmu); } - @After - public void onAfter() throws Exception { - deployer.undeploy(SCRIPT_DEPLOYMENT_NAME); - reconnectAdminClient(); - } - @Test + @DisableFeature(value = SCRIPTS, executeAsLast = false, skipRestart = true) public void testScriptMapperNotAvailableThroughAdminRest() { assertFalse(adminClient.serverInfo().getInfo().getProtocolMapperTypes().get(SamlProtocol.LOGIN_PROTOCOL).stream() .anyMatch( @@ -127,7 +111,6 @@ public void testScriptMapperNotAvailableThroughAdminRest() { @Test - @EnableFeature(value = SCRIPTS, skipRestart = true, executeAsLast = false) public void testScriptMappingThroughServerDeploy() { // ScriptBasedMapper still not available even if SCRIPTS feature is enabled testScriptMapperNotAvailableThroughAdminRest(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/DeployedScriptAuthenticatorTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/DeployedScriptAuthenticatorTest.java index 34dde70fa96a..427541c03463 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/DeployedScriptAuthenticatorTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/DeployedScriptAuthenticatorTest.java @@ -24,15 +24,12 @@ import jakarta.ws.rs.core.Response; -import org.jboss.arquillian.container.test.api.Deployer; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.container.test.api.TargetsContainer; import org.jboss.arquillian.graphene.page.Page; -import org.jboss.arquillian.test.api.ArquillianResource; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.JavaArchive; -import org.junit.After; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Rule; @@ -69,7 +66,8 @@ public class DeployedScriptAuthenticatorTest extends AbstractFlowTest { public static final String EXECUTION_ID = "scriptAuth"; private static final String SCRIPT_DEPLOYMENT_NAME = "scripts.jar"; - @Deployment(name = SCRIPT_DEPLOYMENT_NAME, managed = false, testable = false) + // Managed to make sure that archive is deployed once in @BeforeClass stage and undeployed once in @AfterClass stage + @Deployment(name = SCRIPT_DEPLOYMENT_NAME, managed = true, testable = false) @TargetsContainer(AUTH_SERVER_CURRENT) public static JavaArchive deploy() throws IOException { ScriptProviderDescriptor representation = new ScriptProviderDescriptor(); @@ -93,9 +91,6 @@ public static void verifyEnvironment() { @Page protected LoginPage loginPage; - @ArquillianResource - private Deployer deployer; - private AuthenticationFlowRepresentation flow; @Override @@ -122,8 +117,6 @@ public void configureTestRealm(RealmRepresentation testRealm) { } public void configureFlows() throws Exception { - deployer.deploy(SCRIPT_DEPLOYMENT_NAME); - reconnectAdminClient(); if (testContext.isInitialized()) { return; } @@ -173,12 +166,6 @@ public void configureFlows() throws Exception { testContext.setInitialized(true); } - @After - public void onAfter() throws Exception { - deployer.undeploy(SCRIPT_DEPLOYMENT_NAME); - reconnectAdminClient(); - } - /** * KEYCLOAK-3491 */ diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/DeployedScriptMapperTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/DeployedScriptMapperTest.java index b5b9600cbfcc..5f357b5ca8a4 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/DeployedScriptMapperTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/DeployedScriptMapperTest.java @@ -25,15 +25,11 @@ import java.io.IOException; -import org.jboss.arquillian.container.test.api.Deployer; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.container.test.api.TargetsContainer; -import org.jboss.arquillian.test.api.ArquillianResource; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.JavaArchive; -import org.junit.After; -import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.keycloak.admin.client.resource.ClientResource; @@ -44,6 +40,7 @@ import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.provider.ScriptProviderDescriptor; import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; +import org.keycloak.testsuite.arquillian.annotation.DisableFeature; import org.keycloak.testsuite.arquillian.annotation.EnableFeature; import org.keycloak.testsuite.util.ContainerAssume; import org.keycloak.testsuite.util.OAuthClient; @@ -52,11 +49,13 @@ /** * @author Pedro Igor */ +@EnableFeature(value = SCRIPTS, skipRestart = true) public class DeployedScriptMapperTest extends AbstractTestRealmKeycloakTest { private static final String SCRIPT_DEPLOYMENT_NAME = "scripts.jar"; - @Deployment(name = SCRIPT_DEPLOYMENT_NAME, managed = false, testable = false) + // Managed to make sure that archive is deployed once in @BeforeClass stage and undeployed once in @AfterClass stage + @Deployment(name = SCRIPT_DEPLOYMENT_NAME, managed = true, testable = false) @TargetsContainer(AUTH_SERVER_CURRENT) public static JavaArchive deploy() throws IOException { ScriptProviderDescriptor representation = new ScriptProviderDescriptor(); @@ -74,27 +73,13 @@ public static void verifyEnvironment() { ContainerAssume.assumeNotAuthServerUndertow(); } - @ArquillianResource - private Deployer deployer; - - @Before - public void configureFlows() throws Exception { - deployer.deploy(SCRIPT_DEPLOYMENT_NAME); - reconnectAdminClient(); - } - - @After - public void onAfter() throws Exception { - deployer.undeploy(SCRIPT_DEPLOYMENT_NAME); - reconnectAdminClient(); - } - @Override public void configureTestRealm(RealmRepresentation testRealm) { } @Test + @DisableFeature(value = SCRIPTS, executeAsLast = false, skipRestart = true) public void testScriptMapperNotAvailable() { assertFalse(adminClient.serverInfo().getInfo().getProtocolMapperTypes().get(OIDCLoginProtocol.LOGIN_PROTOCOL).stream() .anyMatch( @@ -102,7 +87,6 @@ public void testScriptMapperNotAvailable() { } @Test - @EnableFeature(value = SCRIPTS, skipRestart = true, executeAsLast = false) public void testTokenScriptMapping() { { ClientResource app = findClientResourceByClientId(adminClient.realm("test"), "test-app"); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/DeployedScriptPolicyTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/DeployedScriptPolicyTest.java index c18b73ed2ecb..84916e9c6b88 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/DeployedScriptPolicyTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/script/DeployedScriptPolicyTest.java @@ -24,14 +24,11 @@ import java.io.IOException; import java.util.List; -import org.jboss.arquillian.container.test.api.Deployer; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.container.test.api.TargetsContainer; -import org.jboss.arquillian.test.api.ArquillianResource; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.JavaArchive; -import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -50,7 +47,6 @@ import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation; import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.provider.ScriptProviderDescriptor; -import org.keycloak.testsuite.arquillian.annotation.DisableFeature; import org.keycloak.testsuite.arquillian.annotation.UncaughtServerErrorExpected; import org.keycloak.testsuite.authz.AbstractAuthzTest; import org.keycloak.testsuite.util.ClientBuilder; @@ -68,7 +64,8 @@ public class DeployedScriptPolicyTest extends AbstractAuthzTest { private static final String SCRIPT_DEPLOYMENT_NAME = "scripts.jar"; - @Deployment(name = SCRIPT_DEPLOYMENT_NAME, managed = false, testable = false) + // Managed to make sure that archive is deployed once in @BeforeClass stage and undeployed once in @AfterClass stage + @Deployment(name = SCRIPT_DEPLOYMENT_NAME, managed = true, testable = false) @TargetsContainer(AUTH_SERVER_CURRENT) public static JavaArchive deploy() throws IOException { ScriptProviderDescriptor representation = new ScriptProviderDescriptor(); @@ -88,9 +85,6 @@ public static void verifyEnvironment() { ContainerAssume.assumeNotAuthServerUndertow(); } - @ArquillianResource - private Deployer deployer; - @Override public void addTestRealms(List testRealms) { testRealms.add(RealmBuilder.create().name("authz-test") @@ -108,18 +102,10 @@ public void addTestRealms(List testRealms) { @Before public void onBefore() throws Exception { - deployer.deploy(SCRIPT_DEPLOYMENT_NAME); - reconnectAdminClient(); AuthorizationResource authorization = getAuthorizationResource(); authorization.resources().create(new ResourceRepresentation("Default Resource")); } - @After - public void onAfter() throws Exception { - deployer.undeploy(SCRIPT_DEPLOYMENT_NAME); - reconnectAdminClient(); - } - @Test public void testJSPolicyProviderNotAvailable() { assertFalse(getAuthorizationResource().policies().policyProviders().stream().anyMatch(rep -> "js".equals(rep.getType()))); From 34c941d953fd92bec47ccbe301c1453ae3448c33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Barto=C5=A1?= Date: Tue, 25 Jul 2023 12:21:31 +0200 Subject: [PATCH 019/135] Profile activation for WF app server doesn't properly work for Windows Fixes #21284 --- .../servers/app-server/jboss/pom.xml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/testsuite/integration-arquillian/servers/app-server/jboss/pom.xml b/testsuite/integration-arquillian/servers/app-server/jboss/pom.xml index 98d4159e9a9a..ebd028c0c2d1 100644 --- a/testsuite/integration-arquillian/servers/app-server/jboss/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/jboss/pom.xml @@ -845,6 +845,23 @@ wildfly + + + + + app-server-wildfly-windows + + + !skipAppServerWildfly + + + Windows + + + + wildfly + + From 75305269d150c9884fa068210f1f55ef1873ac24 Mon Sep 17 00:00:00 2001 From: Ricardo Martin Date: Wed, 26 Jul 2023 11:34:19 +0200 Subject: [PATCH 020/135] Add logout other sessions checkbox to TOTP, webauthn and recovery authn codes setup pages (#21897) * Add logout other sessions checkbox to TOTP, webauthn, recovery authn codes setup pages and to update-email page Closes #10232 --- .../authentication/AuthenticatorUtil.java | 40 ++++++++ .../updateemail/UpdateEmailActionToken.java | 17 +++- .../UpdateEmailActionTokenHandler.java | 5 + .../RecoveryAuthnCodesAction.java | 5 + .../requiredactions/UpdateEmail.java | 11 ++- .../requiredactions/UpdatePassword.java | 16 +--- .../requiredactions/UpdateTotp.java | 5 + .../requiredactions/WebAuthnRegister.java | 9 +- .../auth/page/login/UpdateEmailPage.java | 17 ++-- .../testsuite/pages/LoginConfigTotpPage.java | 2 +- .../pages/LoginPasswordUpdatePage.java | 25 +---- .../testsuite/pages/LogoutSessionsPage.java | 57 ++++++++++++ .../pages/SetupRecoveryAuthnCodesPage.java | 2 +- ...AbstractRequiredActionUpdateEmailTest.java | 20 +++- .../actions/RequiredActionTotpSetupTest.java | 86 +++++++++++++++-- .../RequiredActionUpdateEmailTest.java | 53 +++++++++-- ...onUpdateEmailTestWithVerificationTest.java | 61 +++++++++--- ...BackwardsCompatibilityUserStorageTest.java | 13 ++- .../RecoveryAuthnCodesAuthenticatorTest.java | 93 +++++++++++++++++-- .../webauthn/pages/WebAuthnRegisterPage.java | 4 +- .../AppInitiatedActionWebAuthnTest.java | 61 +++++++++++- .../theme/base/login/login-config-totp.ftl | 7 +- .../login-recovery-authn-code-config.ftl | 8 +- .../base/login/login-update-password.ftl | 11 +-- .../theme/base/login/password-commons.ftl | 12 +++ .../theme/base/login/update-email.ftl | 3 + .../theme/base/login/webauthn-register.ftl | 5 +- 27 files changed, 537 insertions(+), 111 deletions(-) create mode 100644 testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LogoutSessionsPage.java create mode 100644 themes/src/main/resources/theme/base/login/password-commons.ftl diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticatorUtil.java b/services/src/main/java/org/keycloak/authentication/AuthenticatorUtil.java index bba9982529a6..f655d520b988 100755 --- a/services/src/main/java/org/keycloak/authentication/AuthenticatorUtil.java +++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorUtil.java @@ -19,16 +19,25 @@ import com.google.common.collect.Sets; import org.jboss.logging.Logger; +import org.keycloak.authentication.actiontoken.ActionTokenContext; +import org.keycloak.authentication.actiontoken.DefaultActionToken; +import org.keycloak.common.ClientConnection; +import org.keycloak.http.HttpRequest; import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.Constants; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.sessions.AuthenticationSessionModel; import org.keycloak.utils.StringUtil; import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; import static org.keycloak.services.managers.AuthenticationManager.FORCED_REAUTHENTICATION; import static org.keycloak.services.managers.AuthenticationManager.SSO_AUTH; @@ -110,4 +119,35 @@ public static List getExecutionsByType(RealmModel return executions; } + /** + * Logouts all sessions that are different to the current authentication session + * managed in the action context. + * + * @param context The required action context + */ + public static void logoutOtherSessions(RequiredActionContext context) { + logoutOtherSessions(context.getSession(), context.getRealm(), context.getUser(), + context.getAuthenticationSession(), context.getConnection(), context.getHttpRequest()); + } + + /** + * Logouts all sessions that are different to the current authentication session + * managed in the action token context. + * + * @param context The required action token context + */ + public static void logoutOtherSessions(ActionTokenContext context) { + logoutOtherSessions(context.getSession(), context.getRealm(), context.getAuthenticationSession().getAuthenticatedUser(), + context.getAuthenticationSession(), context.getClientConnection(), context.getRequest()); + } + + private static void logoutOtherSessions(KeycloakSession session, RealmModel realm, UserModel user, + AuthenticationSessionModel authSession, ClientConnection conn, HttpRequest req) { + session.sessions().getUserSessionsStream(realm, user) + .filter(s -> !Objects.equals(s.getId(), authSession.getParentSession().getId())) + .collect(Collectors.toList()) // collect to avoid concurrent modification as backchannelLogout removes the user sessions. + .forEach(s -> AuthenticationManager.backchannelLogout(session, realm, s, session.getContext().getUri(), + conn, req.getHttpHeaders(), true) + ); + } } diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/updateemail/UpdateEmailActionToken.java b/services/src/main/java/org/keycloak/authentication/actiontoken/updateemail/UpdateEmailActionToken.java index ac77f2a2b8dc..04b22589b766 100644 --- a/services/src/main/java/org/keycloak/authentication/actiontoken/updateemail/UpdateEmailActionToken.java +++ b/services/src/main/java/org/keycloak/authentication/actiontoken/updateemail/UpdateEmailActionToken.java @@ -28,12 +28,19 @@ public class UpdateEmailActionToken extends DefaultActionToken { private String oldEmail; @JsonProperty("newEmail") private String newEmail; + @JsonProperty("logoutSessions") + private Boolean logoutSessions; - public UpdateEmailActionToken(String userId, int absoluteExpirationInSecs, String oldEmail, String newEmail, String clientId){ + public UpdateEmailActionToken(String userId, int absoluteExpirationInSecs, String oldEmail, String newEmail, String clientId) { + this(userId, absoluteExpirationInSecs, oldEmail, newEmail, clientId, null); + } + + public UpdateEmailActionToken(String userId, int absoluteExpirationInSecs, String oldEmail, String newEmail, String clientId, Boolean logoutSessions){ super(userId, TOKEN_TYPE, absoluteExpirationInSecs, null); this.oldEmail = oldEmail; this.newEmail = newEmail; this.issuedFor = clientId; + this.logoutSessions = Boolean.TRUE.equals(logoutSessions)? true : null; } private UpdateEmailActionToken(){ @@ -55,4 +62,12 @@ public String getNewEmail() { public void setNewEmail(String newEmail) { this.newEmail = newEmail; } + + public Boolean getLogoutSessions() { + return this.logoutSessions; + } + + public void setLogoutSessions(Boolean logoutSessions) { + this.logoutSessions = Boolean.TRUE.equals(logoutSessions)? true : null; + } } diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/updateemail/UpdateEmailActionTokenHandler.java b/services/src/main/java/org/keycloak/authentication/actiontoken/updateemail/UpdateEmailActionTokenHandler.java index 7e9357a4451e..82436ad84905 100644 --- a/services/src/main/java/org/keycloak/authentication/actiontoken/updateemail/UpdateEmailActionTokenHandler.java +++ b/services/src/main/java/org/keycloak/authentication/actiontoken/updateemail/UpdateEmailActionTokenHandler.java @@ -21,6 +21,7 @@ import java.util.Objects; import jakarta.ws.rs.core.Response; import org.keycloak.TokenVerifier; +import org.keycloak.authentication.AuthenticatorUtil; import org.keycloak.authentication.actiontoken.AbstractActionTokenHandler; import org.keycloak.authentication.actiontoken.ActionTokenContext; import org.keycloak.authentication.actiontoken.TokenUtils; @@ -74,6 +75,10 @@ public Response handleToken(UpdateEmailActionToken token, ActionTokenContextBill Burke @@ -95,9 +90,7 @@ public void requiredActionChallenge(RequiredActionContext context) { public void processAction(RequiredActionContext context) { EventBuilder event = context.getEvent(); AuthenticationSessionModel authSession = context.getAuthenticationSession(); - RealmModel realm = context.getRealm(); UserModel user = context.getUser(); - KeycloakSession session = context.getSession(); MultivaluedMap formData = context.getHttpRequest().getDecodedFormParameters(); event.event(EventType.UPDATE_PASSWORD); String passwordNew = formData.getFirst("password-new"); @@ -125,13 +118,8 @@ public void processAction(RequiredActionContext context) { return; } - if ("on".equals(formData.getFirst("logout-sessions"))) - { - session.sessions().getUserSessionsStream(realm, user) - .filter(s -> !Objects.equals(s.getId(), authSession.getParentSession().getId())) - .collect(Collectors.toList()) // collect to avoid concurrent modification as backchannelLogout removes the user sessions. - .forEach(s -> AuthenticationManager.backchannelLogout(session, realm, s, session.getContext().getUri(), - context.getConnection(), context.getHttpRequest().getHttpHeaders(), true)); + if ("on".equals(formData.getFirst("logout-sessions"))) { + AuthenticatorUtil.logoutOtherSessions(context); } try { diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateTotp.java b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateTotp.java index c35f2a21b140..17e93ca86f46 100644 --- a/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateTotp.java +++ b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateTotp.java @@ -18,6 +18,7 @@ package org.keycloak.authentication.requiredactions; import org.keycloak.Config; +import org.keycloak.authentication.AuthenticatorUtil; import org.keycloak.authentication.CredentialRegistrator; import org.keycloak.authentication.InitiatedActionSupport; import org.keycloak.authentication.RequiredActionContext; @@ -105,6 +106,10 @@ public void processAction(RequiredActionContext context) { return; } + if ("on".equals(formData.getFirst("logout-sessions"))) { + AuthenticatorUtil.logoutOtherSessions(context); + } + if (!CredentialHelper.createOTPCredential(context.getSession(), context.getRealm(), context.getUser(), challengeResponse, credentialModel)) { Response challenge = context.form() .setAttribute("mode", mode) diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/WebAuthnRegister.java b/services/src/main/java/org/keycloak/authentication/requiredactions/WebAuthnRegister.java index 208e9370c2aa..f12abb75ee3b 100644 --- a/services/src/main/java/org/keycloak/authentication/requiredactions/WebAuthnRegister.java +++ b/services/src/main/java/org/keycloak/authentication/requiredactions/WebAuthnRegister.java @@ -35,6 +35,7 @@ import org.jboss.logging.Logger; import org.keycloak.http.HttpRequest; import org.keycloak.WebAuthnConstants; +import org.keycloak.authentication.AuthenticatorUtil; import org.keycloak.authentication.CredentialRegistrator; import org.keycloak.authentication.InitiatedActionSupport; import org.keycloak.authentication.RequiredActionContext; @@ -52,6 +53,8 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.UserModel; import org.keycloak.models.WebAuthnPolicy; +import org.keycloak.models.credential.WebAuthnCredentialModel; +import org.keycloak.utils.StringUtil; import com.webauthn4j.converter.util.ObjectConverter; import com.webauthn4j.data.attestation.authenticator.AttestedCredentialData; @@ -73,8 +76,6 @@ import com.webauthn4j.validator.attestation.statement.u2f.FIDOU2FAttestationStatementValidator; import com.webauthn4j.validator.attestation.trustworthiness.certpath.CertPathTrustworthinessValidator; import com.webauthn4j.validator.attestation.trustworthiness.self.DefaultSelfAttestationTrustworthinessValidator; -import org.keycloak.models.credential.WebAuthnCredentialModel; -import org.keycloak.utils.StringUtil; import static org.keycloak.WebAuthnConstants.REG_ERR_DETAIL_LABEL; import static org.keycloak.WebAuthnConstants.REG_ERR_LABEL; @@ -225,6 +226,10 @@ public void processAction(RequiredActionContext context) { RegistrationParameters registrationParameters = new RegistrationParameters(serverProperty, isUserVerificationRequired); + if ("on".equals(params.getFirst("logout-sessions"))) { + AuthenticatorUtil.logoutOtherSessions(context); + } + WebAuthnRegistrationManager webAuthnRegistrationManager = createWebAuthnRegistrationManager(); try { // parse diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/UpdateEmailPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/UpdateEmailPage.java index 96392e345717..f0d5dbb0f20a 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/UpdateEmailPage.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/UpdateEmailPage.java @@ -20,11 +20,12 @@ import static org.keycloak.testsuite.util.UIUtils.getTextFromElement; import org.keycloak.models.UserModel; +import org.keycloak.testsuite.pages.LogoutSessionsPage; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; -public class UpdateEmailPage extends RequiredActions { +public class UpdateEmailPage extends LogoutSessionsPage { @FindBy(id = "email") private WebElement emailInput; @@ -38,22 +39,20 @@ public class UpdateEmailPage extends RequiredActions { @FindBy(css = "input[type='submit']") private WebElement submitActionButton; - @Override - public String getActionId() { - return UserModel.RequiredAction.UPDATE_EMAIL.name(); - } + @FindBy(css = "input[type='submit']") + private WebElement submitButton; @Override public boolean isCurrent() { return driver.getCurrentUrl().contains("login-actions/required-action") - && driver.getCurrentUrl().contains("execution=" + getActionId()); + && driver.getCurrentUrl().contains("execution=" + UserModel.RequiredAction.UPDATE_EMAIL.name()); } public void changeEmail(String email){ emailInput.clear(); emailInput.sendKeys(email); - submit(); + clickLink(submitButton); } public String getEmail() { @@ -84,4 +83,8 @@ public void clickSubmitAction() { clickLink(submitActionButton); } + @Override + public void open() throws Exception { + throw new UnsupportedOperationException(); + } } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginConfigTotpPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginConfigTotpPage.java index 28d74b823b0c..220c606ce665 100755 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginConfigTotpPage.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginConfigTotpPage.java @@ -25,7 +25,7 @@ /** * @author Stian Thorgersen */ -public class LoginConfigTotpPage extends AbstractPage { +public class LoginConfigTotpPage extends LogoutSessionsPage { @FindBy(id = "totpSecret") private WebElement totpSecret; diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java index 37ca7f924954..ce068e943a1a 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPasswordUpdatePage.java @@ -19,14 +19,12 @@ import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import static org.keycloak.testsuite.util.UIUtils.isElementVisible; /** * @author Stian Thorgersen */ -public class LoginPasswordUpdatePage extends LanguageComboboxAwarePage { +public class LoginPasswordUpdatePage extends LogoutSessionsPage { @FindBy(id = "password-new") private WebElement newPasswordInput; @@ -42,9 +40,6 @@ public class LoginPasswordUpdatePage extends LanguageComboboxAwarePage { @FindBy(className = "kc-feedback-text") private WebElement feedbackMessage; - - @FindBy(id = "logout-sessions") - private WebElement logoutSessionsCheckbox; @FindBy(name = "cancel-aia") private WebElement cancelAIAButton; @@ -76,24 +71,6 @@ public String getFeedbackMessage() { return feedbackMessage.getText(); } - public boolean isLogoutSessionDisplayed() { - return isElementVisible(logoutSessionsCheckbox); - } - - public boolean isLogoutSessionsChecked() { - return logoutSessionsCheckbox.isSelected(); - } - - public void checkLogoutSessions() { - assertFalse("Logout sessions is checked", isLogoutSessionsChecked()); - logoutSessionsCheckbox.click(); - } - - public void uncheckLogoutSessions() { - assertTrue("Logout sessions is not checked", isLogoutSessionsChecked()); - logoutSessionsCheckbox.click(); - } - public boolean isCancelDisplayed() { return isElementVisible(cancelAIAButton); } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LogoutSessionsPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LogoutSessionsPage.java new file mode 100644 index 000000000000..48b527c40989 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LogoutSessionsPage.java @@ -0,0 +1,57 @@ +/* + * Copyright 2023 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.testsuite.pages; + +import org.junit.Assert; +import org.keycloak.testsuite.util.UIUtils; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; + +/** + *

A page that contains the logout other sessions checkbox.

+ * + * @author rmartinc + */ +public abstract class LogoutSessionsPage extends LanguageComboboxAwarePage { + + @FindBy(id = "logout-sessions") + private WebElement logoutSessionsCheckbox; + + @Override + public void assertCurrent() { + super.assertCurrent(); + Assert.assertTrue("The page doesn't display the logout other sessions checkbox", this.isLogoutSessionDisplayed()); + } + + public boolean isLogoutSessionDisplayed() { + return UIUtils.isElementVisible(logoutSessionsCheckbox); + } + + public boolean isLogoutSessionsChecked() { + return logoutSessionsCheckbox.isSelected(); + } + + public void checkLogoutSessions() { + Assert.assertFalse("Logout sessions is checked", isLogoutSessionsChecked()); + logoutSessionsCheckbox.click(); + } + + public void uncheckLogoutSessions() { + Assert.assertTrue("Logout sessions is not checked", isLogoutSessionsChecked()); + logoutSessionsCheckbox.click(); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/SetupRecoveryAuthnCodesPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/SetupRecoveryAuthnCodesPage.java index 76cc797ca8c1..7b0b6d21cbbe 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/SetupRecoveryAuthnCodesPage.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/SetupRecoveryAuthnCodesPage.java @@ -9,7 +9,7 @@ import java.util.List; import java.util.Scanner; -public class SetupRecoveryAuthnCodesPage extends LanguageComboboxAwarePage { +public class SetupRecoveryAuthnCodesPage extends LogoutSessionsPage { @FindBy(id = "kc-recovery-codes-list") private WebElement recoveryAuthnCodesList; diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/AbstractRequiredActionUpdateEmailTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/AbstractRequiredActionUpdateEmailTest.java index efb773520db2..91246a24ffd9 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/AbstractRequiredActionUpdateEmailTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/AbstractRequiredActionUpdateEmailTest.java @@ -18,12 +18,15 @@ import static org.junit.Assert.assertFalse; +import java.util.Arrays; +import org.jboss.arquillian.drone.api.annotation.Drone; import org.jboss.arquillian.graphene.page.Page; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.admin.client.resource.UserResource; import org.keycloak.common.Profile; import org.keycloak.models.UserModel; import org.keycloak.representations.idm.RealmRepresentation; @@ -35,7 +38,9 @@ import org.keycloak.testsuite.auth.page.login.UpdateEmailPage; import org.keycloak.testsuite.pages.AppPage; import org.keycloak.testsuite.pages.LoginPage; +import org.keycloak.testsuite.util.SecondBrowser; import org.keycloak.testsuite.util.UserBuilder; +import org.openqa.selenium.WebDriver; @EnableFeature(Profile.Feature.UPDATE_EMAIL) public abstract class AbstractRequiredActionUpdateEmailTest extends AbstractTestRealmKeycloakTest { @@ -52,6 +57,10 @@ public abstract class AbstractRequiredActionUpdateEmailTest extends AbstractTest @Page protected AppPage appPage; + @Drone + @SecondBrowser + protected WebDriver driver2; + @Before public void beforeTest() { ApiUtil.removeUserByUsername(testRealm(), "test-user@localhost"); @@ -81,6 +90,13 @@ private void setRegistrationEmailAsUsername(RealmResource realmResource, boolean realmResource.update(realmRepresentation); } + protected void configureRequiredActionsToUser(String username, String... actions) { + UserResource userResource = ApiUtil.findUserByUsernameId(testRealm(), username); + UserRepresentation userRepresentation = userResource.toRepresentation(); + userRepresentation.setRequiredActions(Arrays.asList(actions)); + userResource.update(userRepresentation); + } + protected void prepareUser(UserRepresentation user) { } @@ -168,7 +184,7 @@ public void updateEmailWithEmailAsUsernameEnabled() throws Exception { setRegistrationEmailAsUsername(testRealm(), true); try { - changeEmailUsingRequiredAction("new@localhost"); + changeEmailUsingRequiredAction("new@localhost", true); UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "new@localhost"); Assert.assertNotNull(user); @@ -177,5 +193,5 @@ public void updateEmailWithEmailAsUsernameEnabled() throws Exception { } } - protected abstract void changeEmailUsingRequiredAction(String newEmail) throws Exception; + protected abstract void changeEmailUsingRequiredAction(String newEmail, boolean logoutOtherSessions) throws Exception; } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java index 5cedb3c811fc..5e1e429783c6 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java @@ -16,6 +16,9 @@ */ package org.keycloak.testsuite.actions; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.jboss.arquillian.drone.api.annotation.Drone; import org.jboss.arquillian.graphene.page.Page; import org.junit.Assert; import org.junit.Before; @@ -34,6 +37,7 @@ import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RequiredActionProviderRepresentation; import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.representations.idm.UserSessionRepresentation; import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; import org.keycloak.testsuite.admin.ApiUtil; @@ -48,12 +52,16 @@ import org.keycloak.testsuite.util.AccountHelper; import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.RealmBuilder; +import org.keycloak.testsuite.util.SecondBrowser; import org.keycloak.testsuite.util.UserBuilder; import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; import java.io.IOException; +import java.util.Arrays; import java.util.LinkedList; import java.util.List; +import java.util.stream.Collectors; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -79,14 +87,26 @@ public void configureTestRealm(RealmRepresentation testRealm) { testRealm.setResetPasswordAllowed(Boolean.TRUE); } + private void setOTPAuthRequirement(AuthenticationExecutionModel.Requirement requirement) { + adminClient.realm(TEST_REALM_NAME).flows().getExecutions("browser"). + stream().filter(execution -> execution.getDisplayName().equals("Browser - Conditional OTP")) + .forEach(execution -> { + execution.setRequirement(requirement.name()); + adminClient.realm("test").flows().updateExecutions("browser", execution); + }); + } + + private void configureRequiredActionsToUser(String username, String... actions) { + UserResource userResource = ApiUtil.findUserByUsernameId(testRealm(), username); + UserRepresentation userRepresentation = userResource.toRepresentation(); + userRepresentation.setRequiredActions(Arrays.asList(actions)); + userResource.update(userRepresentation); + } + @Before public void setOTPAuthRequired() { - adminClient.realm("test").flows().getExecutions("browser"). - stream().filter(execution -> execution.getDisplayName().equals("Browser - Conditional OTP")) - .forEach(execution -> - {execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED.name()); - adminClient.realm("test").flows().updateExecutions("browser", execution);}); + setOTPAuthRequirement(AuthenticationExecutionModel.Requirement.REQUIRED); ApiUtil.removeUserByUsername(testRealm(), "test-user@localhost"); UserRepresentation user = UserBuilder.create().enabled(true) @@ -94,7 +114,7 @@ public void setOTPAuthRequired() { .email("test-user@localhost") .firstName("Tom") .lastName("Brady") - .requiredAction(UserModel.RequiredAction.UPDATE_PROFILE.name()).build(); + .build(); ApiUtil.createUserAndResetPasswordWithAdminClient(testRealm(), user, "password"); } @@ -117,6 +137,10 @@ public void setOTPAuthRequired() { @Page protected RegisterPage registerPage; + @Drone + @SecondBrowser + private WebDriver driver2; + protected TimeBasedOTP totp = new TimeBasedOTP(); @Test @@ -602,4 +626,54 @@ public void setupOtpPolicyChangedHotp() { } + @Test + public void testTotpLogoutOtherSessionsChecked() { + testTotpLogoutOtherSessions(true); + } + + @Test + public void testTotpLogoutOtherSessionsNotChecked() { + testTotpLogoutOtherSessions(false); + } + + private void testTotpLogoutOtherSessions(boolean logoutOtherSessions) { + // allow login via password without OTP forced + setOTPAuthRequirement(AuthenticationExecutionModel.Requirement.CONDITIONAL); + configureRequiredActionsToUser("test-user@localhost"); + + // login with the user using the second driver + UserResource testUser = testRealm().users().get(findUser("test-user@localhost").getId()); + OAuthClient oauth2 = new OAuthClient(); + oauth2.init(driver2); + oauth2.doLogin("test-user@localhost", "password"); + EventRepresentation event1 = events.expectLogin().assertEvent(); + assertEquals(1, testUser.getUserSessions().size()); + + // add action to configure totp + configureRequiredActionsToUser("test-user@localhost", UserModel.RequiredAction.CONFIGURE_TOTP.name()); + + // login and configure totp checking/unchecking the logout checkbox + loginPage.open(); + loginPage.login("test-user@localhost", "password"); + totpPage.assertCurrent(); + if (!logoutOtherSessions) { + totpPage.uncheckLogoutSessions(); + } + Assert.assertEquals(logoutOtherSessions, totpPage.isLogoutSessionsChecked()); + totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret())); + assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); + EventRepresentation event2 = events.expectRequiredAction(EventType.UPDATE_TOTP).user(event1.getUserId()).detail(Details.USERNAME, "test-user@localhost").assertEvent(); + event2 = events.expectLogin().user(event2.getUserId()).session(event2.getDetails().get(Details.CODE_ID)).detail(Details.USERNAME, "test-user@localhost").assertEvent(); + + // assert old session is gone or is maintained + List sessions = testUser.getUserSessions(); + if (logoutOtherSessions) { + assertEquals(1, sessions.size()); + assertEquals(event2.getSessionId(), sessions.iterator().next().getId()); + } else { + assertEquals(2, sessions.size()); + MatcherAssert.assertThat(sessions.stream().map(UserSessionRepresentation::getId).collect(Collectors.toList()), + Matchers.containsInAnyOrder(event1.getSessionId(), event2.getSessionId())); + } + } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateEmailTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateEmailTest.java index 85ecb6978629..35e2466995bc 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateEmailTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateEmailTest.java @@ -19,35 +19,66 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import java.util.List; +import java.util.stream.Collectors; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.Assert; import org.junit.Test; +import org.keycloak.admin.client.resource.UserResource; import org.keycloak.events.Details; import org.keycloak.events.EventType; import org.keycloak.models.UserModel; +import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.representations.idm.UserSessionRepresentation; import org.keycloak.testsuite.pages.AppPage; +import org.keycloak.testsuite.util.OAuthClient; public class RequiredActionUpdateEmailTest extends AbstractRequiredActionUpdateEmailTest { @Override - protected void changeEmailUsingRequiredAction(String newEmail) { + protected void changeEmailUsingRequiredAction(String newEmail, boolean logoutOtherSessions) { loginPage.open(); loginPage.login("test-user@localhost", "password"); - updateEmailPage.assertCurrent(); + if (!logoutOtherSessions) { + updateEmailPage.uncheckLogoutSessions(); + } + Assert.assertEquals(logoutOtherSessions, updateEmailPage.isLogoutSessionsChecked()); updateEmailPage.changeEmail(newEmail); } - @Test - public void updateEmail() { - changeEmailUsingRequiredAction("new@localhost"); + private void updateEmail(boolean logoutOtherSessions) { + // login using another session + configureRequiredActionsToUser("test-user@localhost"); + UserResource testUser = testRealm().users().get(findUser("test-user@localhost").getId()); + OAuthClient oauth2 = new OAuthClient(); + oauth2.init(driver2); + oauth2.doLogin("test-user@localhost", "password"); + EventRepresentation event1 = events.expectLogin().assertEvent(); + assertEquals(1, testUser.getUserSessions().size()); + + // add the action and change it + configureRequiredActionsToUser("test-user@localhost", UserModel.RequiredAction.UPDATE_EMAIL.name()); + changeEmailUsingRequiredAction("new@localhost", logoutOtherSessions); events.expectRequiredAction(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost") .detail(Details.UPDATED_EMAIL, "new@localhost").assertEvent(); assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType()); - events.expectLogin().assertEvent(); + EventRepresentation event2 = events.expectLogin().assertEvent(); + List sessions = testUser.getUserSessions(); + if (logoutOtherSessions) { + assertEquals(1, sessions.size()); + assertEquals(event2.getSessionId(), sessions.iterator().next().getId()); + } else { + assertEquals(2, sessions.size()); + MatcherAssert.assertThat(sessions.stream().map(UserSessionRepresentation::getId).collect(Collectors.toList()), + Matchers.containsInAnyOrder(event1.getSessionId(), event2.getSessionId())); + } // assert user is really updated in persistent store UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost"); @@ -56,4 +87,14 @@ public void updateEmail() { assertEquals("Brady", user.getLastName()); assertFalse(user.getRequiredActions().contains(UserModel.RequiredAction.UPDATE_EMAIL.name())); } + + @Test + public void updateEmailLogoutSessionsChecked() { + updateEmail(true); + } + + @Test + public void updateEmailLogoutSessionsNotChecked() { + updateEmail(false); + } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateEmailTestWithVerificationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateEmailTestWithVerificationTest.java index a4dd2135399e..b784d2b4fc60 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateEmailTestWithVerificationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateEmailTestWithVerificationTest.java @@ -20,23 +20,29 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import java.io.IOException; import jakarta.mail.Address; import jakarta.mail.Message; import jakarta.mail.MessagingException; import jakarta.mail.internet.MimeMessage; +import java.io.IOException; +import java.util.List; import org.jboss.arquillian.graphene.page.Page; +import org.junit.Assert; import org.junit.Rule; import org.junit.Test; +import org.keycloak.admin.client.resource.UserResource; import org.keycloak.events.Details; import org.keycloak.events.EventType; import org.keycloak.models.UserModel; +import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.representations.idm.UserSessionRepresentation; import org.keycloak.testsuite.pages.ErrorPage; import org.keycloak.testsuite.pages.InfoPage; import org.keycloak.testsuite.util.GreenMailRule; import org.keycloak.testsuite.util.MailUtils; +import org.keycloak.testsuite.util.OAuthClient; public class RequiredActionUpdateEmailTestWithVerificationTest extends AbstractRequiredActionUpdateEmailTest { @@ -59,13 +65,17 @@ public void configureTestRealm(RealmRepresentation testRealm) { } @Override - protected void changeEmailUsingRequiredAction(String newEmail) throws Exception { + protected void changeEmailUsingRequiredAction(String newEmail, boolean logoutOtherSessions) throws Exception { loginPage.open(); loginPage.login("test-user@localhost", "password"); - updateEmailPage.assertCurrent(); - updateEmailPage.changeEmail(newEmail); + updateEmailPage.assertCurrent(); + if (!logoutOtherSessions) { + updateEmailPage.uncheckLogoutSessions(); + } + Assert.assertEquals(logoutOtherSessions, updateEmailPage.isLogoutSessionsChecked()); + updateEmailPage.changeEmail(newEmail); events.expect(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, newEmail).assertEvent(); UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost"); @@ -78,19 +88,48 @@ protected void changeEmailUsingRequiredAction(String newEmail) throws Exception assertEquals("The account email has been successfully updated to new@localhost.", infoPage.getInfo()); } - @Test - public void updateEmail() throws Exception { - changeEmailUsingRequiredAction("new@localhost"); - - events.expect(EventType.UPDATE_EMAIL) - .detail(Details.PREVIOUS_EMAIL, "test-user@localhost") - .detail(Details.UPDATED_EMAIL, "new@localhost"); + private void updateEmail(boolean logoutOtherSessions) throws Exception { + // login using another session + configureRequiredActionsToUser("test-user@localhost"); + UserResource testUser = testRealm().users().get(findUser("test-user@localhost").getId()); + OAuthClient oauth2 = new OAuthClient(); + oauth2.init(driver2); + oauth2.doLogin("test-user@localhost", "password"); + EventRepresentation event1 = events.expectLogin().assertEvent(); + assertEquals(1, testUser.getUserSessions().size()); + + // add action and change email + configureRequiredActionsToUser("test-user@localhost", UserModel.RequiredAction.UPDATE_EMAIL.name()); + changeEmailUsingRequiredAction("new@localhost", logoutOtherSessions); + + events.expect(EventType.UPDATE_EMAIL) + .detail(Details.PREVIOUS_EMAIL, "test-user@localhost") + .detail(Details.UPDATED_EMAIL, "new@localhost") + .assertEvent(); + + List sessions = testUser.getUserSessions(); + if (logoutOtherSessions) { + assertEquals(0, sessions.size()); + } else { + assertEquals(1, sessions.size()); + assertEquals(event1.getSessionId(), sessions.iterator().next().getId()); + } UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost"); assertEquals("new@localhost", user.getEmail()); assertFalse(user.getRequiredActions().contains(UserModel.RequiredAction.UPDATE_EMAIL.name())); } + @Test + public void updateEmailLogoutSessionsChecked() throws Exception { + updateEmail(true); + } + + @Test + public void updateEmailLogoutSessionsNotChecked() throws Exception { + updateEmail(false); + } + @Test public void confirmEmailUpdateAfterThirdPartyEmailUpdate() throws MessagingException, IOException { loginPage.open(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/BackwardsCompatibilityUserStorageTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/BackwardsCompatibilityUserStorageTest.java index 9c81ebb93c1b..7b69678b2d44 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/BackwardsCompatibilityUserStorageTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/BackwardsCompatibilityUserStorageTest.java @@ -168,7 +168,7 @@ public void testOTPUpdateAndLogin() throws URISyntaxException, IOException { getCleanup().addUserId(userId); // Setup OTP for the user - String totpSecret = setupOTPForUserWithRequiredAction(userId); + String totpSecret = setupOTPForUserWithRequiredAction(userId, true); // Assert user has OTP in the userStorage assertUserDontHaveDBCredentials(); @@ -209,7 +209,7 @@ public void testOTPSetupThroughAdminRESTAndLogin() throws URISyntaxException, IO getCleanup().addUserId(userId); // Setup OTP - String totpSecret = setupOTPForUserWithRequiredAction(userId); + String totpSecret = setupOTPForUserWithRequiredAction(userId, true); assertUserDontHaveDBCredentials(); assertUserHasOTPCredentialInUserStorage(true); @@ -245,7 +245,7 @@ public void testOTPSetupAndRemoveThroughAccountMgmtAndLogin() throws URISyntaxEx String accountToken = tokenUtil.getToken(); // Setup OTP - String totpSecret = setupOTPForUserWithRequiredAction(userId); + String totpSecret = setupOTPForUserWithRequiredAction(userId, false); assertUserDontHaveDBCredentials(); assertUserHasOTPCredentialInUserStorage(true); @@ -281,7 +281,7 @@ public void testDisableCredentialsInUserStorage() throws URISyntaxException, IOE getCleanup().addUserId(userId); // Setup OTP for the user - setupOTPForUserWithRequiredAction(userId); + setupOTPForUserWithRequiredAction(userId, true); // Assert user has OTP in the userStorage assertUserDontHaveDBCredentials(); @@ -315,7 +315,7 @@ public void testSearchUserStorage() { } // return created totpSecret - private String setupOTPForUserWithRequiredAction(String userId) throws URISyntaxException, IOException { + private String setupOTPForUserWithRequiredAction(String userId, boolean logoutOtherSessions) throws URISyntaxException, IOException { // Add required action to the user to reset OTP UserResource user = testRealm().users().get(userId); UserRepresentation userRep = user.toRepresentation(); @@ -328,6 +328,9 @@ private String setupOTPForUserWithRequiredAction(String userId) throws URISyntax testAppHelper.startLogin("otp1", "pass"); configureTotpRequiredActionPage.assertCurrent(); + if (!logoutOtherSessions) { + configureTotpRequiredActionPage.uncheckLogoutSessions(); + } String totpSecret = configureTotpRequiredActionPage.getTotpSecret(); configureTotpRequiredActionPage.configure(totp.generateTOTP(totpSecret)); appPage.assertCurrent(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RecoveryAuthnCodesAuthenticatorTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RecoveryAuthnCodesAuthenticatorTest.java index 06b211ac3e88..d16d13af82a6 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RecoveryAuthnCodesAuthenticatorTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RecoveryAuthnCodesAuthenticatorTest.java @@ -1,25 +1,39 @@ package org.keycloak.testsuite.forms; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; import org.jboss.arquillian.drone.api.annotation.Drone; import org.jboss.arquillian.graphene.page.Page; +import org.junit.Assert; +import org.junit.FixMethodOrder; +import org.junit.Rule; import org.junit.Test; +import org.junit.runners.MethodSorters; +import org.keycloak.admin.client.resource.UserResource; import org.keycloak.authentication.AuthenticationFlow; import org.keycloak.authentication.authenticators.browser.RecoveryAuthnCodesFormAuthenticatorFactory; import org.keycloak.authentication.authenticators.browser.PasswordFormFactory; import org.keycloak.authentication.authenticators.browser.UsernameFormFactory; import org.keycloak.credential.CredentialModel; +import org.keycloak.events.Details; +import org.keycloak.events.EventType; import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.models.credential.RecoveryAuthnCodesCredentialModel; import org.keycloak.models.utils.RecoveryAuthnCodesUtils; +import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RequiredActionProviderSimpleRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.representations.idm.UserSessionRepresentation; import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; +import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.arquillian.annotation.EnableFeature; import org.keycloak.testsuite.arquillian.annotation.IgnoreBrowserDriver; import org.keycloak.testsuite.client.KeycloakTestingClient; +import org.keycloak.testsuite.pages.AppPage; import org.keycloak.testsuite.pages.EnterRecoveryAuthnCodePage; import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.LoginUsernameOnlyPage; @@ -27,16 +41,19 @@ import org.keycloak.testsuite.pages.SelectAuthenticatorPage; import org.keycloak.testsuite.pages.SetupRecoveryAuthnCodesPage; import org.keycloak.testsuite.util.FlowUtil; +import org.keycloak.testsuite.util.OAuthClient; +import org.keycloak.testsuite.util.SecondBrowser; import org.openqa.selenium.WebDriver; -import org.junit.Assert; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.firefox.FirefoxDriver; import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; import static org.keycloak.common.Profile.Feature.RECOVERY_CODES; /** @@ -44,6 +61,7 @@ * * @author Venkata Nukala */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) @EnableFeature(value = RECOVERY_CODES, skipRestart = true) public class RecoveryAuthnCodesAuthenticatorTest extends AbstractTestRealmKeycloakTest { @@ -51,9 +69,6 @@ public class RecoveryAuthnCodesAuthenticatorTest extends AbstractTestRealmKeyclo private static final int BRUTE_FORCE_FAIL_ATTEMPTS = 3; - @Drone - protected WebDriver driver; - @Page protected LoginPage loginPage; @@ -72,6 +87,16 @@ public class RecoveryAuthnCodesAuthenticatorTest extends AbstractTestRealmKeyclo @Page protected PasswordPage passwordPage; + @Page + protected AppPage appPage; + + @Drone + @SecondBrowser + private WebDriver driver2; + + @Rule + public AssertEvents events = new AssertEvents(this); + @Override public void configureTestRealm(RealmRepresentation testRealm) { @@ -97,12 +122,63 @@ void configureBrowserFlowWithRecoveryAuthnCodes(KeycloakTestingClient testingCli ); ApiUtil.removeUserByUsername(testRealm(), "test-user@localhost"); - String userId = createUser("test", "test-user@localhost", "password", UserModel.RequiredAction.CONFIGURE_RECOVERY_AUTHN_CODES.name()); + createUser("test", "test-user@localhost", "password", UserModel.RequiredAction.CONFIGURE_RECOVERY_AUTHN_CODES.name()); + } + + private void testSetupRecoveryAuthnCodesLogoutOtherSessions(boolean logoutOtherSessions) { + // login with the user using the second driver + UserResource testUser = testRealm().users().get(findUser("test-user@localhost").getId()); + OAuthClient oauth2 = new OAuthClient(); + oauth2.init(driver2); + oauth2.doLogin("test-user@localhost", "password"); + EventRepresentation event1 = events.expectLogin().assertEvent(); + assertEquals(1, testUser.getUserSessions().size()); + + // add action to recovery codes for the test user + UserRepresentation userRepresentation = testUser.toRepresentation(); + userRepresentation.setRequiredActions(Arrays.asList(UserModel.RequiredAction.CONFIGURE_RECOVERY_AUTHN_CODES.name())); + testUser.update(userRepresentation); + + // login and configure codes + loginPage.open(); + loginPage.login("test-user@localhost", "password"); + setupRecoveryAuthnCodesPage.assertCurrent(); + if (!logoutOtherSessions) { + setupRecoveryAuthnCodesPage.uncheckLogoutSessions(); + } + Assert.assertEquals(logoutOtherSessions, setupRecoveryAuthnCodesPage.isLogoutSessionsChecked()); + setupRecoveryAuthnCodesPage.clickSaveRecoveryAuthnCodesButton(); + assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType()); + EventRepresentation event2 = events.expectRequiredAction(EventType.CUSTOM_REQUIRED_ACTION) + .user(event1.getUserId()).detail(Details.USERNAME, "test-user@localhost").assertEvent(); + event2 = events.expectLogin().user(event2.getUserId()).session(event2.getDetails().get(Details.CODE_ID)) + .detail(Details.USERNAME, "test-user@localhost").assertEvent(); + + // assert old session is gone or is maintained + List sessions = testUser.getUserSessions(); + if (logoutOtherSessions) { + assertEquals(1, sessions.size()); + assertEquals(event2.getSessionId(), sessions.iterator().next().getId()); + } else { + assertEquals(2, sessions.size()); + MatcherAssert.assertThat(sessions.stream().map(UserSessionRepresentation::getId).collect(Collectors.toList()), + Matchers.containsInAnyOrder(event1.getSessionId(), event2.getSessionId())); + } + } + + @Test + public void test01SetupRecoveryAuthnCodesLogoutOtherSessionsChecked() throws Exception { + testSetupRecoveryAuthnCodesLogoutOtherSessions(true); + } + + @Test + public void test02SetupRecoveryAuthnCodesLogoutOtherSessionsNotChecked() { + testSetupRecoveryAuthnCodesLogoutOtherSessions(false); } // In a sub-flow with alternative credential executors, test whether Recovery Authentication Codes are working @Test - public void testAuthenticateRecoveryAuthnCodes() { + public void test03AuthenticateRecoveryAuthnCodes() { try { configureBrowserFlowWithRecoveryAuthnCodes(testingClient); testRealm().flows().removeRequiredAction(UserModel.RequiredAction.CONFIGURE_RECOVERY_AUTHN_CODES.name()); @@ -146,7 +222,7 @@ public void testAuthenticateRecoveryAuthnCodes() { @Test @IgnoreBrowserDriver(FirefoxDriver.class) @IgnoreBrowserDriver(ChromeDriver.class) - public void testSetupRecoveryAuthnCodes() { + public void test04SetupRecoveryAuthnCodes() { try { configureBrowserFlowWithRecoveryAuthnCodes(testingClient); RequiredActionProviderSimpleRepresentation simpleRepresentation = new RequiredActionProviderSimpleRepresentation(); @@ -174,11 +250,10 @@ public void testSetupRecoveryAuthnCodes() { } } - @Test @IgnoreBrowserDriver(FirefoxDriver.class) // TODO: https://github.com/keycloak/keycloak/issues/13543 @IgnoreBrowserDriver(ChromeDriver.class) - public void testBruteforceProtectionRecoveryAuthnCodes() { + public void test05BruteforceProtectionRecoveryAuthnCodes() { try { configureBrowserFlowWithRecoveryAuthnCodes(testingClient); RealmRepresentation rep = testRealm().toRepresentation(); diff --git a/testsuite/integration-arquillian/tests/other/webauthn/src/main/java/org/keycloak/testsuite/webauthn/pages/WebAuthnRegisterPage.java b/testsuite/integration-arquillian/tests/other/webauthn/src/main/java/org/keycloak/testsuite/webauthn/pages/WebAuthnRegisterPage.java index 8f1723f58a0d..425ab0e35c55 100644 --- a/testsuite/integration-arquillian/tests/other/webauthn/src/main/java/org/keycloak/testsuite/webauthn/pages/WebAuthnRegisterPage.java +++ b/testsuite/integration-arquillian/tests/other/webauthn/src/main/java/org/keycloak/testsuite/webauthn/pages/WebAuthnRegisterPage.java @@ -18,7 +18,7 @@ package org.keycloak.testsuite.webauthn.pages; import org.hamcrest.CoreMatchers; -import org.keycloak.testsuite.pages.AbstractPage; +import org.keycloak.testsuite.pages.LogoutSessionsPage; import org.keycloak.testsuite.util.WaitUtils; import org.openqa.selenium.Alert; import org.openqa.selenium.NoSuchElementException; @@ -39,7 +39,7 @@ * Page will be displayed after successful JS call of "navigator.credentials.create", which will register WebAuthn credential * with the browser */ -public class WebAuthnRegisterPage extends AbstractPage { +public class WebAuthnRegisterPage extends LogoutSessionsPage { public static final long ALERT_CHECK_TIMEOUT = 3; //seconds public static final long ALERT_DEFAULT_TIMEOUT = 60; //seconds diff --git a/testsuite/integration-arquillian/tests/other/webauthn/src/test/java/org/keycloak/testsuite/webauthn/AppInitiatedActionWebAuthnTest.java b/testsuite/integration-arquillian/tests/other/webauthn/src/test/java/org/keycloak/testsuite/webauthn/AppInitiatedActionWebAuthnTest.java index e6a5857ad831..44a8ec113625 100644 --- a/testsuite/integration-arquillian/tests/other/webauthn/src/test/java/org/keycloak/testsuite/webauthn/AppInitiatedActionWebAuthnTest.java +++ b/testsuite/integration-arquillian/tests/other/webauthn/src/test/java/org/keycloak/testsuite/webauthn/AppInitiatedActionWebAuthnTest.java @@ -16,6 +16,7 @@ */ package org.keycloak.testsuite.webauthn; +import org.jboss.arquillian.drone.api.annotation.Drone; import org.jboss.arquillian.graphene.page.Page; import org.junit.After; import org.junit.Before; @@ -28,25 +29,35 @@ import org.keycloak.authentication.requiredactions.WebAuthnPasswordlessRegisterFactory; import org.keycloak.authentication.requiredactions.WebAuthnRegisterFactory; import org.keycloak.events.Details; +import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RequiredActionProviderRepresentation; +import org.keycloak.representations.idm.UserSessionRepresentation; import org.keycloak.testsuite.actions.AbstractAppInitiatedActionTest; import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.pages.LoginUsernameOnlyPage; import org.keycloak.testsuite.pages.PasswordPage; +import org.keycloak.testsuite.updaters.RealmAttributeUpdater; import org.keycloak.testsuite.util.FlowUtil; +import org.keycloak.testsuite.util.OAuthClient; +import org.keycloak.testsuite.util.SecondBrowser; import org.keycloak.testsuite.webauthn.authenticators.DefaultVirtualAuthOptions; import org.keycloak.testsuite.webauthn.authenticators.UseVirtualAuthenticators; import org.keycloak.testsuite.webauthn.authenticators.VirtualAuthenticatorManager; import org.keycloak.testsuite.webauthn.pages.WebAuthnRegisterPage; +import org.openqa.selenium.WebDriver; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.function.Supplier; +import java.util.stream.Collectors; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.junit.Assert.assertEquals; import static org.keycloak.models.AuthenticationExecutionModel.Requirement.ALTERNATIVE; import static org.keycloak.models.AuthenticationExecutionModel.Requirement.REQUIRED; import static org.keycloak.testsuite.util.BrowserDriverUtil.isDriverFirefox; @@ -72,6 +83,10 @@ public class AppInitiatedActionWebAuthnTest extends AbstractAppInitiatedActionTe @Page WebAuthnRegisterPage webAuthnRegisterPage; + @Drone + @SecondBrowser + private WebDriver driver2; + @Before @Override public void setUpVirtualAuthenticator() { @@ -150,8 +165,32 @@ public void cancelSetupWebAuthn() { } @Test - public void proceedSetupWebAuthn() { - loginUser(); + public void proceedSetupWebAuthnLogoutOtherSessionsChecked() throws IOException { + testWebAuthnLogoutOtherSessions(true); + } + + @Test + public void proceedSetupWebAuthnLogoutOtherSessionsNotChecked() throws IOException { + testWebAuthnLogoutOtherSessions(false); + } + + private void testWebAuthnLogoutOtherSessions(boolean logoutOtherSessions) throws IOException { + UserResource testUser = testRealm().users().get(findUser(DEFAULT_USERNAME).getId()); + + // perform a login using normal user/password form to have an old session + EventRepresentation event1; + try (RealmAttributeUpdater rau = new RealmAttributeUpdater(testRealm()) + .setBrowserFlow("browser") + .update()) { + OAuthClient oauth2 = new OAuthClient(); + oauth2.init(driver2); + oauth2.doLogin(DEFAULT_USERNAME, DEFAULT_PASSWORD); + event1 = events.expectLogin().assertEvent(); + assertEquals(1, testUser.getUserSessions().size()); + } + + EventRepresentation event2 = loginUser(); + assertEquals(2, testUser.getUserSessions().size()); doAIA(); @@ -163,15 +202,29 @@ public void proceedSetupWebAuthn() { final int credentialsCount = getCredentialCount.get(); webAuthnRegisterPage.assertCurrent(); + if (!logoutOtherSessions) { + webAuthnRegisterPage.uncheckLogoutSessions(); + } + assertThat(webAuthnRegisterPage.isLogoutSessionsChecked(), is(logoutOtherSessions)); webAuthnRegisterPage.clickRegister(); webAuthnRegisterPage.registerWebAuthnCredential("authenticator1"); assertKcActionStatus(SUCCESS); assertThat(getCredentialCount.get(), is(credentialsCount + 1)); + + List sessions = testUser.getUserSessions(); + if (logoutOtherSessions) { + assertThat(sessions.size(), is(1)); + assertThat(sessions.iterator().next().getId(), is(event2.getSessionId())); + } else { + assertThat(sessions.size(), is(2)); + assertThat(sessions.stream().map(UserSessionRepresentation::getId).collect(Collectors.toList()), + containsInAnyOrder(event1.getSessionId(), event2.getSessionId())); + } } - private void loginUser() { + private EventRepresentation loginUser() { usernamePage.open(); usernamePage.assertCurrent(); usernamePage.login(DEFAULT_USERNAME); @@ -179,7 +232,7 @@ private void loginUser() { passwordPage.assertCurrent(); passwordPage.login(DEFAULT_PASSWORD); - events.expectLogin() + return events.expectLogin() .detail(Details.USERNAME, DEFAULT_USERNAME) .assertEvent(); } diff --git a/themes/src/main/resources/theme/base/login/login-config-totp.ftl b/themes/src/main/resources/theme/base/login/login-config-totp.ftl index 80145856d8ee..5b21da49cfe9 100755 --- a/themes/src/main/resources/theme/base/login/login-config-totp.ftl +++ b/themes/src/main/resources/theme/base/login/login-config-totp.ftl @@ -1,4 +1,5 @@ <#import "template.ftl" as layout> +<#import "password-commons.ftl" as passwordCommons> <@layout.registrationLayout displayRequiredFields=false displayMessage=!messagesPerField.existsError('totp','userLabel'); section> <#if section = "header"> @@ -88,6 +89,10 @@ +
+ <@passwordCommons.logoutOtherSessions/> +
+ <#if isAppInitiatedAction??> - \ No newline at end of file + diff --git a/themes/src/main/resources/theme/base/login/login-recovery-authn-code-config.ftl b/themes/src/main/resources/theme/base/login/login-recovery-authn-code-config.ftl index 5bd3559d73ff..ef81710be438 100644 --- a/themes/src/main/resources/theme/base/login/login-recovery-authn-code-config.ftl +++ b/themes/src/main/resources/theme/base/login/login-recovery-authn-code-config.ftl @@ -1,4 +1,5 @@ <#import "template.ftl" as layout> +<#import "password-commons.ftl" as passwordCommons> <@layout.registrationLayout; section> <#if section = "header"> @@ -38,17 +39,18 @@ -
+
- +
-
+ + <@passwordCommons.logoutOtherSessions/> <#if isAppInitiatedAction??> +<#import "password-commons.ftl" as passwordCommons> <@layout.registrationLayout displayMessage=!messagesPerField.existsError('password','password-confirm'); section> <#if section = "header"> ${msg("updatePasswordTitle")} @@ -47,13 +48,7 @@
-
-
-
- -
-
-
+ <@passwordCommons.logoutOtherSessions/>
<#if isAppInitiatedAction??> @@ -66,4 +61,4 @@
- \ No newline at end of file + diff --git a/themes/src/main/resources/theme/base/login/password-commons.ftl b/themes/src/main/resources/theme/base/login/password-commons.ftl new file mode 100644 index 000000000000..233c781d75a8 --- /dev/null +++ b/themes/src/main/resources/theme/base/login/password-commons.ftl @@ -0,0 +1,12 @@ +<#macro logoutOtherSessions> +
+
+
+ +
+
+
+ diff --git a/themes/src/main/resources/theme/base/login/update-email.ftl b/themes/src/main/resources/theme/base/login/update-email.ftl index 4c85e5b5da24..e63b012de560 100644 --- a/themes/src/main/resources/theme/base/login/update-email.ftl +++ b/themes/src/main/resources/theme/base/login/update-email.ftl @@ -1,4 +1,5 @@ <#import "template.ftl" as layout> +<#import "password-commons.ftl" as passwordCommons> <@layout.registrationLayout displayMessage=!messagesPerField.existsError('email'); section> <#if section = "header"> ${msg("updateEmailTitle")} @@ -28,6 +29,8 @@
+ <@passwordCommons.logoutOtherSessions/> +
<#if isAppInitiatedAction??> diff --git a/themes/src/main/resources/theme/base/login/webauthn-register.ftl b/themes/src/main/resources/theme/base/login/webauthn-register.ftl index 90461641fe10..e0eff2256c2c 100644 --- a/themes/src/main/resources/theme/base/login/webauthn-register.ftl +++ b/themes/src/main/resources/theme/base/login/webauthn-register.ftl @@ -1,4 +1,6 @@ <#import "template.ftl" as layout> + <#import "password-commons.ftl" as passwordCommons> + <@layout.registrationLayout; section> <#if section = "title"> title @@ -15,6 +17,7 @@ + <@passwordCommons.logoutOtherSessions/>
@@ -188,4 +191,4 @@ - \ No newline at end of file + From aed55cdeee17ca8690ae5e8d26eb30767e00671b Mon Sep 17 00:00:00 2001 From: Alexander Schwartz Date: Fri, 28 Jul 2023 11:23:03 +0200 Subject: [PATCH 021/135] Fixed updated links for freeipa (#22040) (#22054) Closes #22039 (cherry picked from commit 08dfdffbfbea8ea8b4191cec5ee1926b2a386c99) --- .../server_admin/topics/authentication/kerberos.adoc | 2 +- .../documentation/server_admin/topics/user-federation/sssd.adoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/documentation/server_admin/topics/authentication/kerberos.adoc b/docs/documentation/server_admin/topics/authentication/kerberos.adoc index 5875064f5c28..8627306e43b8 100644 --- a/docs/documentation/server_admin/topics/authentication/kerberos.adoc +++ b/docs/documentation/server_admin/topics/authentication/kerberos.adoc @@ -61,7 +61,7 @@ Ensure the keytab file `/tmp/http.keytab` is accessible on the host where {proje Install a Kerberos client on your machine. .Procedure -. Install a Kerberos client. If your machine runs Fedora, Ubuntu, or RHEL, install the link:https://freeipa.org/page/Downloads[freeipa-client] package, containing a Kerberos client and other utilities. +. Install a Kerberos client. If your machine runs Fedora, Ubuntu, or RHEL, install the link:https://www.freeipa.org/page/Downloads[freeipa-client] package, containing a Kerberos client and other utilities. . Configure the Kerberos client (on Linux, the configuration settings are in the link:https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/krb5_conf.html[/etc/krb5.conf] file ). + Add your Kerberos realm to the configuration and configure the HTTP domains your server runs on. diff --git a/docs/documentation/server_admin/topics/user-federation/sssd.adoc b/docs/documentation/server_admin/topics/user-federation/sssd.adoc index ad3a946c6ddd..56bd160016ff 100644 --- a/docs/documentation/server_admin/topics/user-federation/sssd.adoc +++ b/docs/documentation/server_admin/topics/user-federation/sssd.adoc @@ -18,7 +18,7 @@ image:images/keycloak-sssd-freeipa-integration-overview.png[] ==== FreeIPA/IdM server -The https://quay.io/repository/freeipa/freeipa-server?tab=tags/[FreeIPA Container image] is available at https://quay.io/[Quay.io]. To set up the FreeIPA server, see the https://freeipa.org/page/Quick_Start_Guide[FreeIPA documentation]. +The https://quay.io/repository/freeipa/freeipa-server?tab=tags/[FreeIPA Container image] is available at https://quay.io/[Quay.io]. To set up the FreeIPA server, see the https://www.freeipa.org/page/Quick_Start_Guide[FreeIPA documentation]. .Procedure . Run your FreeIPA server using this command: From 23238b5f8a3f6b5c638f8446e22b7c74287591d6 Mon Sep 17 00:00:00 2001 From: Peter Zaoral Date: Mon, 31 Jul 2023 14:25:43 +0200 Subject: [PATCH 022/135] Hostname guide improvements (#22117) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * changed hostname.adoc Closes #20931 Signed-off-by: Peter Zaoral Co-authored-by: Steven Hawkins , Václav Muzikář --- docs/guides/server/hostname.adoc | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/guides/server/hostname.adoc b/docs/guides/server/hostname.adoc index ae2afa1a3aa0..19e45859c37a 100644 --- a/docs/guides/server/hostname.adoc +++ b/docs/guides/server/hostname.adoc @@ -40,7 +40,7 @@ Most of the time, it should be enough to set the `hostname` option in order to c When using the `hostname` option the server is going to resolve the HTTP scheme, port, and path, automatically so that: * `https` scheme is used unless you set `hostname-strict-https=false` -* Use the standard HTTP ports (e.g.: `80` and `443`) if a `proxy` is set or use the port you set to the `hostname-port` option +* if the `proxy` option is set, the proxy will use the default ports (i.e.: 80 and 443). If the proxy uses a different port, it needs to be specified via the `hostname-port` configuration option However, if you want to set not only the host but also a scheme, port, and path, you can set the `hostname-url` option: @@ -49,6 +49,11 @@ However, if you want to set not only the host but also a scheme, port, and path, This option gives you more flexibility as you can set the different parts of the URL from a single option. Note that the `hostname` and `hostname-url` are mutually exclusive. +[NOTE] +==== +By `hostname` and `proxy` configuration options you affect only the static resources URLs, redirect URIs, OIDC well-known endpoints, etc. In order to change, where/on which port the server actually listens on, you need to use the `http/tls` configuration options (e.g. `http-host`, `https-port`, etc.). For more details, see <@links.server id="enabletls"/> and <@links.server id="all-config"/>. +==== + === Backend The backend endpoints are those accessible through a public domain or through a private network. They are used for a direct communication @@ -70,6 +75,7 @@ The server exposes the administration console and static resources using a speci By default, the URLs for the administration console are also based on the incoming request. However, you can set a specific host or base URL if you want to restrict access to the administration console using a specific URL. Similarly to how you set the frontend URLs, you can use the `hostname-admin` and `hostname-admin-url` options to achieve that. +Note that if HTTPS is enabled (`http-enabled` configuration option is set to false, which is the default setting for the production mode), the Keycloak server automatically assumes you want to use HTTPS URLs. The admin console then tries to contact Keycloak over HTTPS and HTTPS URLs are also used for its configured redirect/web origin URLs. It is not recommended for production, but you can use HTTP URL as `hostname-admin-url` to override this behaviour. Most of the time, it should be enough to set the `hostname-admin` option in order to change only the *host* of the administration console URLs: @@ -122,6 +128,18 @@ In this example, the server is accessible using a port other than the default po .Keycloak configuration: <@kc.start parameters="--hostname-url=https://mykeycloak:8989"/> +=== Exposing Keycloak behind a TLS reencrypt proxy using different ports + +In this example, the server is running behind a proxy and both the server and the proxy are using their own certificates, so the communication between Keycloak and the proxy is encrypted. Because we want the proxy to use its own certificate, the proxy mode `reencrypt` will be used. We need to keep in mind that the proxy configuration options (as well as hostname configuration options) are not changing the ports on which the server actually is listening on (it changes only the ports of static resources like JavaScript and CSS links, OIDC well-known endpoints, redirect URIs, etc.). Therefore, we need to use HTTP configuration options to change the Keycloak server to internally listen on a different port, e.g. 8543. The proxy will be listening on the port 8443 (the port visible while accessing the console via a browser). The example hostname `my-keycloak.org` will be used for the server and similarly the admin console will be accessible via the `admin.my-keycloak.org` subdomain. + +.Keycloak configuration: +<@kc.start parameters="--proxy=reencrypt --https-port=8543 --hostname-url=https://my-keycloak.org:8443 --hostname-admin-url=https://admin.my-keycloak.org:8443"/> + +Note: there is currently no difference between the `passthrough` and `reencrypt` modes. For now, this is meant for future-proof configuration compatibility. The only difference is that when the `edge` proxy mode is used, HTTP is implicitly enabled (again as mentioned above, this does not affect the server behaviour). + +WARNING: Usage any of the proxy modes makes Keycloak rely on Forwarded and X-Forwarded-* headers. +Misconfiguration may leave Keycloak exposed to security issues. For more details, see <@links.server id="reverseproxy"/>. + == Troubleshooting To troubleshoot the hostname configuration, you can use a dedicated debug tool which can be enabled as: From 29d5fc6c49a18428a9262d7826b77a1f0bf5b488 Mon Sep 17 00:00:00 2001 From: mposolda Date: Thu, 27 Jul 2023 14:15:31 +0200 Subject: [PATCH 023/135] Fix authenticatorConfig for javascript providers Closes #20005 (cherry picked from commit 6f6b5e8e84b7acf4323d108da95ce9d79169a7fe) --- .../exportimport/util/ExportUtils.java | 2 +- .../datastore/LegacyExportImportManager.java | 11 +- .../map/datastore/MapExportImportManager.java | 11 +- .../DeployedConfigurationsManager.java | 78 +++++++++++++ .../DeployedConfigurationsProvider.java | 38 +++++++ ...DeployedConfigurationsProviderFactory.java | 28 +++++ .../deployment/DeployedConfigurationsSpi.java | 50 +++++++++ .../models/utils/ModelToRepresentation.java | 15 +-- .../models/utils/RepresentationToModel.java | 5 +- .../services/org.keycloak.provider.Spi | 1 + .../DeployedScriptAuthenticatorFactory.java | 12 ++ ...DefaultDeployedConfigurationsProvider.java | 51 +++++++++ ...DeployedConfigurationsProviderFactory.java | 62 ++++++++++ .../AuthenticationManagementResource.java | 106 +++++++++++------- ...ment.DeployedConfigurationsProviderFactory | 20 ++++ .../rest/TestingResourceProvider.java | 4 +- .../DeployedScriptAuthenticatorTest.java | 57 ++++++++++ .../org/keycloak/testsuite/util/FlowUtil.java | 12 +- 18 files changed, 497 insertions(+), 66 deletions(-) create mode 100644 server-spi-private/src/main/java/org/keycloak/deployment/DeployedConfigurationsManager.java create mode 100644 server-spi-private/src/main/java/org/keycloak/deployment/DeployedConfigurationsProvider.java create mode 100644 server-spi-private/src/main/java/org/keycloak/deployment/DeployedConfigurationsProviderFactory.java create mode 100644 server-spi-private/src/main/java/org/keycloak/deployment/DeployedConfigurationsSpi.java create mode 100644 services/src/main/java/org/keycloak/deployment/DefaultDeployedConfigurationsProvider.java create mode 100644 services/src/main/java/org/keycloak/deployment/DefaultDeployedConfigurationsProviderFactory.java create mode 100644 services/src/main/resources/META-INF/services/org.keycloak.deployment.DeployedConfigurationsProviderFactory diff --git a/model/legacy-private/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/model/legacy-private/src/main/java/org/keycloak/exportimport/util/ExportUtils.java index 8ab3fed0f63c..53776099a1a5 100755 --- a/model/legacy-private/src/main/java/org/keycloak/exportimport/util/ExportUtils.java +++ b/model/legacy-private/src/main/java/org/keycloak/exportimport/util/ExportUtils.java @@ -75,7 +75,7 @@ public static RealmRepresentation exportRealm(KeycloakSession session, RealmMode public static RealmRepresentation exportRealm(KeycloakSession session, RealmModel realm, ExportOptions options, boolean internal) { RealmRepresentation rep = ModelToRepresentation.toRepresentation(session, realm, internal); - ModelToRepresentation.exportAuthenticationFlows(realm, rep); + ModelToRepresentation.exportAuthenticationFlows(session, realm, rep); ModelToRepresentation.exportRequiredActions(realm, rep); // Project/product version diff --git a/model/legacy-private/src/main/java/org/keycloak/storage/datastore/LegacyExportImportManager.java b/model/legacy-private/src/main/java/org/keycloak/storage/datastore/LegacyExportImportManager.java index 44f04a3e1755..6fe1126bb192 100644 --- a/model/legacy-private/src/main/java/org/keycloak/storage/datastore/LegacyExportImportManager.java +++ b/model/legacy-private/src/main/java/org/keycloak/storage/datastore/LegacyExportImportManager.java @@ -21,6 +21,7 @@ import org.keycloak.common.enums.SslRequired; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.component.ComponentModel; +import org.keycloak.deployment.DeployedConfigurationsManager; import org.keycloak.exportimport.ExportAdapter; import org.keycloak.exportimport.ExportOptions; import org.keycloak.exportimport.util.ExportUtils; @@ -301,7 +302,7 @@ public void importRealm(RealmRepresentation rep, RealmModel newRealm, boolean sk updateParSettings(rep, newRealm); - Map mappedFlows = importAuthenticationFlows(newRealm, rep); + Map mappedFlows = importAuthenticationFlows(session, newRealm, rep); if (rep.getRequiredActions() != null) { for (RequiredActionProviderRepresentation action : rep.getRequiredActions()) { RequiredActionProviderModel model = toModel(action); @@ -1251,7 +1252,7 @@ private static WebAuthnPolicy getWebAuthnPolicyPasswordless(RealmRepresentation return webAuthnPolicy; } - public static Map importAuthenticationFlows(RealmModel newRealm, RealmRepresentation rep) { + public static Map importAuthenticationFlows(KeycloakSession session, RealmModel newRealm, RealmRepresentation rep) { Map mappedFlows = new HashMap<>(); if (rep.getAuthenticationFlows() == null) { // assume this is an old version being imported @@ -1279,7 +1280,7 @@ public static Map importAuthenticationFlows(RealmModel newRealm, for (AuthenticationFlowRepresentation flowRep : rep.getAuthenticationFlows()) { AuthenticationFlowModel model = newRealm.getFlowByAlias(flowRep.getAlias()); for (AuthenticationExecutionExportRepresentation exeRep : flowRep.getAuthenticationExecutions()) { - AuthenticationExecutionModel execution = toModel(newRealm, model, exeRep); + AuthenticationExecutionModel execution = toModel(session, newRealm, model, exeRep); newRealm.addAuthenticatorExecution(execution); } } @@ -1365,10 +1366,10 @@ public static Map importAuthenticationFlows(RealmModel newRealm, return mappedFlows; } - private static AuthenticationExecutionModel toModel(RealmModel realm, AuthenticationFlowModel parentFlow, AuthenticationExecutionExportRepresentation rep) { + private static AuthenticationExecutionModel toModel(KeycloakSession session, RealmModel realm, AuthenticationFlowModel parentFlow, AuthenticationExecutionExportRepresentation rep) { AuthenticationExecutionModel model = new AuthenticationExecutionModel(); if (rep.getAuthenticatorConfig() != null) { - AuthenticatorConfigModel config = realm.getAuthenticatorConfigByAlias(rep.getAuthenticatorConfig()); + AuthenticatorConfigModel config = new DeployedConfigurationsManager(session).getAuthenticatorConfigByAlias(realm, rep.getAuthenticatorConfig()); model.setAuthenticatorConfig(config.getId()); } model.setAuthenticator(rep.getAuthenticator()); diff --git a/model/map/src/main/java/org/keycloak/models/map/datastore/MapExportImportManager.java b/model/map/src/main/java/org/keycloak/models/map/datastore/MapExportImportManager.java index 4a40d7155f18..a7eaf0785ad3 100644 --- a/model/map/src/main/java/org/keycloak/models/map/datastore/MapExportImportManager.java +++ b/model/map/src/main/java/org/keycloak/models/map/datastore/MapExportImportManager.java @@ -24,6 +24,7 @@ import org.keycloak.common.enums.SslRequired; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.component.ComponentModel; +import org.keycloak.deployment.DeployedConfigurationsManager; import org.keycloak.models.AdminRoles; import org.keycloak.models.ClientProvider; import org.keycloak.models.ClientScopeProvider; @@ -308,7 +309,7 @@ public void importRealm(RealmRepresentation rep, RealmModel newRealm, boolean sk updateParSettings(rep, newRealm); - Map mappedFlows = importAuthenticationFlows(newRealm, rep); + Map mappedFlows = importAuthenticationFlows(session, newRealm, rep); if (rep.getRequiredActions() != null) { for (RequiredActionProviderRepresentation action : rep.getRequiredActions()) { RequiredActionProviderModel model = toModel(action); @@ -1455,7 +1456,7 @@ private static WebAuthnPolicy getWebAuthnPolicyPasswordless(RealmRepresentation return webAuthnPolicy; } - public static Map importAuthenticationFlows(RealmModel newRealm, RealmRepresentation rep) { + public static Map importAuthenticationFlows(KeycloakSession session, RealmModel newRealm, RealmRepresentation rep) { Map mappedFlows = new HashMap<>(); if (rep.getAuthenticationFlows() == null) { // assume this is an old version being imported @@ -1483,7 +1484,7 @@ public static Map importAuthenticationFlows(RealmModel newRealm, for (AuthenticationFlowRepresentation flowRep : rep.getAuthenticationFlows()) { AuthenticationFlowModel model = newRealm.getFlowByAlias(flowRep.getAlias()); for (AuthenticationExecutionExportRepresentation exeRep : flowRep.getAuthenticationExecutions()) { - AuthenticationExecutionModel execution = toModel(newRealm, model, exeRep); + AuthenticationExecutionModel execution = toModel(session, newRealm, model, exeRep); newRealm.addAuthenticatorExecution(execution); } } @@ -1569,10 +1570,10 @@ public static Map importAuthenticationFlows(RealmModel newRealm, return mappedFlows; } - private static AuthenticationExecutionModel toModel(RealmModel realm, AuthenticationFlowModel parentFlow, AuthenticationExecutionExportRepresentation rep) { + private static AuthenticationExecutionModel toModel(KeycloakSession session, RealmModel realm, AuthenticationFlowModel parentFlow, AuthenticationExecutionExportRepresentation rep) { AuthenticationExecutionModel model = new AuthenticationExecutionModel(); if (rep.getAuthenticatorConfig() != null) { - AuthenticatorConfigModel config = realm.getAuthenticatorConfigByAlias(rep.getAuthenticatorConfig()); + AuthenticatorConfigModel config = new DeployedConfigurationsManager(session).getAuthenticatorConfigByAlias(realm, rep.getAuthenticatorConfig()); model.setAuthenticatorConfig(config.getId()); } model.setAuthenticator(rep.getAuthenticator()); diff --git a/server-spi-private/src/main/java/org/keycloak/deployment/DeployedConfigurationsManager.java b/server-spi-private/src/main/java/org/keycloak/deployment/DeployedConfigurationsManager.java new file mode 100644 index 000000000000..211b33644787 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/deployment/DeployedConfigurationsManager.java @@ -0,0 +1,78 @@ +/* + * Copyright 2023 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.keycloak.deployment; + +import org.jboss.logging.Logger; +import org.keycloak.models.AuthenticatorConfigModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; + +/** + * Allows to CRUD for configurations (like Authenticator configs). Those are typically saved in the store (realm), but can be also + * deployed and hence not saved in the DB + * + * @author Marek Posolda + */ +public class DeployedConfigurationsManager { + + private static final Logger log = Logger.getLogger(DeployedConfigurationsManager.class); + + private final KeycloakSession session; + + public DeployedConfigurationsManager(KeycloakSession session) { + this.session = session; + } + + public void registerDeployedAuthenticatorConfig(AuthenticatorConfigModel model) { + log.debugf("Register deployed authenticator config: %s", model.getId()); + session.getProvider(DeployedConfigurationsProvider.class).registerDeployedAuthenticatorConfig(model); + } + + public AuthenticatorConfigModel getDeployedAuthenticatorConfig(String configId) { + return session.getProvider(DeployedConfigurationsProvider.class).getDeployedAuthenticatorConfigs() + .filter(config -> configId.equals(config.getId())) + .findFirst().orElse(null); + } + + public AuthenticatorConfigModel getAuthenticatorConfig(RealmModel realm, String configId) { + AuthenticatorConfigModel cfgModel = getDeployedAuthenticatorConfig(configId); + if (cfgModel != null) { + log.tracef("Found deployed configuration by id: %s", configId); + return cfgModel; + } else { + return realm.getAuthenticatorConfigById(configId); + } + } + + + public AuthenticatorConfigModel getAuthenticatorConfigByAlias(RealmModel realm, String alias) { + if (alias == null) return null; + AuthenticatorConfigModel cfgModel = session.getProvider(DeployedConfigurationsProvider.class).getDeployedAuthenticatorConfigs() + .filter(config -> alias.equals(config.getAlias())) + .findFirst().orElse(null); + if (cfgModel != null) { + log.debugf("Found deployed configuration by alias: %s", alias); + return cfgModel; + } else { + return realm.getAuthenticatorConfigByAlias(alias); + } + } + +} diff --git a/server-spi-private/src/main/java/org/keycloak/deployment/DeployedConfigurationsProvider.java b/server-spi-private/src/main/java/org/keycloak/deployment/DeployedConfigurationsProvider.java new file mode 100644 index 000000000000..8de1a2b34042 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/deployment/DeployedConfigurationsProvider.java @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.keycloak.deployment; + +import java.util.stream.Stream; + +import org.keycloak.models.AuthenticatorConfigModel; +import org.keycloak.provider.Provider; + +/** + * Allows to register "deployed configurations", which are retrieved in runtime from deployed providers and hence are not saved in the DB + * + * @author Marek Posolda + */ +public interface DeployedConfigurationsProvider extends Provider { + + void registerDeployedAuthenticatorConfig(AuthenticatorConfigModel model); + + Stream getDeployedAuthenticatorConfigs(); + +} diff --git a/server-spi-private/src/main/java/org/keycloak/deployment/DeployedConfigurationsProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/deployment/DeployedConfigurationsProviderFactory.java new file mode 100644 index 000000000000..18f86a3d2576 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/deployment/DeployedConfigurationsProviderFactory.java @@ -0,0 +1,28 @@ +/* + * Copyright 2023 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.keycloak.deployment; + +import org.keycloak.provider.ProviderFactory; + +/** + * @author Marek Posolda + */ +public interface DeployedConfigurationsProviderFactory extends ProviderFactory { +} diff --git a/server-spi-private/src/main/java/org/keycloak/deployment/DeployedConfigurationsSpi.java b/server-spi-private/src/main/java/org/keycloak/deployment/DeployedConfigurationsSpi.java new file mode 100644 index 000000000000..cd355841dd3e --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/deployment/DeployedConfigurationsSpi.java @@ -0,0 +1,50 @@ +/* + * Copyright 2023 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.keycloak.deployment; + +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.provider.Spi; + +/** + * @author Marek Posolda + */ +public class DeployedConfigurationsSpi implements Spi { + + @Override + public boolean isInternal() { + return true; + } + + @Override + public String getName() { + return "deployed-configurations"; + } + + @Override + public Class getProviderClass() { + return DeployedConfigurationsProvider.class; + } + + @Override + public Class getProviderFactoryClass() { + return DeployedConfigurationsProviderFactory.class; + } +} diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index df514a1cb39d..3573a28c2112 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -35,6 +35,7 @@ import org.keycloak.component.ComponentModel; import org.keycloak.credential.CredentialMetadata; import org.keycloak.credential.CredentialModel; +import org.keycloak.deployment.DeployedConfigurationsManager; import org.keycloak.events.Event; import org.keycloak.events.admin.AdminEvent; import org.keycloak.events.admin.AuthDetails; @@ -546,7 +547,7 @@ public static RealmRepresentation toRepresentation(KeycloakSession session, Real rep.setSupportedLocales(realm.getSupportedLocalesStream().collect(Collectors.toSet())); rep.setDefaultLocale(realm.getDefaultLocale()); if (internal) { - exportAuthenticationFlows(realm, rep); + exportAuthenticationFlows(session, realm, rep); exportRequiredActions(realm, rep); exportGroups(realm, rep); } @@ -585,10 +586,10 @@ public static void exportGroups(RealmModel realm, RealmRepresentation rep) { rep.setGroups(toGroupHierarchy(realm, true).collect(Collectors.toList())); } - public static void exportAuthenticationFlows(RealmModel realm, RealmRepresentation rep) { + public static void exportAuthenticationFlows(KeycloakSession session, RealmModel realm, RealmRepresentation rep) { List authenticationFlows = realm.getAuthenticationFlowsStream() .sorted(AuthenticationFlowModel.AuthenticationFlowComparator.SINGLETON) - .map(flow -> toRepresentation(realm, flow)) + .map(flow -> toRepresentation(session, realm, flow)) .collect(Collectors.toList()); rep.setAuthenticationFlows(authenticationFlows); @@ -873,7 +874,7 @@ public static UserConsentRepresentation toRepresentation(UserConsentModel model) return consentRep; } - public static AuthenticationFlowRepresentation toRepresentation(RealmModel realm, AuthenticationFlowModel model) { + public static AuthenticationFlowRepresentation toRepresentation(KeycloakSession session, RealmModel realm, AuthenticationFlowModel model) { AuthenticationFlowRepresentation rep = new AuthenticationFlowRepresentation(); rep.setId(model.getId()); rep.setBuiltIn(model.isBuiltIn()); @@ -882,14 +883,14 @@ public static AuthenticationFlowRepresentation toRepresentation(RealmModel realm rep.setAlias(model.getAlias()); rep.setDescription(model.getDescription()); rep.setAuthenticationExecutions(realm.getAuthenticationExecutionsStream(model.getId()) - .map(e -> toRepresentation(realm, e)).collect(Collectors.toList())); + .map(e -> toRepresentation(session, realm, e)).collect(Collectors.toList())); return rep; } - public static AuthenticationExecutionExportRepresentation toRepresentation(RealmModel realm, AuthenticationExecutionModel model) { + public static AuthenticationExecutionExportRepresentation toRepresentation(KeycloakSession session, RealmModel realm, AuthenticationExecutionModel model) { AuthenticationExecutionExportRepresentation rep = new AuthenticationExecutionExportRepresentation(); if (model.getAuthenticatorConfig() != null) { - AuthenticatorConfigModel config = realm.getAuthenticatorConfigById(model.getAuthenticatorConfig()); + AuthenticatorConfigModel config = new DeployedConfigurationsManager(session).getAuthenticatorConfig(realm, model.getAuthenticatorConfig()); rep.setAuthenticatorConfig(config.getAlias()); } rep.setAuthenticator(model.getAuthenticator()); diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index 9cb91b0d31f3..025ff00ce346 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -57,6 +57,7 @@ import org.keycloak.common.util.UriUtils; import org.keycloak.component.ComponentModel; import org.keycloak.credential.CredentialModel; +import org.keycloak.deployment.DeployedConfigurationsManager; import org.keycloak.migration.migrators.MigrationUtils; import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.AuthenticationFlowModel; @@ -926,7 +927,7 @@ public static AuthenticationFlowModel toModel(AuthenticationFlowRepresentation r } - public static AuthenticationExecutionModel toModel(RealmModel realm, AuthenticationExecutionRepresentation rep) { + public static AuthenticationExecutionModel toModel(KeycloakSession session, RealmModel realm, AuthenticationExecutionRepresentation rep) { AuthenticationExecutionModel model = new AuthenticationExecutionModel(); model.setId(rep.getId()); model.setFlowId(rep.getFlowId()); @@ -938,7 +939,7 @@ public static AuthenticationExecutionModel toModel(RealmModel realm, Authenticat model.setRequirement(AuthenticationExecutionModel.Requirement.valueOf(rep.getRequirement())); if (rep.getAuthenticatorConfig() != null) { - AuthenticatorConfigModel cfg = realm.getAuthenticatorConfigByAlias(rep.getAuthenticatorConfig()); + AuthenticatorConfigModel cfg = new DeployedConfigurationsManager(session).getAuthenticatorConfigByAlias(realm, rep.getAuthenticatorConfig()); model.setAuthenticatorConfig(cfg.getId()); } return model; diff --git a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi index 4920730d77b0..8e9c90c5beaa 100755 --- a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -62,6 +62,7 @@ org.keycloak.authentication.otp.OTPApplicationSpi org.keycloak.authorization.policy.provider.PolicySpi org.keycloak.authorization.store.StoreFactorySpi org.keycloak.authorization.AuthorizationSpi +org.keycloak.deployment.DeployedConfigurationsSpi org.keycloak.models.cache.authorization.CachedStoreFactorySpi org.keycloak.protocol.oidc.TokenExchangeSpi org.keycloak.protocol.oidc.TokenIntrospectionSpi diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/DeployedScriptAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/DeployedScriptAuthenticatorFactory.java index 04b9ded43c8b..3615758a850f 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/DeployedScriptAuthenticatorFactory.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/DeployedScriptAuthenticatorFactory.java @@ -24,9 +24,14 @@ import org.keycloak.authentication.AuthenticationFlowContext; import org.keycloak.authentication.Authenticator; import org.keycloak.common.Profile; +import org.keycloak.deployment.DeployedConfigurationsManager; import org.keycloak.models.AuthenticatorConfigModel; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.models.utils.PostMigrationEvent; import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.provider.ProviderEvent; import org.keycloak.representations.provider.ScriptProviderMetadata; /** @@ -88,6 +93,13 @@ public void init(Config.Scope config) { configProperties = super.getConfigProperties(); } + @Override + public void postInit(KeycloakSessionFactory factory) { + KeycloakModelUtils.runJobInTransaction(factory, session -> { + new DeployedConfigurationsManager(session).registerDeployedAuthenticatorConfig(model); + }); + } + @Override public List getConfigProperties() { return configProperties; diff --git a/services/src/main/java/org/keycloak/deployment/DefaultDeployedConfigurationsProvider.java b/services/src/main/java/org/keycloak/deployment/DefaultDeployedConfigurationsProvider.java new file mode 100644 index 000000000000..f1638da15b4b --- /dev/null +++ b/services/src/main/java/org/keycloak/deployment/DefaultDeployedConfigurationsProvider.java @@ -0,0 +1,51 @@ +/* + * Copyright 2023 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.keycloak.deployment; + +import java.util.Map; +import java.util.stream.Stream; + +import org.keycloak.models.AuthenticatorConfigModel; + +/** + * @author Marek Posolda + */ +public class DefaultDeployedConfigurationsProvider implements DeployedConfigurationsProvider { + + private final Map deployedAuthenticatorConfigs; + public DefaultDeployedConfigurationsProvider(Map deployedAuthenticatorConfigs) { + this.deployedAuthenticatorConfigs = deployedAuthenticatorConfigs; + } + + @Override + public void registerDeployedAuthenticatorConfig(AuthenticatorConfigModel model) { + deployedAuthenticatorConfigs.put(model.getId(), model); + } + + @Override + public Stream getDeployedAuthenticatorConfigs() { + return deployedAuthenticatorConfigs.values().stream(); + } + + @Override + public void close() { + + } +} diff --git a/services/src/main/java/org/keycloak/deployment/DefaultDeployedConfigurationsProviderFactory.java b/services/src/main/java/org/keycloak/deployment/DefaultDeployedConfigurationsProviderFactory.java new file mode 100644 index 000000000000..20dc7922ae00 --- /dev/null +++ b/services/src/main/java/org/keycloak/deployment/DefaultDeployedConfigurationsProviderFactory.java @@ -0,0 +1,62 @@ +/* + * Copyright 2023 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.keycloak.deployment; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; + +import org.keycloak.Config; +import org.keycloak.models.AuthenticatorConfigModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; + +/** + * @author Marek Posolda + */ +public class DefaultDeployedConfigurationsProviderFactory implements DeployedConfigurationsProviderFactory { + + public static final String PROVIDER_ID = "default"; + private final Map deployedAuthenticatorConfigs = new ConcurrentHashMap<>(); + @Override + public DeployedConfigurationsProvider create(KeycloakSession session) { + return new DefaultDeployedConfigurationsProvider(deployedAuthenticatorConfigs); + } + + @Override + public void init(Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } + + @Override + public void close() { + + } + + @Override + public String getId() { + return PROVIDER_ID; + } +} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java index 86df1b3fa5e6..883d18c1b535 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java @@ -33,11 +33,13 @@ import org.keycloak.authentication.FormAuthenticator; import org.keycloak.authentication.RequiredActionFactory; import org.keycloak.authentication.RequiredActionProvider; +import org.keycloak.deployment.DeployedConfigurationsManager; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.AuthenticationFlowModel; import org.keycloak.models.AuthenticatorConfigModel; +import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RequiredActionProviderModel; @@ -205,7 +207,7 @@ public Stream getFlows() { return realm.getAuthenticationFlowsStream() .filter(flow -> flow.isTopLevel() && !Objects.equals(flow.getAlias(), DefaultAuthenticationFlows.SAML_ECP_FLOW)) - .map(flow -> ModelToRepresentation.toRepresentation(realm, flow)); + .map(flow -> ModelToRepresentation.toRepresentation(session, realm, flow)); } /** @@ -264,7 +266,7 @@ public AuthenticationFlowRepresentation getFlow(@Parameter(description = "Flow i if (flow == null) { throw new NotFoundException("Could not find flow with id"); } - return ModelToRepresentation.toRepresentation(realm, flow); + return ModelToRepresentation.toRepresentation(session, realm, flow); } /** @@ -375,7 +377,7 @@ public Response copy(@Parameter(description="name of the existing authentication logger.debug("flow not found: " + flowAlias); return Response.status(NOT_FOUND).build(); } - AuthenticationFlowModel copy = copyFlow(realm, flow, newName); + AuthenticationFlowModel copy = copyFlow(session, realm, flow, newName); data.put("id", copy.getId()); adminEvent.operation(OperationType.CREATE).resourcePath(session.getContext().getUri()).representation(data).success(); @@ -384,7 +386,7 @@ public Response copy(@Parameter(description="name of the existing authentication } - public static AuthenticationFlowModel copyFlow(RealmModel realm, AuthenticationFlowModel flow, String newName) { + public static AuthenticationFlowModel copyFlow(KeycloakSession session, RealmModel realm, AuthenticationFlowModel flow, String newName) { AuthenticationFlowModel copy = new AuthenticationFlowModel(); copy.setAlias(newName); copy.setDescription(flow.getDescription()); @@ -392,11 +394,11 @@ public static AuthenticationFlowModel copyFlow(RealmModel realm, AuthenticationF copy.setBuiltIn(false); copy.setTopLevel(flow.isTopLevel()); copy = realm.addAuthenticationFlow(copy); - copy(realm, newName, flow, copy); + copy(session, realm, newName, flow, copy); return copy; } - public static void copy(RealmModel realm, String newName, AuthenticationFlowModel from, AuthenticationFlowModel to) { + public static void copy(KeycloakSession session, RealmModel realm, String newName, AuthenticationFlowModel from, AuthenticationFlowModel to) { realm.getAuthenticationExecutionsStream(from.getId()).forEachOrdered(execution -> { if (execution.isAuthenticatorFlow()) { AuthenticationFlowModel subFlow = realm.getAuthenticationFlowById(execution.getFlowId()); @@ -408,26 +410,32 @@ public static void copy(RealmModel realm, String newName, AuthenticationFlowMode copy.setTopLevel(false); copy = realm.addAuthenticationFlow(copy); execution.setFlowId(copy.getId()); - copy(realm, newName, subFlow, copy); + copy(session, realm, newName, subFlow, copy); } if (execution.getAuthenticatorConfig() != null) { - AuthenticatorConfigModel config = realm.getAuthenticatorConfigById(execution.getAuthenticatorConfig()); + DeployedConfigurationsManager configManager = new DeployedConfigurationsManager(session); + AuthenticatorConfigModel config = configManager.getAuthenticatorConfig(realm, execution.getAuthenticatorConfig()); if (config == null) { logger.debugf("Authentication execution with id [%s] not found", config.getId()); throw new IllegalStateException("Authentication execution configuration not found"); } - config.setId(null); + if (configManager.getDeployedAuthenticatorConfig(execution.getAuthenticatorConfig()) != null) { + // Shared configuration of deployed provider + execution.setAuthenticatorConfig(config.getId()); + } else { + config.setId(null); - if (config.getAlias() != null) { - config.setAlias(newName + " " + config.getAlias()); - } + if (config.getAlias() != null) { + config.setAlias(newName + " " + config.getAlias()); + } - AuthenticatorConfigModel newConfig = realm.addAuthenticatorConfig(config); + AuthenticatorConfigModel newConfig = realm.addAuthenticatorConfig(config); - execution.setAuthenticatorConfig(newConfig.getId()); + execution.setAuthenticatorConfig(newConfig.getId()); + } } execution.setId(null); @@ -524,17 +532,7 @@ public Response addExecutionToFlow(@Parameter(description = "Alias of parent flo String provider = data.get("provider"); // make sure provider is one of the registered providers - ProviderFactory f; - if (parentFlow.getProviderId().equals(AuthenticationFlow.CLIENT_FLOW)) { - f = session.getKeycloakSessionFactory().getProviderFactory(ClientAuthenticator.class, provider); - } else if (parentFlow.getProviderId().equals(AuthenticationFlow.FORM_FLOW)) { - f = session.getKeycloakSessionFactory().getProviderFactory(FormAction.class, provider); - } else { - f = session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, provider); - } - if (f == null) { - throw new BadRequestException("No authentication provider found for id: " + provider); - } + ProviderFactory f = getProviderFactory( parentFlow, provider); AuthenticationExecutionModel execution = new AuthenticationExecutionModel(); execution.setParentFlow(parentFlow.getId()); @@ -551,24 +549,45 @@ public Response addExecutionToFlow(@Parameter(description = "Alias of parent flo execution = realm.addAuthenticatorExecution(execution); + checkConfigForDeployedProvider(f, execution); + + data.put("id", execution.getId()); + adminEvent.operation(OperationType.CREATE).resource(ResourceType.AUTH_EXECUTION).resourcePath(session.getContext().getUri()).representation(data).success(); + + String addExecutionPathSegment = UriBuilder.fromMethod(AuthenticationManagementResource.class, "addExecutionToFlow").build(parentFlow.getAlias()).getPath(); + return Response.created(session.getContext().getUri().getBaseUriBuilder().path(session.getContext().getUri().getPath().replace(addExecutionPathSegment, "")).path("executions").path(execution.getId()).build()).build(); + } + + private ProviderFactory getProviderFactory(AuthenticationFlowModel parentFlow, String provider) { + ProviderFactory f = null; + if (parentFlow.getProviderId().equals(AuthenticationFlow.CLIENT_FLOW)) { + f = session.getKeycloakSessionFactory().getProviderFactory(ClientAuthenticator.class, provider); + } else if (parentFlow.getProviderId().equals(AuthenticationFlow.FORM_FLOW)) { + f = session.getKeycloakSessionFactory().getProviderFactory(FormAction.class, provider); + } else { + f = session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, provider); + } + if (f == null) { + throw new BadRequestException("No authentication provider found for id: " + provider); + } + return f; + } + + + private void checkConfigForDeployedProvider(ProviderFactory f, AuthenticationExecutionModel execution) { if (f instanceof ConfiguredProvider) { ConfiguredProvider internalProviderFactory = (ConfiguredProvider) f; AuthenticatorConfigModel config = internalProviderFactory.getConfig(); if (config != null) { - // creates a default configuration if the factory defines one + // use a default configuration if the factory defines one + // Assumption is that this is registered in DeployedConfigurationsProvider // useful for internal providers that already provide a built-in configuration - AuthenticatorConfigRepresentation configRepresentation = ModelToRepresentation.toRepresentation( - config); - newExecutionConfig(execution.getId(), configRepresentation).close(); + logger.tracef("Updating execution of provider '%s' with shared configuration.", execution.getAuthenticator()); + execution.setAuthenticatorConfig(config.getId()); + realm.updateAuthenticatorExecution(execution); } } - - data.put("id", execution.getId()); - adminEvent.operation(OperationType.CREATE).resource(ResourceType.AUTH_EXECUTION).resourcePath(session.getContext().getUri()).representation(data).success(); - - String addExecutionPathSegment = UriBuilder.fromMethod(AuthenticationManagementResource.class, "addExecutionToFlow").build(parentFlow.getAlias()).getPath(); - return Response.created(session.getContext().getUri().getBaseUriBuilder().path(session.getContext().getUri().getPath().replace(addExecutionPathSegment, "")).path("executions").path(execution.getId()).build()).build(); } /** @@ -649,7 +668,7 @@ public void recurseExecutions(AuthenticationFlowModel flow, List scriptExecution = new HashMap<>(); + scriptExecution.put("provider", "script-authenticator-a.js"); + + // It should be possible to add another script-authenticator to the flow + authMgmtResource.addExecution("scriptBrowser", scriptExecution); + + List executions = authMgmtResource.getExecutions("scriptBrowser"); + List scriptExecutions = executions.stream() + .filter(execution -> execution.getDisplayName().equals("My Authenticator")) + .collect(Collectors.toList()); + + // Both executions refers to same config of deployed script provider + Assert.assertEquals(2, scriptExecutions.size()); + for (AuthenticationExecutionInfoRepresentation execution : scriptExecutions) { + Assert.assertEquals(execution.getAuthenticationConfig(), "script-authenticator-a.js"); + } + + // Assert updating config should fail due it's read-only + AuthenticatorConfigRepresentation configRep = authMgmtResource.getAuthenticatorConfig("script-authenticator-a.js"); + configRep.getConfig().put("scriptCode", "Something"); + try { + authMgmtResource.updateAuthenticatorConfig("script-authenticator-a.js", configRep); + Assert.fail("Update of configuration should have failed"); + } catch (BadRequestException bre) { + // Expected + } + + // Test copy flow is OK + Map newFlow = new HashMap<>(); + newFlow.put("newName", "Copy of script flow"); + Response resp = authMgmtResource.copy("scriptBrowser", newFlow); + Assert.assertEquals(201, resp.getStatus()); + resp.close(); + AuthenticationFlowRepresentation copiedFlow = AbstractAuthenticationTest.findFlowByAlias("Copy of script flow", authMgmtResource.getFlows()); + + // Cleanup + authMgmtResource.deleteFlow(copiedFlow.getId()); + authMgmtResource.removeExecution(scriptExecutions.get(1).getId()); + } + private UserRepresentation okayUser() { return adminClient.realm(TEST_REALM_NAME).users().search("user", true).get(0); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/FlowUtil.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/FlowUtil.java index 6c47e75754f4..2cd8844babd6 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/FlowUtil.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/FlowUtil.java @@ -27,7 +27,8 @@ import static org.keycloak.models.utils.DefaultAuthenticationFlows.RESET_CREDENTIALS_FLOW; public class FlowUtil { - private RealmModel realm; + private final KeycloakSession session; + private final RealmModel realm; private AuthenticationFlowModel currentFlow; private String flowAlias; private int maxPriority = 0; @@ -42,7 +43,8 @@ public FlowUtilException(String message) { } } - public FlowUtil(RealmModel realm) { + private FlowUtil(KeycloakSession session, RealmModel realm) { + this.session = session; this.realm = realm; } @@ -55,11 +57,11 @@ public AuthenticationFlowModel build() { } public static FlowUtil inCurrentRealm(KeycloakSession session) { - return new FlowUtil(session.getContext().getRealm()); + return new FlowUtil(session, session.getContext().getRealm()); } private FlowUtil newFlowUtil(AuthenticationFlowModel flowModel) { - FlowUtil subflow = new FlowUtil(realm); + FlowUtil subflow = new FlowUtil(session, realm); subflow.currentFlow = flowModel; return subflow; } @@ -112,7 +114,7 @@ public FlowUtil copyFlow(String original, String newFlowAlias) { realm.removeAuthenticationFlow(foundFlow); } - currentFlow = AuthenticationManagementResource.copyFlow(realm, existingBrowserFlow, newFlowAlias); + currentFlow = AuthenticationManagementResource.copyFlow(session, realm, existingBrowserFlow, newFlowAlias); return this; } From aa8dec57481d100f53d6af4e3e1539f73de6c6d1 Mon Sep 17 00:00:00 2001 From: rmartinc Date: Fri, 28 Jul 2023 16:12:48 +0200 Subject: [PATCH 024/135] Remove option Nerver Expires for tokens in Advanced OIDC client configuration Closes https://github.com/keycloak/keycloak/issues/21927 --- .../topics/keycloak/changes-22_0_2.adoc | 3 ++ .../upgrading/topics/keycloak/changes.adoc | 4 ++ .../src/clients/advanced/AdvancedSettings.tsx | 6 ++- .../src/clients/advanced/TokenLifespan.tsx | 37 +++++++------- .../models/utils/SessionExpirationUtils.java | 48 ++++++++++++------- .../utils/SessionExpirationUtilsTest.java | 16 +++++++ 6 files changed, 76 insertions(+), 38 deletions(-) create mode 100644 docs/documentation/upgrading/topics/keycloak/changes-22_0_2.adoc diff --git a/docs/documentation/upgrading/topics/keycloak/changes-22_0_2.adoc b/docs/documentation/upgrading/topics/keycloak/changes-22_0_2.adoc new file mode 100644 index 000000000000..b32b6681d41d --- /dev/null +++ b/docs/documentation/upgrading/topics/keycloak/changes-22_0_2.adoc @@ -0,0 +1,3 @@ += Never expires option removed from client advanced settings combos + +The option `Never expires` is now removed from all the combos of the Advanced Settings client tab. This option was misleading because the different lifespans or idle timeouts were never infinite, but limited by the general user session or realm values. Therefore, this option is removed in favor of the other two remaining options: `Inherits from the realm settings` (the client uses general realm timeouts) and `Expires in` (the value is overriden for the client). Internally the `Never expires` was represented by `-1`. Now that value is shown with a warning in the Admin Console and cannot be set directly by the administrator. diff --git a/docs/documentation/upgrading/topics/keycloak/changes.adoc b/docs/documentation/upgrading/topics/keycloak/changes.adoc index 229c7bf9039b..2d8651f1adf5 100644 --- a/docs/documentation/upgrading/topics/keycloak/changes.adoc +++ b/docs/documentation/upgrading/topics/keycloak/changes.adoc @@ -1,5 +1,9 @@ == Migration Changes +=== Migrating to 22.0.2 + +include::changes-22_0_2.adoc[leveloffset=3] + === Migrating to 22.0.0 include::changes-22_0_0.adoc[leveloffset=3] diff --git a/js/apps/admin-ui/src/clients/advanced/AdvancedSettings.tsx b/js/apps/admin-ui/src/clients/advanced/AdvancedSettings.tsx index 661ee7a555cf..34cf3e0a410d 100644 --- a/js/apps/admin-ui/src/clients/advanced/AdvancedSettings.tsx +++ b/js/apps/admin-ui/src/clients/advanced/AdvancedSettings.tsx @@ -127,7 +127,11 @@ export const AdvancedSettings = ({ name={convertAttributeNameToForm( "attributes.client.offline.session.max.lifespan" )} - defaultValue={realm?.offlineSessionMaxLifespan} + defaultValue={ + realm?.offlineSessionMaxLifespanEnabled + ? realm?.offlineSessionMaxLifespan + : undefined + } units={["minute", "day", "hour"]} /> diff --git a/js/apps/admin-ui/src/clients/advanced/TokenLifespan.tsx b/js/apps/admin-ui/src/clients/advanced/TokenLifespan.tsx index 859ba5dd94f4..738e8a566b9f 100644 --- a/js/apps/admin-ui/src/clients/advanced/TokenLifespan.tsx +++ b/js/apps/admin-ui/src/clients/advanced/TokenLifespan.tsx @@ -24,7 +24,6 @@ type TokenLifespanProps = { }; const inherited = "tokenLifespan.inherited"; -const never = "tokenLifespan.never"; const expires = "tokenLifespan.expires"; export const TokenLifespan = ({ @@ -42,8 +41,8 @@ export const TokenLifespan = ({ const { control } = useFormContext(); const isExpireSet = (value: string | number) => - (typeof value === "number" && value !== -1) || - (typeof value === "string" && value !== "" && value !== "-1") || + typeof value === "number" || + (typeof value === "string" && value !== "") || focused; return ( @@ -73,30 +72,28 @@ export const TokenLifespan = ({ setOpen(false); }} selections={[ - isExpireSet(field.value) - ? t(expires) - : field.value === "" - ? t(inherited) - : t(never), + isExpireSet(field.value) ? t(expires) : t(inherited), ]} > {t(inherited)} - {t(never)} {t(expires)} - {field.value !== "-1" && field.value !== -1 && ( - - )} + )} diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/SessionExpirationUtils.java b/server-spi-private/src/main/java/org/keycloak/models/utils/SessionExpirationUtils.java index 66ea74a86f14..0186617a1ccf 100644 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/SessionExpirationUtils.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/SessionExpirationUtils.java @@ -21,6 +21,7 @@ import org.keycloak.models.Constants; import org.keycloak.models.RealmModel; import org.keycloak.protocol.oidc.OIDCConfigAttributes; +import org.keycloak.utils.StringUtil; /** *

Shared methods to calculate the session expiration and idle.

@@ -92,30 +93,29 @@ public static long calculateClientSessionMaxLifespanTimestamp(boolean offline, b long clientSessionCreated, long userSessionCreated, RealmModel realm, ClientModel client) { long timestamp = -1; if (offline) { - if (realm.isOfflineSessionMaxLifespanEnabled()) { - long clientOfflineSessionMaxLifespan = TimeUnit.SECONDS.toMillis(getOfflineSessionMaxLifespan(realm)); - - String clientOfflineSessionMaxLifespanPerClient = client == null? null : client.getAttribute(OIDCConfigAttributes.CLIENT_OFFLINE_SESSION_MAX_LIFESPAN); - if (clientOfflineSessionMaxLifespanPerClient != null && !clientOfflineSessionMaxLifespanPerClient.trim().isEmpty()) { - clientOfflineSessionMaxLifespan = TimeUnit.SECONDS.toMillis(Long.parseLong(clientOfflineSessionMaxLifespanPerClient)); + long clientOfflineSessionMaxLifespan = getClientAttributeTimeout(client, OIDCConfigAttributes.CLIENT_OFFLINE_SESSION_MAX_LIFESPAN); + if (realm.isOfflineSessionMaxLifespanEnabled() || clientOfflineSessionMaxLifespan > 0) { + if (clientOfflineSessionMaxLifespan > 0) { + clientOfflineSessionMaxLifespan = TimeUnit.SECONDS.toMillis(clientOfflineSessionMaxLifespan); } else if (realm.getClientOfflineSessionMaxLifespan() > 0) { clientOfflineSessionMaxLifespan = TimeUnit.SECONDS.toMillis(realm.getClientOfflineSessionMaxLifespan()); + } else { + clientOfflineSessionMaxLifespan = TimeUnit.SECONDS.toMillis(getOfflineSessionMaxLifespan(realm)); } - timestamp = clientSessionCreated + clientOfflineSessionMaxLifespan; long userSessionExpires = calculateUserSessionMaxLifespanTimestamp(offline, isRememberMe, userSessionCreated, realm); - timestamp = Math.min(timestamp, userSessionExpires); + timestamp = userSessionExpires > 0? Math.min(timestamp, userSessionExpires) : timestamp; } } else { long clientSessionMaxLifespan = TimeUnit.SECONDS.toMillis(getSsoSessionMaxLifespan(realm)); if (isRememberMe) { clientSessionMaxLifespan = Math.max(clientSessionMaxLifespan, TimeUnit.SECONDS.toMillis(realm.getSsoSessionMaxLifespanRememberMe())); } - String clientSessionMaxLifespanPerClient = client == null? null : client.getAttribute(OIDCConfigAttributes.CLIENT_SESSION_MAX_LIFESPAN); - if (clientSessionMaxLifespanPerClient != null && !clientSessionMaxLifespanPerClient.trim().isEmpty()) { - clientSessionMaxLifespan = TimeUnit.SECONDS.toMillis(Long.parseLong(clientSessionMaxLifespanPerClient)); + long clientSessionMaxLifespanPerClient = getClientAttributeTimeout(client, OIDCConfigAttributes.CLIENT_SESSION_MAX_LIFESPAN); + if (clientSessionMaxLifespanPerClient > 0) { + clientSessionMaxLifespan = TimeUnit.SECONDS.toMillis(clientSessionMaxLifespanPerClient); } else if (realm.getClientSessionMaxLifespan() > 0) { clientSessionMaxLifespan = TimeUnit.SECONDS.toMillis(realm.getClientSessionMaxLifespan()); } @@ -144,9 +144,9 @@ public static long calculateClientSessionIdleTimestamp(boolean offline, boolean long timestamp; if (offline) { long clientOfflineSessionIdleTimeout = TimeUnit.SECONDS.toMillis(getOfflineSessionIdleTimeout(realm)); - String clientOfflineSessionIdleTimeoutPerClient = client == null? null : client.getAttribute(OIDCConfigAttributes.CLIENT_OFFLINE_SESSION_IDLE_TIMEOUT); - if (clientOfflineSessionIdleTimeoutPerClient != null && !clientOfflineSessionIdleTimeoutPerClient.trim().isEmpty()) { - clientOfflineSessionIdleTimeout = TimeUnit.SECONDS.toMillis(Long.parseLong(clientOfflineSessionIdleTimeoutPerClient)); + long clientOfflineSessionIdleTimeoutPerClient = getClientAttributeTimeout(client, OIDCConfigAttributes.CLIENT_OFFLINE_SESSION_IDLE_TIMEOUT); + if (clientOfflineSessionIdleTimeoutPerClient > 0) { + clientOfflineSessionIdleTimeout = TimeUnit.SECONDS.toMillis(clientOfflineSessionIdleTimeoutPerClient); } else if (realm.getClientOfflineSessionIdleTimeout() > 0) { clientOfflineSessionIdleTimeout = TimeUnit.SECONDS.toMillis(realm.getClientOfflineSessionIdleTimeout()); } @@ -157,9 +157,9 @@ public static long calculateClientSessionIdleTimestamp(boolean offline, boolean if (isRememberMe) { clientSessionIdleTimeout = Math.max(clientSessionIdleTimeout, TimeUnit.SECONDS.toMillis(realm.getSsoSessionIdleTimeoutRememberMe())); } - String clientSessionIdleTimeoutPerClient = client == null? null : client.getAttribute(OIDCConfigAttributes.CLIENT_SESSION_IDLE_TIMEOUT); - if (clientSessionIdleTimeoutPerClient != null && !clientSessionIdleTimeoutPerClient.trim().isEmpty()) { - clientSessionIdleTimeout = TimeUnit.SECONDS.toMillis(Long.parseLong(clientSessionIdleTimeoutPerClient)); + long clientSessionIdleTimeoutPerClient = getClientAttributeTimeout(client, OIDCConfigAttributes.CLIENT_SESSION_IDLE_TIMEOUT); + if (clientSessionIdleTimeoutPerClient > 0) { + clientSessionIdleTimeout = TimeUnit.SECONDS.toMillis(clientSessionIdleTimeoutPerClient); } else if (realm.getClientSessionIdleTimeout() > 0){ clientSessionIdleTimeout = TimeUnit.SECONDS.toMillis(realm.getClientSessionIdleTimeout()); } @@ -200,4 +200,18 @@ private static int getOfflineSessionIdleTimeout(RealmModel realm) { } return idle; } + + private static long getClientAttributeTimeout(ClientModel client, String attr) { + if (client != null) { + final String value = client.getAttribute(attr); + if (StringUtil.isNotBlank(value)) { + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + // no-op + } + } + } + return -1; + } } diff --git a/server-spi-private/src/test/java/org/keycloak/models/utils/SessionExpirationUtilsTest.java b/server-spi-private/src/test/java/org/keycloak/models/utils/SessionExpirationUtilsTest.java index 07bb52b6752e..0a2461463a51 100644 --- a/server-spi-private/src/test/java/org/keycloak/models/utils/SessionExpirationUtilsTest.java +++ b/server-spi-private/src/test/java/org/keycloak/models/utils/SessionExpirationUtilsTest.java @@ -180,6 +180,9 @@ public void testCalculateClientSessionMaxLifespanTimestampOnline() { realmMap.put("getSsoSessionMaxLifespan", 1000); realmMap.put("getSsoSessionMaxLifespanRememberMe", 2000); Assert.assertEquals(2000 * 1000L, SessionExpirationUtils.calculateClientSessionMaxLifespanTimestamp(false, true, t, t, realm, client) - t); + // set -1 in the client and should be not taken into account + clientMap.put(OIDCConfigAttributes.CLIENT_SESSION_MAX_LIFESPAN, "-1"); + Assert.assertEquals(2000 * 1000L, SessionExpirationUtils.calculateClientSessionMaxLifespanTimestamp(false, true, t, t, realm, client) - t); } @Test @@ -207,6 +210,13 @@ public void testCalculateClientSessionMaxLifespanTimestampOffline() { long t2 = t - 100; realmMap.put("getOfflineSessionMaxLifespan", 2000); Assert.assertEquals(2000 * 1000L, SessionExpirationUtils.calculateClientSessionMaxLifespanTimestamp(true, false, t, t2, realm, client) - t2); + // set -1 in the client and should be not taken into account + clientMap.put(OIDCConfigAttributes.CLIENT_OFFLINE_SESSION_MAX_LIFESPAN, "-1"); + Assert.assertEquals(2000 * 1000L, SessionExpirationUtils.calculateClientSessionMaxLifespanTimestamp(true, false, t, t, realm, client) - t); + // set no expiration at realm but set expiration at client level + realmMap.put("isOfflineSessionMaxLifespanEnabled", false); + clientMap.put(OIDCConfigAttributes.CLIENT_OFFLINE_SESSION_MAX_LIFESPAN, "2000"); + Assert.assertEquals(2000 * 1000L, SessionExpirationUtils.calculateClientSessionMaxLifespanTimestamp(true, false, t, t, realm, client) - t); } @Test @@ -230,6 +240,9 @@ public void testCalculateClientSessionIdleTimestampOnline() { // override value in client clientMap.put(OIDCConfigAttributes.CLIENT_SESSION_IDLE_TIMEOUT, "3000"); Assert.assertEquals(3000 * 1000L, SessionExpirationUtils.calculateClientSessionIdleTimestamp(false, false, t, realm, client) - t); + // set -1 in the client and should be not taken into account + clientMap.put(OIDCConfigAttributes.CLIENT_SESSION_IDLE_TIMEOUT, "-1"); + Assert.assertEquals(4000 * 1000L, SessionExpirationUtils.calculateClientSessionIdleTimestamp(false, false, t, realm, client) - t); } @Test @@ -253,5 +266,8 @@ public void testCalculateClientSessionIdleTimestampOffline() { // override value in client clientMap.put(OIDCConfigAttributes.CLIENT_OFFLINE_SESSION_IDLE_TIMEOUT, "3000"); Assert.assertEquals(3000 * 1000L, SessionExpirationUtils.calculateClientSessionIdleTimestamp(true, false, t, realm, client) - t); + // set -1 in the client and should be not taken into account + clientMap.put(OIDCConfigAttributes.CLIENT_OFFLINE_SESSION_IDLE_TIMEOUT, "-1"); + Assert.assertEquals(4000 * 1000L, SessionExpirationUtils.calculateClientSessionIdleTimestamp(true, false, t, realm, client) - t); } } From 5f533e8d9fcbdfff96463fe7d3620ae06284d878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Muzik=C3=A1=C5=99?= Date: Thu, 27 Jul 2023 15:42:51 +0200 Subject: [PATCH 025/135] Fix Operator tests on OpenShift Closes #22032 Closes #22140 Closes #22142 --- .../src/main/resources/example-db-secret.yaml | 6 +-- .../src/main/resources/example-postgres.yaml | 23 ++++++--- .../integration/BaseOperatorTest.java | 10 ++-- .../integration/KeycloakIngressTest.java | 14 ++++++ .../integration/PodTemplateTest.java | 50 +++++++++++-------- .../integration/WatchedSecretsTest.java | 2 +- 6 files changed, 71 insertions(+), 34 deletions(-) diff --git a/operator/src/main/resources/example-db-secret.yaml b/operator/src/main/resources/example-db-secret.yaml index 99e4842ef2e7..4082f8f53127 100644 --- a/operator/src/main/resources/example-db-secret.yaml +++ b/operator/src/main/resources/example-db-secret.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: Secret metadata: name: keycloak-db-secret -data: - username: cG9zdGdyZXM= # postgres - password: dGVzdHBhc3N3b3Jk # testpassword +stringData: + username: "kc-user" + password: "testpassword" type: Opaque \ No newline at end of file diff --git a/operator/src/main/resources/example-postgres.yaml b/operator/src/main/resources/example-postgres.yaml index 8b6f49183524..e6385b22e366 100644 --- a/operator/src/main/resources/example-postgres.yaml +++ b/operator/src/main/resources/example-postgres.yaml @@ -16,16 +16,25 @@ spec: spec: containers: - name: postgresql-db - image: postgres:latest + # WARN: this image is not ARM64 native, consider using "postgres:latest" or "registry.redhat.io/rhel9/postgresql-15:latest" if you need that + # See also https://github.com/sclorg/postgresql-container/pull/527 + # Using c8s instead of c9s as c9s requires additional arguments for emulation on ARM machines, while c8s works OOTB + image: quay.io/sclorg/postgresql-15-c8s:latest volumeMounts: - - mountPath: /data + - mountPath: /var/lib/pgsql/data name: cache-volume env: - - name: POSTGRES_PASSWORD - value: testpassword - - name: PGDATA - value: /data/pgdata - - name: POSTGRES_DB + - name: POSTGRESQL_USER + valueFrom: + secretKeyRef: + key: username + name: keycloak-db-secret + - name: POSTGRESQL_PASSWORD + valueFrom: + secretKeyRef: + key: password + name: keycloak-db-secret + - name: POSTGRESQL_DATABASE value: keycloak volumes: - name: cache-volume diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/integration/BaseOperatorTest.java b/operator/src/test/java/org/keycloak/operator/testsuite/integration/BaseOperatorTest.java index 3c525133c8fe..9c8774eee9ca 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/integration/BaseOperatorTest.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/integration/BaseOperatorTest.java @@ -164,10 +164,16 @@ private static void createNamespace() { } private static void calculateNamespace() { - namespace = "keycloak-test-" + UUID.randomUUID(); + namespace = getNewRandomNamespaceName(); + } + + public static String getNewRandomNamespaceName() { + return "keycloak-test-" + UUID.randomUUID(); } protected static void deployDB() { + deployDBSecret(); + // DB Log.info("Creating new PostgreSQL deployment"); K8sUtils.set(k8sclient, BaseOperatorTest.class.getResourceAsStream("/example-postgres.yaml")); @@ -176,8 +182,6 @@ protected static void deployDB() { Log.info("Checking Postgres is running"); Awaitility.await() .untilAsserted(() -> assertThat(k8sclient.apps().statefulSets().inNamespace(namespace).withName("postgresql-db").get().getStatus().getReadyReplicas()).isEqualTo(1)); - - deployDBSecret(); } protected static void deployDBSecret() { diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/integration/KeycloakIngressTest.java b/operator/src/test/java/org/keycloak/operator/testsuite/integration/KeycloakIngressTest.java index 0888f06bb992..fce35b53692f 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/integration/KeycloakIngressTest.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/integration/KeycloakIngressTest.java @@ -32,6 +32,7 @@ import org.keycloak.operator.crds.v2alpha1.deployment.spec.IngressSpec; import org.keycloak.operator.crds.v2alpha1.deployment.spec.IngressSpecBuilder; import org.keycloak.operator.crds.v2alpha1.deployment.spec.HostnameSpecBuilder; +import org.keycloak.operator.crds.v2alpha1.deployment.spec.UnsupportedSpecBuilder; import org.keycloak.operator.testsuite.utils.K8sUtils; import org.keycloak.operator.controllers.KeycloakIngress; @@ -76,6 +77,19 @@ public void testIngressOnHTTP() { // on OpenShift, when Keycloak is configured for HTTP only, we use edge TLS termination, i.e. Route still uses TLS baseUrl = "https://" + testHostname + ":443"; hostnameSpecBuilder.withHostname(testHostname); + // see https://github.com/keycloak/keycloak/issues/14400#issuecomment-1659900081 + kc.getSpec().setUnsupported(new UnsupportedSpecBuilder() + .withNewPodTemplate() + .withNewSpec() + .addNewContainer() + .addNewEnv() + .withName("KC_PROXY") + .withValue("edge") + .endEnv() + .endContainer() + .endSpec() + .endPodTemplate() + .build()); } else { baseUrl = "http://" + kubernetesIp + ":80"; diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/integration/PodTemplateTest.java b/operator/src/test/java/org/keycloak/operator/testsuite/integration/PodTemplateTest.java index 0fa8439f5c61..40ba5adbe48a 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/integration/PodTemplateTest.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/integration/PodTemplateTest.java @@ -19,6 +19,7 @@ import io.fabric8.kubernetes.api.model.LocalObjectReference; import io.fabric8.kubernetes.api.model.LocalObjectReferenceBuilder; +import io.fabric8.kubernetes.api.model.NamespaceBuilder; import io.fabric8.kubernetes.api.model.PodTemplateSpecBuilder; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.client.dsl.Resource; @@ -110,26 +111,35 @@ public void testPodTemplateIncorrectName() { @Test public void testPodTemplateIncorrectNamespace() { - // Arrange - var plainKc = getEmptyPodTemplateKeycloak(); - var podTemplate = new PodTemplateSpecBuilder() - .withNewMetadata() - .withNamespace("bar") - .endMetadata() - .build(); - plainKc.getSpec().getUnsupported().setPodTeplate(podTemplate); - - // Act - K8sUtils.set(k8sclient, plainKc); - - // Assert - Log.info("Getting status of Keycloak"); - Awaitility - .await() - .ignoreExceptions() - .atMost(3, MINUTES).untilAsserted(() -> { - CRAssert.assertKeycloakStatusCondition(getCrSelector().get(), HAS_ERRORS, false, "cannot be modified"); - }); + final String wrongNamespace = getNewRandomNamespaceName(); + try { + // Arrange + Log.info("Using incorrect namespace: " + wrongNamespace); + k8sclient.resource(new NamespaceBuilder().withNewMetadata().withName(wrongNamespace).endMetadata().build()).create(); // OpenShift actually checks existence of the NS + var plainKc = getEmptyPodTemplateKeycloak(); + var podTemplate = new PodTemplateSpecBuilder() + .withNewMetadata() + .withNamespace(wrongNamespace) + .endMetadata() + .build(); + plainKc.getSpec().getUnsupported().setPodTeplate(podTemplate); + + // Act + K8sUtils.set(k8sclient, plainKc); + + // Assert + Log.info("Getting status of Keycloak"); + Awaitility + .await() + .ignoreExceptions() + .atMost(3, MINUTES).untilAsserted(() -> { + CRAssert.assertKeycloakStatusCondition(getCrSelector().get(), HAS_ERRORS, false, "cannot be modified"); + }); + } + finally { + Log.info("Deleting incorrect namespace: " + wrongNamespace); + k8sclient.namespaces().withName(wrongNamespace).delete(); + } } @Test diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/integration/WatchedSecretsTest.java b/operator/src/test/java/org/keycloak/operator/testsuite/integration/WatchedSecretsTest.java index 9e303ed8db09..a6bac35e9f34 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/integration/WatchedSecretsTest.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/integration/WatchedSecretsTest.java @@ -278,7 +278,7 @@ private void hardcodeDBCredsInCR(Keycloak kc) { kc.getSpec().getDatabaseSpec().setUsernameSecret(null); kc.getSpec().getDatabaseSpec().setPasswordSecret(null); - var username = new ValueOrSecret("db-username", "postgres"); + var username = new ValueOrSecret("db-username", "kc-user"); var password = new ValueOrSecret("db-password", "testpassword"); kc.getSpec().getAdditionalOptions().remove(username); From 842b70c6c2f209c99ea4160ff7250b606b064ee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Muzik=C3=A1=C5=99?= Date: Mon, 7 Aug 2023 09:04:32 +0200 Subject: [PATCH 026/135] Upgrade to Quarkus 3.2.3 (#22256) Closes #22220 (cherry picked from commit 4b537bee421053f281f819e73b5b4c04648757de) --- pom.xml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 48a25db2b8a1..21f49a849d6d 100644 --- a/pom.xml +++ b/pom.xml @@ -43,8 +43,8 @@ jboss-snapshots-repository https://s01.oss.sonatype.org/content/repositories/snapshots/ - 3.2.2.Final - 3.2.2.Final + 3.2.3.Final + 3.2.3.Final ${timestamp} @@ -74,8 +74,6 @@ 3.3.10 2.1.3 2.2.220 - - 6.2.7.Final 6.2.7.Final 6.2.7.Final 14.0.13.Final @@ -506,11 +504,6 @@ ${h2.version} test
- - org.hibernate.orm - hibernate-core - ${hibernate-orm.version} - org.hibernate.orm hibernate-c3p0 From ec119ebeb8217f2e0f0c0d08d3eb3571c08f5f5d Mon Sep 17 00:00:00 2001 From: Alexander Schwartz Date: Mon, 7 Aug 2023 11:21:27 +0200 Subject: [PATCH 027/135] Ignore new NodeJS redirect (#22218) Closes #22186 (cherry picked from commit 5c6df3d26eae58423957f02e2d404689039a35d1) --- .../org/keycloak/documentation/test/ExternalLinksTest.java | 2 +- .../tests/src/test/resources/ignored-link-redirects | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/documentation/tests/src/test/java/org/keycloak/documentation/test/ExternalLinksTest.java b/docs/documentation/tests/src/test/java/org/keycloak/documentation/test/ExternalLinksTest.java index 5b89a3d1dabb..9ee90ae77376 100644 --- a/docs/documentation/tests/src/test/java/org/keycloak/documentation/test/ExternalLinksTest.java +++ b/docs/documentation/tests/src/test/java/org/keycloak/documentation/test/ExternalLinksTest.java @@ -17,7 +17,7 @@ public void checkExternalLinks(String guideName) throws IOException { List invalidLinks = LinkUtils.getInstance().findInvalidLinks(guide); if (!invalidLinks.isEmpty()) { StringBuilder sb = new StringBuilder(); - sb.append("Broken links (" + invalidLinks.size() + "):"); + sb.append("Broken links (" + invalidLinks.size() + ") in guide '" + guideName + "': "); for (LinkUtils.InvalidLink l : invalidLinks) { sb.append("\n\t\t- " + l.getLink() + " (" + l.getError() + ")"); diff --git a/docs/documentation/tests/src/test/resources/ignored-link-redirects b/docs/documentation/tests/src/test/resources/ignored-link-redirects index d5cabf3e6ec6..8a3c3bceacc2 100644 --- a/docs/documentation/tests/src/test/resources/ignored-link-redirects +++ b/docs/documentation/tests/src/test/resources/ignored-link-redirects @@ -8,10 +8,11 @@ https://apps.dev.microsoft.com/?mkt=en-us&deeplink=/appList/create/sapi&referrer /wiki/spaces/ops4j/pages/5046828/Pax Web Extender - Whiteboard /wiki/spaces/ops4j/pages/5046828/Pax Web Extender - Whiteboard https://nodejs.org/en/ +https://nodejs.org/en +/en/ http://docs.openshift.com/container-platform/* https://access.redhat.com/documentation/en/red-hat-jboss-middleware-for-openshift/3/single/openshift-primer/ https://access.redhat.com/login?redirectTo=https://access.redhat.com/terms-based-registry https://sso.redhat.com/auth/* REGEX:https://access.redhat.com/documentation/.*/index -https://nodejs.org/en REGEX:/login\.jsp.+KEYCLOAK-[0-9]+.* \ No newline at end of file From 6bb0ca304a6c34bf02bc750042cad2e73acf4d59 Mon Sep 17 00:00:00 2001 From: Peter Zaoral Date: Mon, 7 Aug 2023 15:09:57 +0200 Subject: [PATCH 028/135] Update OCP4 Social IdP example setup in the latest docs (#22293) * improved openshift.adoc Closes #22159 Signed-off-by: Peter Zaoral --- .../openshift-4-add-identity-provider.png | Bin 33670 -> 81265 bytes .../images/openshift-4-result.png | Bin 0 -> 155998 bytes .../identity-broker/social/openshift.adoc | 29 +++++++----------- 3 files changed, 11 insertions(+), 18 deletions(-) create mode 100644 docs/documentation/server_admin/images/openshift-4-result.png diff --git a/docs/documentation/server_admin/images/openshift-4-add-identity-provider.png b/docs/documentation/server_admin/images/openshift-4-add-identity-provider.png index 5a5bcf92e1b9b247bcf9b8b59fba0928ebac26b2..feb64d045be60d7df33637a6696f827c7576d9db 100644 GIT binary patch literal 81265 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 literal 33670 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 j4-NVL(4oFJi4VF<4>S{35j$i%{!Q|hn diff --git a/docs/documentation/server_admin/images/openshift-4-result.png b/docs/documentation/server_admin/images/openshift-4-result.png new file mode 100644 index 0000000000000000000000000000000000000000..3d9e2ac82f05b2bd5c5cc339c97f15a798b5d167 GIT binary patch literal 155998 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 literal 0 HcmV?d00001 diff --git a/docs/documentation/server_admin/topics/identity-broker/social/openshift.adoc b/docs/documentation/server_admin/topics/identity-broker/social/openshift.adoc index b5d31a0359ef..60e3b38dcbfb 100644 --- a/docs/documentation/server_admin/topics/identity-broker/social/openshift.adoc +++ b/docs/documentation/server_admin/topics/identity-broker/social/openshift.adoc @@ -38,25 +38,18 @@ grantMethod: prompt <4> ==== OpenShift 4 .Prerequisites -. Installation of https://stedolan.github.io/jq/[jq]. -. `X509_CA_BUNDLE` configured in the container and set to `/var/run/secrets/kubernetes.io/serviceaccount/ca.crt`. +. A certificate of the OpenShift 4 instance stored in the Keycloak Truststore. +. A Keycloak server configured in order to use the truststore. For more information, see the https://www.keycloak.org/server/keycloak-truststore[Configuring a Truststore] {section}. .Procedure -. Run the following command on the command line and note the OpenShift 4 API URL output. -+ -[source,subs="attributes+"] ----- -curl -s -k -H "Authorization: Bearer $(oc whoami -t)" \https:///apis/config.openshift.io/v1/infrastructures/cluster | jq ".status.apiServerURL" ----- -+ . Click *Identity Providers* in the {project_name} menu. -. From the `Add provider` list, select `Openshift`. +. From the `Social` section, select `Openshift v4` tile. +. Enter the *Client ID* and *Client Secret* and in the *Base URL* field, enter the API URL of your OpenShift 4 instance. Additionally, you can copy the *Redirect URI* to your clipboard. + .Add identity provider image:images/openshift-4-add-identity-provider.png[Add Identity Provider] + -. Copy the value of *Redirect URI* to your clipboard. -. Register your client using the `oc` command-line tool. +. Register your client, either via OpenShift 4 Console (Home -> API Explorer -> OAuth Client -> Instances) or using the `oc` command-line tool. + [source, subs="attributes+"] ---- @@ -64,10 +57,10 @@ $ oc create -f <(echo ' kind: OAuthClient apiVersion: oauth.openshift.io/v1 metadata: - name: keycloak-broker <1> + name: kc-client <1> secret: "..." <2> redirectURIs: - - "" <3> + - "" <3> grantMethod: prompt <4> ') ---- @@ -76,10 +69,10 @@ grantMethod: prompt <4> <2> The `secret` {project_name} uses as the `client_secret` request parameter. <3> The `redirect_uri` parameter specified in requests to `__/oauth/authorize` and `__/oauth/token` must be equal to (or prefixed by) one of the URIs in `redirectURIs`. The easiest way to configure it correctly is to copy-paste it from {project_name} OpenShift 4 Identity Provider configuration page (`Redirect URI` field). <4> The `grantMethod` {project_name} uses to determine the action when this client requests tokens but has not been granted access by the user. -+ -. In {project_name}, paste the value of the *Client ID* into the *Client ID* field. -. In {project_name}, paste the value of the *Client Secret* into the *Client Secret* field. -. Click *Add*. +In the end you should see the OpenShift 4 Identity Provider on the login page of your {project_name} instance. After clicking on it, you should be redirected to the OpenShift 4 login page. + +.Result +image:images/openshift-4-result.png[Result] See https://docs.okd.io/latest/authentication/configuring-oauth-clients.html#oauth-register-additional-client_configuring-oauth-clients[official OpenShift documentation] for more information. From 4cd44d7f8d01c1398d80383ae63b16d55f7d209d Mon Sep 17 00:00:00 2001 From: Alexander Schwartz Date: Mon, 7 Aug 2023 18:33:03 +0200 Subject: [PATCH 029/135] Prevent concurrent session cleanup on different instances in the cluster (#22298) Closes #22198 (cherry picked from commit 5f95929092f3849c0b8b1df9269ffaf25f6174be) --- .../storage/datastore/LegacyDatastoreProviderFactory.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/model/legacy-private/src/main/java/org/keycloak/storage/datastore/LegacyDatastoreProviderFactory.java b/model/legacy-private/src/main/java/org/keycloak/storage/datastore/LegacyDatastoreProviderFactory.java index 7e2ea943e726..dc93b6861012 100644 --- a/model/legacy-private/src/main/java/org/keycloak/storage/datastore/LegacyDatastoreProviderFactory.java +++ b/model/legacy-private/src/main/java/org/keycloak/storage/datastore/LegacyDatastoreProviderFactory.java @@ -32,7 +32,6 @@ import org.keycloak.services.scheduled.ClearExpiredEvents; import org.keycloak.services.scheduled.ClearExpiredUserSessions; import org.keycloak.services.scheduled.ClusterAwareScheduledTaskRunner; -import org.keycloak.services.scheduled.ScheduledTaskRunner; import org.keycloak.storage.DatastoreProvider; import org.keycloak.storage.DatastoreProviderFactory; import org.keycloak.storage.LegacyStoreMigrateRepresentationEvent; @@ -106,7 +105,7 @@ public static void setupScheduledTasks(final KeycloakSessionFactory sessionFacto timer.schedule(new ClusterAwareScheduledTaskRunner(sessionFactory, new ClearExpiredEvents(), interval), interval, "ClearExpiredEvents"); timer.schedule(new ClusterAwareScheduledTaskRunner(sessionFactory, new ClearExpiredAdminEvents(), interval), interval, "ClearExpiredAdminEvents"); timer.schedule(new ClusterAwareScheduledTaskRunner(sessionFactory, new ClearExpiredClientInitialAccessTokens(), interval), interval, "ClearExpiredClientInitialAccessTokens"); - timer.schedule(new ScheduledTaskRunner(sessionFactory, new ClearExpiredUserSessions()), interval, ClearExpiredUserSessions.TASK_NAME); + timer.schedule(new ClusterAwareScheduledTaskRunner(sessionFactory, new ClearExpiredUserSessions(), interval), interval, ClearExpiredUserSessions.TASK_NAME); UserStorageSyncManager.bootstrapPeriodic(sessionFactory, timer); } } From d395e199a142cb66aeeecc1da5a19c18cb0c049e Mon Sep 17 00:00:00 2001 From: Alexander Schwartz Date: Thu, 10 Aug 2023 11:52:46 +0200 Subject: [PATCH 030/135] Changes to the time offset should be visible in all threads instantly (#22291) --- common/src/main/java/org/keycloak/common/util/Time.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/org/keycloak/common/util/Time.java b/common/src/main/java/org/keycloak/common/util/Time.java index 76ce5f776c3b..ef4187cd7af5 100644 --- a/common/src/main/java/org/keycloak/common/util/Time.java +++ b/common/src/main/java/org/keycloak/common/util/Time.java @@ -24,7 +24,7 @@ */ public class Time { - private static int offset; + private static volatile int offset; /** * Returns current time in seconds adjusted by adding {@link #offset) seconds. From adec6c5f01c152ab92a04fd56197aab585cfe665 Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Tue, 8 Aug 2023 16:16:34 -0300 Subject: [PATCH 031/135] Fixing how e-mail attribute permissions are set for both USER_API and ACCOUNT contexts Closes #21751 --- .../AbstractUserProfileProvider.java | 17 +++++++++--- .../userprofile/LegacyAttributes.java | 9 +++++-- .../account/AccountRestServiceTest.java | 22 +++++++++++++++ .../keycloak/testsuite/admin/UserTest.java | 27 +++++++++++++++++++ 4 files changed, 69 insertions(+), 6 deletions(-) diff --git a/services/src/main/java/org/keycloak/userprofile/AbstractUserProfileProvider.java b/services/src/main/java/org/keycloak/userprofile/AbstractUserProfileProvider.java index b015ce1c542b..423f7217624e 100644 --- a/services/src/main/java/org/keycloak/userprofile/AbstractUserProfileProvider.java +++ b/services/src/main/java/org/keycloak/userprofile/AbstractUserProfileProvider.java @@ -39,6 +39,7 @@ import java.util.stream.Collectors; import org.keycloak.Config; import org.keycloak.common.Profile; +import org.keycloak.common.Profile.Feature; import org.keycloak.models.KeycloakContext; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; @@ -119,13 +120,21 @@ private static boolean editEmailCondition(AttributeContext c) { } private static boolean readEmailCondition(AttributeContext c) { - RealmModel realm = c.getSession().getContext().getRealm(); + UserProfileContext context = c.getContext(); - if (realm.isRegistrationEmailAsUsername() && !realm.isEditUsernameAllowed()) { - return REGISTRATION_PROFILE.equals(c.getContext()); + if (UPDATE_PROFILE.equals(context)) { + if (Profile.isFeatureEnabled(Feature.UPDATE_EMAIL)) { + return false; + } + + RealmModel realm = c.getSession().getContext().getRealm(); + + if (realm.isRegistrationEmailAsUsername() && !realm.isEditUsernameAllowed()) { + return false; + } } - return !Profile.isFeatureEnabled(Profile.Feature.UPDATE_EMAIL) || c.getContext() != UPDATE_PROFILE; + return true; } public static Pattern getRegexPatternString(String[] builtinReadOnlyAttributes) { diff --git a/services/src/main/java/org/keycloak/userprofile/LegacyAttributes.java b/services/src/main/java/org/keycloak/userprofile/LegacyAttributes.java index e6514e5389cf..cc48000a815c 100644 --- a/services/src/main/java/org/keycloak/userprofile/LegacyAttributes.java +++ b/services/src/main/java/org/keycloak/userprofile/LegacyAttributes.java @@ -48,7 +48,12 @@ public Map> getReadable() { @Override protected boolean isIncludeAttributeIfNotProvided(AttributeMetadata metadata) { - // user api expects that built-in attributes are not updated if not provided when in legacy mode - return UserProfileContext.USER_API.equals(context) && !isRootAttribute(metadata.getName()); + if (UserModel.LOCALE.equals(metadata.getName())) { + // locale is an internal attribute and should be updated as a regular attribute + return false; + } + + // user api expects that attributes are not updated if not provided when in legacy mode + return UserProfileContext.USER_API.equals(context); } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java index f3fe05644185..fbe88b3e8896 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java @@ -421,6 +421,28 @@ public void testUpdateProfile() throws IOException { } + @Test + public void testEmailReadableWhenEditUsernameDisabled() throws IOException { + RealmRepresentation realmRep = testRealm().toRepresentation(); + Boolean emailAsUsername = realmRep.isRegistrationEmailAsUsername(); + Boolean editUsernameAllowed = realmRep.isEditUsernameAllowed(); + realmRep.setRegistrationEmailAsUsername(true); + realmRep.setEditUsernameAllowed(false); + testRealm().update(realmRep); + + try { + UserRepresentation user = getUser(); + String email = user.getEmail(); + assertNotNull(email); + user = updateAndGet(user); + assertEquals(email, user.getEmail()); + } finally { + realmRep.setRegistrationEmailAsUsername(emailAsUsername); + realmRep.setEditUsernameAllowed(editUsernameAllowed); + testRealm().update(realmRep); + } + } + @Test public void testUpdateProfileCannotChangeThroughAttributes() throws IOException { UserRepresentation user = getUser(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java index fed8dda62807..81c6b13396a1 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java @@ -107,6 +107,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; @@ -2319,6 +2320,32 @@ public void updateUserWithExistingEmail() { } } + @Test + public void testKeepRootAttributeWhenOtherAttributesAreSet() { + String random = UUID.randomUUID().toString(); + String userName = String.format("username-%s", random); + String email = String.format("my@mail-%s.com", random); + UserRepresentation user = new UserRepresentation(); + user.setUsername(userName); + user.setEmail(email); + String userId = createUser(user); + + UserRepresentation created = realm.users().get(userId).toRepresentation(); + assertThat(created.getEmail(), equalTo(email)); + assertThat(created.getUsername(), equalTo(userName)); + assertThat(created.getAttributes(), Matchers.nullValue()); + + UserRepresentation update = new UserRepresentation(); + update.setId(userId); + update.setAttributes(Map.of("phoneNumber", List.of("123"))); + updateUser(realm.users().get(userId), update); + + UserRepresentation updated = realm.users().get(userId).toRepresentation(); + assertThat(updated.getUsername(), equalTo(userName)); + assertThat(updated.getAttributes(), equalTo(Map.of("phoneNumber", List.of("123")))); + assertThat(updated.getEmail(), equalTo(email)); + } + @Test public void updateUserWithNewUsernameNotPossible() { String id = createUser(); From 159b750fe3e74db2bd53c1808494ebf341470d4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Barto=C5=A1?= Date: Tue, 15 Aug 2023 15:21:13 +0200 Subject: [PATCH 032/135] Remove JUnit dependencies from the distribution (#22458) Closes #22445 --- quarkus/deployment/pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/quarkus/deployment/pom.xml b/quarkus/deployment/pom.xml index dfa65c1dc632..36d04e3b2c25 100644 --- a/quarkus/deployment/pom.xml +++ b/quarkus/deployment/pom.xml @@ -21,6 +21,16 @@ io.quarkus quarkus-core-deployment + + + org.junit.platform + junit-platform-launcher + + + org.junit.jupiter + junit-jupiter + + io.quarkus From 132cf631f06030e58752cd404d4c76af1e314dfb Mon Sep 17 00:00:00 2001 From: Steven Hawkins Date: Tue, 15 Aug 2023 09:51:06 -0400 Subject: [PATCH 033/135] removes logging full resources to omit secret data (#22440) Closes #22080 --- .../operator/controllers/OperatorManagedResource.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/operator/src/main/java/org/keycloak/operator/controllers/OperatorManagedResource.java b/operator/src/main/java/org/keycloak/operator/controllers/OperatorManagedResource.java index b0c93fdccdb7..2836793af2a2 100644 --- a/operator/src/main/java/org/keycloak/operator/controllers/OperatorManagedResource.java +++ b/operator/src/main/java/org/keycloak/operator/controllers/OperatorManagedResource.java @@ -25,7 +25,6 @@ import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.dsl.base.PatchContext; import io.fabric8.kubernetes.client.dsl.base.PatchType; -import io.fabric8.kubernetes.client.utils.Serialization; import io.quarkus.logging.Log; import org.keycloak.operator.Constants; @@ -79,10 +78,11 @@ public void createOrUpdateReconciled() { throw ex; } } - Log.debugf("Successfully created or updated resource: %s", resource); + Log.debugf("Successfully created or updated resource: %s %s/%s", resource.getKind(), resource.getMetadata().getNamespace(), + resource.getMetadata().getName()); } catch (Exception e) { - Log.error("Failed to create or update resource"); - Log.error(Serialization.asYaml(resource)); + Log.errorf("Failed to create or update resource %s %s/%s", resource.getKind(), resource.getMetadata().getNamespace(), + resource.getMetadata().getName()); throw KubernetesClientException.launderThrowable(e); } }); From 705704258796d1537040fccf7f2ec23be4057983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Barto=C5=A1?= Date: Tue, 15 Aug 2023 17:52:59 +0200 Subject: [PATCH 034/135] Remove Vert.x Dev UI dependency (#22466) Closes #22446 --- quarkus/deployment/pom.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/quarkus/deployment/pom.xml b/quarkus/deployment/pom.xml index 36d04e3b2c25..743dc72ba68e 100644 --- a/quarkus/deployment/pom.xml +++ b/quarkus/deployment/pom.xml @@ -39,6 +39,13 @@ io.quarkus quarkus-reactive-routes-deployment + + + + io.quarkus + quarkus-vertx-http-dev-ui-resources + + io.quarkus From 0b180543ceba6d3eb841f5319a3717148e9b6437 Mon Sep 17 00:00:00 2001 From: Alexander Schwartz Date: Wed, 16 Aug 2023 16:00:30 +0200 Subject: [PATCH 035/135] Upgrade to Infinispan 14.0.14 (#22485) Closes #21092 (cherry picked from commit dfc8c80264b03ebf85b5735103586cae616838ce) --- .../keycloak/connections/infinispan/RemoteCacheProvider.java | 3 ++- pom.xml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/RemoteCacheProvider.java b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/RemoteCacheProvider.java index 1fa81d49747b..946dfcd7a2ca 100644 --- a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/RemoteCacheProvider.java +++ b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/RemoteCacheProvider.java @@ -34,6 +34,7 @@ import org.infinispan.client.hotrod.RemoteCacheManager; import org.infinispan.client.hotrod.configuration.Configuration; import org.infinispan.client.hotrod.configuration.ConfigurationBuilder; +import org.infinispan.commons.configuration.Combine; import org.infinispan.manager.EmbeddedCacheManager; import org.jboss.logging.Logger; import org.keycloak.Config; @@ -138,7 +139,7 @@ protected RemoteCacheManager getOrCreateSecuredRemoteCacheManager(Config.Scope c Configuration origConfig = origManager.getConfiguration(); ConfigurationBuilder cfgBuilder = new ConfigurationBuilder() - .read(origConfig); + .read(origConfig, Combine.DEFAULT); String securedHotRodEndpoint = origConfig.servers().stream() .map(serverConfiguration -> serverConfiguration.host() + ":" + serverConfiguration.port()) diff --git a/pom.xml b/pom.xml index 21f49a849d6d..32684d9b4a47 100644 --- a/pom.xml +++ b/pom.xml @@ -76,7 +76,7 @@ 2.2.220 6.2.7.Final 6.2.7.Final - 14.0.13.Final + 14.0.14.Final 4.6.2.Final From 85c99e9abc08bd99f15a4b7e514b305a384fbece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Muzik=C3=A1=C5=99?= Date: Thu, 17 Aug 2023 08:56:55 +0200 Subject: [PATCH 036/135] Upgrade to Quarkus 3.2.4.Final (#22420) Closes #22418 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 32684d9b4a47..cb242f4926cb 100644 --- a/pom.xml +++ b/pom.xml @@ -43,7 +43,7 @@ jboss-snapshots-repository https://s01.oss.sonatype.org/content/repositories/snapshots/ - 3.2.3.Final + 3.2.4.Final 3.2.3.Final ${timestamp} From 902214f6ae55678e369f74592b91ade1d63b1c38 Mon Sep 17 00:00:00 2001 From: Steven Hawkins Date: Thu, 17 Aug 2023 03:39:54 -0400 Subject: [PATCH 037/135] removing vertx-uri-template as a dependency (#22502) there's no usage of UriTemplate (smallrye or vertx in keycloak / fabric8), so it can be removed from server and the operator Closes #22468 --- operator/pom.xml | 12 ++++++++++++ quarkus/runtime/pom.xml | 6 ++++++ 2 files changed, 18 insertions(+) diff --git a/operator/pom.xml b/operator/pom.xml index b28548f5c605..48b88439d759 100644 --- a/operator/pom.xml +++ b/operator/pom.xml @@ -58,6 +58,12 @@ io.quarkiverse.operatorsdk quarkus-operator-sdk + + + io.vertx + vertx-uri-template + + io.quarkiverse.operatorsdk @@ -86,6 +92,12 @@ io.quarkus quarkus-kubernetes-client + + + io.vertx + vertx-uri-template + + io.quarkus diff --git a/quarkus/runtime/pom.xml b/quarkus/runtime/pom.xml index a795218738cc..5e208bc3ac3e 100644 --- a/quarkus/runtime/pom.xml +++ b/quarkus/runtime/pom.xml @@ -18,6 +18,12 @@ io.quarkus quarkus-vertx-http + + + io.vertx + vertx-uri-template + + io.quarkus From 0466261a634d200e8908e496dcb1315b38b964b2 Mon Sep 17 00:00:00 2001 From: Peter Zaoral Date: Thu, 17 Aug 2023 12:23:08 +0200 Subject: [PATCH 038/135] Remove Brotli dependencies from the distribution and operator (#22510) * excluded the dependencies from the keycloak-quarkus-server and operator poms Closes #22482 Signed-off-by: Peter Zaoral --- operator/pom.xml | 4 ++++ quarkus/runtime/pom.xml | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/operator/pom.xml b/operator/pom.xml index 48b88439d759..4b9d5c2f8dd4 100644 --- a/operator/pom.xml +++ b/operator/pom.xml @@ -97,6 +97,10 @@ io.vertx vertx-uri-template + + com.aayushatharva.brotli4j + * + diff --git a/quarkus/runtime/pom.xml b/quarkus/runtime/pom.xml index 5e208bc3ac3e..19b135dcb6bd 100644 --- a/quarkus/runtime/pom.xml +++ b/quarkus/runtime/pom.xml @@ -28,6 +28,12 @@ io.quarkus quarkus-vertx + + + com.aayushatharva.brotli4j + * + + io.quarkus From 4570718ec66ca3acff77f244e92b93578e2f4717 Mon Sep 17 00:00:00 2001 From: rmartinc Date: Wed, 16 Aug 2023 11:18:51 +0200 Subject: [PATCH 039/135] RedirectUtils needs to use KeycloakUriBuilder with no parameter parsing Closes https://github.com/keycloak/keycloak/issues/22424 --- .../java/org/keycloak/common/util/Encode.java | 19 ++++ .../common/util/KeycloakUriBuilder.java | 87 ++++++++++++------- .../common/util/KeycloakUriBuilderTest.java | 8 ++ .../protocol/oidc/utils/RedirectUtils.java | 4 +- .../oidc/utils/RedirectUtilsTest.java | 13 +++ 5 files changed, 98 insertions(+), 33 deletions(-) diff --git a/common/src/main/java/org/keycloak/common/util/Encode.java b/common/src/main/java/org/keycloak/common/util/Encode.java index 5999b2f2acc4..b17d61be5bf5 100755 --- a/common/src/main/java/org/keycloak/common/util/Encode.java +++ b/common/src/main/java/org/keycloak/common/util/Encode.java @@ -168,6 +168,15 @@ public static String encodeQueryString(String value) return encodeValue(value, queryStringEncoding); } + /** + * Keep encoded values "%..." but not the template parameters. + * @param value + * @return + */ + public static String encodeQueryStringNotTemplateParameters(String value) { + return encodeNonCodes(encodeFromArray(value, queryStringEncoding, false)); + } + /** * Keep encoded values "%...", matrix parameters, template parameters, and '/' characters intact. */ @@ -192,6 +201,16 @@ public static String encodeFragment(String value) return encodeValue(value, queryStringEncoding); } + /** + * Keep encoded values "%..." but not the template parameters. + * @param value + * @return + */ + public static String encodeFragmentNotTemplateParameters(String value) + { + return encodeNonCodes(encodeFromArray(value, queryStringEncoding, false)); + } + /** * Keep encoded values "%..." and template parameters intact. */ diff --git a/common/src/main/java/org/keycloak/common/util/KeycloakUriBuilder.java b/common/src/main/java/org/keycloak/common/util/KeycloakUriBuilder.java index 1a56f016348f..659c1d8c418c 100755 --- a/common/src/main/java/org/keycloak/common/util/KeycloakUriBuilder.java +++ b/common/src/main/java/org/keycloak/common/util/KeycloakUriBuilder.java @@ -51,7 +51,12 @@ public static KeycloakUriBuilder fromUri(URI uri) { } public static KeycloakUriBuilder fromUri(String uriTemplate) { - return new KeycloakUriBuilder().uri(uriTemplate); + // default fromUri manages template params {} + return new KeycloakUriBuilder().uri(uriTemplate, true); + } + + public static KeycloakUriBuilder fromUri(String uri, boolean template) { + return new KeycloakUriBuilder().uri(uri, template); } public static KeycloakUriBuilder fromPath(String path) throws IllegalArgumentException { @@ -131,28 +136,10 @@ public static KeycloakUriBuilder fromTemplate(String uriTemplate) { * @return */ public KeycloakUriBuilder uriTemplate(String uriTemplate) { - if (uriTemplate == null) throw new IllegalArgumentException("uriTemplate parameter is null"); - Matcher opaque = opaqueUri.matcher(uriTemplate); - if (opaque.matches()) { - this.authority = null; - this.host = null; - this.port = -1; - this.userInfo = null; - this.query = null; - this.scheme = opaque.group(1); - this.ssp = opaque.group(2); - return this; - } else { - Matcher match = hierarchicalUri.matcher(uriTemplate); - if (match.matches()) { - ssp = null; - return parseHierarchicalUri(uriTemplate, match); - } - } - throw new IllegalArgumentException("Illegal uri template: " + uriTemplate); + return uri(uriTemplate, true); } - protected KeycloakUriBuilder parseHierarchicalUri(String uriTemplate, Matcher match) { + protected KeycloakUriBuilder parseHierarchicalUri(String uri, Matcher match, boolean template) { boolean scheme = match.group(2) != null; if (scheme) this.scheme = match.group(2); String authority = match.group(4); @@ -171,7 +158,7 @@ protected KeycloakUriBuilder parseHierarchicalUri(String uriTemplate, Matcher ma try { this.port = Integer.parseInt(hostPortMatch.group(2)); } catch (NumberFormatException e) { - throw new IllegalArgumentException("Illegal uri template: " + uriTemplate, e); + throw new IllegalArgumentException("Illegal uri template: " + uri, e); } } else { this.host = host; @@ -180,16 +167,39 @@ protected KeycloakUriBuilder parseHierarchicalUri(String uriTemplate, Matcher ma if (match.group(5) != null) { String group = match.group(5); if (!scheme && !"".equals(group) && !group.startsWith("/") && group.indexOf(':') > -1) - throw new IllegalArgumentException("Illegal uri template: " + uriTemplate); - if (!"".equals(group)) replacePath(group); + throw new IllegalArgumentException("Illegal uri template: " + uri); + if (!"".equals(group)) replacePath(group, template); } - if (match.group(7) != null) replaceQuery(match.group(7)); - if (match.group(9) != null) fragment(match.group(9)); + if (match.group(7) != null) replaceQuery(match.group(7), template); + if (match.group(9) != null) fragment(match.group(9), template); return this; } - public KeycloakUriBuilder uri(String uriTemplate) throws IllegalArgumentException { - return uriTemplate(uriTemplate); + public KeycloakUriBuilder uri(String uri) throws IllegalArgumentException { + // default uri manages template params {} + return uri(uri, true); + } + + public KeycloakUriBuilder uri(String uri, boolean template) throws IllegalArgumentException { + if (uri == null) throw new IllegalArgumentException("uri parameter is null"); + Matcher opaque = opaqueUri.matcher(uri); + if (opaque.matches()) { + this.authority = null; + this.host = null; + this.port = -1; + this.userInfo = null; + this.query = null; + this.scheme = opaque.group(1); + this.ssp = opaque.group(2); + return this; + } else { + Matcher match = hierarchicalUri.matcher(uri); + if (match.matches()) { + ssp = null; + return parseHierarchicalUri(uri, match, template); + } + } + throw new IllegalArgumentException("Illegal uri template: " + uri); } public KeycloakUriBuilder uri(URI uri) throws IllegalArgumentException { @@ -356,20 +366,30 @@ public KeycloakUriBuilder replaceMatrix(String matrix) throws IllegalArgumentExc } public KeycloakUriBuilder replaceQuery(String query) throws IllegalArgumentException { + // default replaceQuery manages template params {} + return replaceQuery(query, true); + } + + public KeycloakUriBuilder replaceQuery(String query, boolean template) throws IllegalArgumentException { if (query == null || query.length() == 0) { this.query = null; return this; } - this.query = Encode.encodeQueryString(query); + this.query = template? Encode.encodeQueryString(query) : Encode.encodeQueryStringNotTemplateParameters(query); return this; } public KeycloakUriBuilder fragment(String fragment) throws IllegalArgumentException { + // default fragment manages template params {} + return fragment(fragment, true); + } + + public KeycloakUriBuilder fragment(String fragment, boolean template) throws IllegalArgumentException { if (fragment == null) { this.fragment = null; return this; } - this.fragment = Encode.encodeFragment(fragment); + this.fragment = template? Encode.encodeFragment(fragment) : Encode.encodeFragmentNotTemplateParameters(fragment); return this; } @@ -709,11 +729,16 @@ public KeycloakUriBuilder segment(String... segments) throws IllegalArgumentExce } public KeycloakUriBuilder replacePath(String path) { + // default replacePath manages template expression {} + return replacePath(path, true); + } + + public KeycloakUriBuilder replacePath(String path, boolean template) { if (path == null) { this.path = null; return this; } - this.path = Encode.encodePath(path); + this.path = template? Encode.encodePath(path) : Encode.encodePathSaveEncodings(path); return this; } diff --git a/common/src/test/java/org/keycloak/common/util/KeycloakUriBuilderTest.java b/common/src/test/java/org/keycloak/common/util/KeycloakUriBuilderTest.java index 414478fbaf09..5e418d73e5de 100644 --- a/common/src/test/java/org/keycloak/common/util/KeycloakUriBuilderTest.java +++ b/common/src/test/java/org/keycloak/common/util/KeycloakUriBuilderTest.java @@ -72,4 +72,12 @@ public void testPort() { Assert.assertEquals("https://localhost/path", KeycloakUriBuilder.fromUri("https://localhost/path").buildAsString()); Assert.assertEquals("https://localhost/path", KeycloakUriBuilder.fromUri("https://localhost/path").preserveDefaultPort().buildAsString()); } + + @Test + public void testTemplateAndNotTemplate() { + Assert.assertEquals("https://localhost:8443/path?key=query#fragment", KeycloakUriBuilder.fromUri( + "https://localhost:8443/{path}?key={query}#{fragment}").buildAsString("path", "query", "fragment")); + Assert.assertEquals("https://localhost:8443/%7Bpath%7D?key=%7Bquery%7D#%7Bfragment%7D", KeycloakUriBuilder.fromUri( + "https://localhost:8443/{path}?key={query}#{fragment}", false).buildAsString()); + } } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java index 447685e6d8b2..7c9c4755e79b 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java @@ -196,7 +196,7 @@ private static String decodeRedirectUri(String redirectUri) { int MAX_DECODING_COUNT = 5; // Max count of attempts for decoding URL (in case it was encoded multiple times) try { - KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(redirectUri).preserveDefaultPort(); + KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(redirectUri, false).preserveDefaultPort(); String origQuery = uriBuilder.getQuery(); String origFragment = uriBuilder.getFragment(); String encodedRedirectUri = uriBuilder @@ -209,7 +209,7 @@ private static String decodeRedirectUri(String redirectUri) { decodedRedirectUri = Encode.decode(encodedRedirectUri); if (decodedRedirectUri.equals(encodedRedirectUri)) { // URL is decoded. We can return it (after attach original query and fragment) - return KeycloakUriBuilder.fromUri(decodedRedirectUri).preserveDefaultPort() + return KeycloakUriBuilder.fromUri(decodedRedirectUri, false).preserveDefaultPort() .replaceQuery(origQuery) .fragment(origFragment) .buildAsString(); diff --git a/services/src/test/java/org/keycloak/protocol/oidc/utils/RedirectUtilsTest.java b/services/src/test/java/org/keycloak/protocol/oidc/utils/RedirectUtilsTest.java index ea23040bade9..1d953cd8977d 100644 --- a/services/src/test/java/org/keycloak/protocol/oidc/utils/RedirectUtilsTest.java +++ b/services/src/test/java/org/keycloak/protocol/oidc/utils/RedirectUtilsTest.java @@ -114,4 +114,17 @@ public void testverifyRedirectUriInvalidScheme() { Assert.assertNull(RedirectUtils.verifyRedirectUri(session, null, "custom3:/test", set, false)); } + + @Test + public void testverifyRedirectUriWithCurlyBrackets() { + Set set = Stream.of( + "https://keycloak.org/%7B123%7D", + "https://keycloak.org/parent/*" + ).collect(Collectors.toSet()); + + Assert.assertEquals("https://keycloak.org/%7B123%7D", RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/%7B123%7D", set, false)); + Assert.assertEquals("https://keycloak.org/parent/%7B123%7D", RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/parent/%7B123%7D", set, false)); + + Assert.assertNull(RedirectUtils.verifyRedirectUri(session, null, "https://keycloak.org/%7Babc%7D", set, false)); + } } From 531b8f1300c4701420e090d4124559121c87f7bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Barto=C5=A1?= Date: Thu, 17 Aug 2023 17:36:13 +0200 Subject: [PATCH 040/135] Fix IDELauncher after removing Vert.x Dev UI dependency (#22522) --- quarkus/server/pom.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/quarkus/server/pom.xml b/quarkus/server/pom.xml index 8fa8ef0fb93b..53c1253993f9 100644 --- a/quarkus/server/pom.xml +++ b/quarkus/server/pom.xml @@ -19,6 +19,14 @@ org.keycloak keycloak-quarkus-server + + + + + io.quarkus + quarkus-vertx-http-dev-ui-resources + provided +
From 14c7d96912d2644c43ed1c90d481571fb79c1482 Mon Sep 17 00:00:00 2001 From: Steven Hawkins Date: Mon, 21 Aug 2023 09:58:35 -0400 Subject: [PATCH 041/135] removing build time dependencies from the runtime (#22574) Closes #22496 --- operator/pom.xml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/operator/pom.xml b/operator/pom.xml index 4b9d5c2f8dd4..4abfce4e7629 100644 --- a/operator/pom.xml +++ b/operator/pom.xml @@ -40,13 +40,10 @@ - - io.fabric8 - crd-generator-api - io.fabric8 crd-generator-apt + provided io.fabric8 @@ -178,6 +175,11 @@ build + + + true + + From abf27e7b06184cbe80d7d62cfd72a2e38884ef66 Mon Sep 17 00:00:00 2001 From: Steven Hawkins Date: Mon, 21 Aug 2023 09:59:39 -0400 Subject: [PATCH 042/135] removing the quarkus-minikube dependency (#22575) Closes #22517 --- operator/README.md | 3 ++- operator/pom.xml | 4 ---- operator/src/main/kubernetes/kustomization.yml | 5 +---- operator/src/main/kubernetes/minikube.yml | 1 - 4 files changed, 3 insertions(+), 10 deletions(-) delete mode 120000 operator/src/main/kubernetes/minikube.yml diff --git a/operator/README.md b/operator/README.md index 844555ba71dc..0c1134d30353 100644 --- a/operator/README.md +++ b/operator/README.md @@ -52,12 +52,13 @@ eval $(minikube -p minikube docker-env) Compile the project and generate the Docker image with JIB: ```bash -mvn clean package -Doperator -Dquarkus.container-image.build=true -Dquarkus.kubernetes.deployment-target=minikube +mvn clean package -Dquarkus.kubernetes.image-pull-policy=IfNotPresent -Dquarkus.container-image.build=true ``` Install the CRD definition and the operator in the cluster in the `keycloak` namespace: ```bash +kubectl create namespace keycloak kubectl apply -k target ``` diff --git a/operator/pom.xml b/operator/pom.xml index 4abfce4e7629..340e97815866 100644 --- a/operator/pom.xml +++ b/operator/pom.xml @@ -82,10 +82,6 @@ io.quarkus quarkus-openshift - - io.quarkus - quarkus-minikube - io.quarkus quarkus-kubernetes-client diff --git a/operator/src/main/kubernetes/kustomization.yml b/operator/src/main/kubernetes/kustomization.yml index f3c53b11416e..4ca5d8f50db3 100644 --- a/operator/src/main/kubernetes/kustomization.yml +++ b/operator/src/main/kubernetes/kustomization.yml @@ -6,7 +6,4 @@ namespace: keycloak resources: - kubernetes/keycloaks.k8s.keycloak.org-v1.yml - kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml - - kubernetes/kubernetes.yml - -patchesStrategicMerge: - - kubernetes/minikube.yml + - kubernetes/kubernetes.yml \ No newline at end of file diff --git a/operator/src/main/kubernetes/minikube.yml b/operator/src/main/kubernetes/minikube.yml deleted file mode 120000 index 88d25d07189b..000000000000 --- a/operator/src/main/kubernetes/minikube.yml +++ /dev/null @@ -1 +0,0 @@ -kubernetes.yml \ No newline at end of file From 5f43ed47938e3c01c84d8da57e898324f6a86c68 Mon Sep 17 00:00:00 2001 From: Stan Silvert Date: Mon, 21 Aug 2023 12:31:51 -0400 Subject: [PATCH 043/135] Don't render fed link w/o view-realm access. (#22397) Fixes #22175 --- js/apps/admin-ui/src/user/UserForm.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/apps/admin-ui/src/user/UserForm.tsx b/js/apps/admin-ui/src/user/UserForm.tsx index b121a438a95b..99d0356cfc7f 100644 --- a/js/apps/admin-ui/src/user/UserForm.tsx +++ b/js/apps/admin-ui/src/user/UserForm.tsx @@ -97,6 +97,7 @@ export const UserForm = ({ const { addAlert, addError } = useAlerts(); const { hasAccess } = useAccess(); const isManager = hasAccess("manage-users"); + const canViewFederationLink = hasAccess("view-realm"); const { handleSubmit, @@ -209,7 +210,7 @@ export const UserForm = ({ label="requiredUserActions" help="users-help:requiredUserActions" /> - {(user?.federationLink || user?.origin) && ( + {(user?.federationLink || user?.origin) && canViewFederationLink && ( Date: Mon, 21 Aug 2023 12:32:10 -0400 Subject: [PATCH 044/135] Allow assign realm roles with query-users access. (#22396) Fixes #22079 --- .../src/components/role-mapping/AddRoleMappingModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/apps/admin-ui/src/components/role-mapping/AddRoleMappingModal.tsx b/js/apps/admin-ui/src/components/role-mapping/AddRoleMappingModal.tsx index 6bbd13f43afe..ed9f830b48f0 100644 --- a/js/apps/admin-ui/src/components/role-mapping/AddRoleMappingModal.tsx +++ b/js/apps/admin-ui/src/components/role-mapping/AddRoleMappingModal.tsx @@ -42,7 +42,7 @@ export const AddRoleMappingModal = ({ }: AddRoleMappingModalProps) => { const { t } = useTranslation(type); const { hasAccess } = useAccess(); - const canViewRealmRoles = hasAccess("view-realm"); + const canViewRealmRoles = hasAccess("view-realm") || hasAccess("query-users"); const [searchToggle, setSearchToggle] = useState(false); From 52e4ae07efbbc7ef15d002f5315d3935e576fd3d Mon Sep 17 00:00:00 2001 From: Stan Silvert Date: Mon, 21 Aug 2023 12:32:28 -0400 Subject: [PATCH 045/135] Allow view creds tab if view fine-grained permission (#22394) Fixes #22002 --- js/apps/admin-ui/src/clients/ClientDetails.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/apps/admin-ui/src/clients/ClientDetails.tsx b/js/apps/admin-ui/src/clients/ClientDetails.tsx index 35d1feb87cce..f4a72f814c04 100644 --- a/js/apps/admin-ui/src/clients/ClientDetails.tsx +++ b/js/apps/admin-ui/src/clients/ClientDetails.tsx @@ -453,7 +453,9 @@ export default function ClientDetails() { )} {!client.publicClient && !isRealmClient(client) && - (hasViewClients || client.access?.configure) && ( + (hasViewClients || + client.access?.configure || + client.access?.view) && ( {t("credentials")}} From 46bb5cf301d24bac5d11301bd02ed8b32083183a Mon Sep 17 00:00:00 2001 From: Jon Koops Date: Tue, 22 Aug 2023 18:01:41 +0200 Subject: [PATCH 046/135] Upgrade Prettier to the latest version (#22618) Backports #21601 --- .../src/account-security/DeviceActivity.tsx | 4 +- .../src/account-security/LinkedAccounts.tsx | 4 +- .../src/account-security/SigningIn.tsx | 8 +- js/apps/account-ui/src/api.ts | 18 +-- js/apps/account-ui/src/api/methods.ts | 8 +- js/apps/account-ui/src/api/parse-response.ts | 4 +- js/apps/account-ui/src/api/request.ts | 6 +- .../src/applications/Applications.tsx | 4 +- .../src/components/format/format-date.ts | 4 +- .../src/components/formatter/format-date.ts | 4 +- js/apps/account-ui/src/groups/Groups.tsx | 6 +- js/apps/account-ui/src/main.tsx | 2 +- .../src/personal-info/LocaleSelector.tsx | 4 +- .../src/personal-info/PersonalInfo.tsx | 4 +- .../src/resources/EditTheResource.tsx | 4 +- .../src/resources/PermissionRequest.tsx | 6 +- .../account-ui/src/resources/ResourcesTab.tsx | 12 +- .../src/resources/ShareTheResource.tsx | 8 +- js/apps/account-ui/src/root/PageNav.tsx | 2 +- js/apps/account-ui/src/root/Root.tsx | 2 +- js/apps/account-ui/src/utils/usePromise.ts | 2 +- .../e2e/authentication_policies.spec.ts | 8 +- .../e2e/authentication_policies_ciba.spec.ts | 6 +- .../cypress/e2e/authentication_test.spec.ts | 14 +-- .../e2e/client_authorization_test.spec.ts | 14 +-- .../e2e/client_registration_policies.spec.ts | 2 +- .../cypress/e2e/client_scopes_test.spec.ts | 10 +- .../cypress/e2e/clients_saml_advanced.spec.ts | 2 +- .../cypress/e2e/clients_saml_test.spec.ts | 4 +- .../admin-ui/cypress/e2e/clients_test.spec.ts | 24 ++-- .../admin-ui/cypress/e2e/events_test.spec.ts | 8 +- .../admin-ui/cypress/e2e/group_test.spec.ts | 10 +- .../admin-ui/cypress/e2e/i18n_test.spec.ts | 10 +- .../e2e/identity_providers_oidc_test.spec.ts | 10 +- .../e2e/identity_providers_saml_test.spec.ts | 4 +- .../e2e/identity_providers_test.spec.ts | 16 +-- .../cypress/e2e/masthead_test.spec.ts | 2 +- .../cypress/e2e/partial_export_test.spec.ts | 2 +- .../cypress/e2e/partial_import_test.spec.ts | 2 +- .../cypress/e2e/realm_roles_test.spec.ts | 16 +-- ...ealm_settings_client_policies_test.spec.ts | 12 +- ...ealm_settings_client_profiles_test.spec.ts | 4 +- .../e2e/realm_settings_events_test.spec.ts | 46 ++++---- .../realm_settings_general_tab_test.spec.ts | 12 +- .../e2e/realm_settings_tabs_test.spec.ts | 6 +- .../realm_settings_user_profile_tab.spec.ts | 10 +- .../admin-ui/cypress/e2e/realm_test.spec.ts | 8 +- .../cypress/e2e/sessions_test.spec.ts | 2 +- .../e2e/user_fed_kerberos_test.spec.ts | 4 +- ...ser_fed_ldap_hardcoded_mapper_test.spec.ts | 4 +- .../e2e/user_fed_ldap_mapper_test.spec.ts | 4 +- .../cypress/e2e/user_fed_ldap_test.spec.ts | 58 +++++----- .../admin-ui/cypress/e2e/users_test.spec.ts | 32 +++--- .../cypress/support/forms/FormValidation.ts | 8 +- .../admin-ui/cypress/support/forms/Select.ts | 4 +- .../cypress/support/pages/CommonElements.ts | 4 +- .../cypress/support/pages/LoginPage.ts | 2 +- .../support/pages/admin-ui/Masthead.ts | 2 +- .../support/pages/admin-ui/SidebarPage.ts | 2 +- .../pages/admin-ui/components/FormPage.ts | 2 +- .../pages/admin-ui/components/PageObject.ts | 48 ++++---- .../pages/admin-ui/components/TabPage.ts | 2 +- .../pages/admin-ui/components/TablePage.ts | 28 ++--- .../realm_settings/PartialImportModal.ts | 2 +- .../pages/admin-ui/manage/RoleMappingTab.ts | 2 +- .../manage/authentication/FlowDetail.ts | 6 +- .../manage/authentication/OTPPolicies.ts | 2 +- .../manage/authentication/WebAuthnPolicies.ts | 2 +- .../client_scopes/CreateClientScopePage.ts | 2 +- .../manage/clients/CreateClientPage.ts | 2 +- .../client_details/CreatePermissionPage.ts | 2 +- .../client_details/tabs/AdvancedSamlTab.ts | 2 +- .../client_details/tabs/AdvancedTab.ts | 14 +-- .../clients/client_details/tabs/KeysTab.ts | 2 +- .../tabs/authorization_subtabs/PoliciesTab.ts | 2 +- .../clients/tabs/InitialAccessTokenTab.ts | 2 +- .../manage/events/tabs/AdminEventsTab.ts | 22 ++-- .../manage/events/tabs/UserEventsTab.ts | 20 ++-- .../pages/admin-ui/manage/groups/GroupPage.ts | 8 +- .../groups/group_details/GroupDetailPage.ts | 4 +- .../groups/group_details/tabs/MembersTab.ts | 4 +- .../identity_providers/AddMapperPage.ts | 6 +- .../identity_providers/CreateProviderPage.ts | 4 +- .../ProviderBaseAdvancedSettingsPage.ts | 34 +++--- .../ProviderBaseGeneralSettingsPage.ts | 4 +- .../social/ProviderFacebookGeneralSettings.ts | 6 +- .../social/ProviderGoogleGeneralSettings.ts | 2 +- .../ProviderOpenshiftGeneralSettings.ts | 2 +- .../admin-ui/manage/providers/ProviderPage.ts | 8 +- .../manage/realm_roles/AssociatedRolesPage.ts | 4 +- .../realm_settings/RealmSettingsPage.ts | 104 +++++++++--------- .../AdminEventsSettingsTab.ts | 2 +- .../tabs/IdentityProviderLinksTab.ts | 12 +- .../cypress/support/util/AdminClient.ts | 18 +-- .../support/util/grantClipboardAccess.ts | 2 +- js/apps/admin-ui/src/PageNav.tsx | 6 +- .../authentication/AuthenticationSection.tsx | 6 +- .../src/authentication/BindFlowDialog.tsx | 2 +- .../src/authentication/DuplicateFlowModal.tsx | 4 +- .../src/authentication/EditFlowModal.tsx | 2 +- .../src/authentication/FlowDetails.tsx | 20 ++-- .../src/authentication/RequiredActions.tsx | 12 +- .../components/AddFlowDropdown.tsx | 4 +- .../components/DraggableTable.tsx | 8 +- .../components/ExecutionConfigModal.tsx | 4 +- .../authentication/components/FlowDiagram.tsx | 24 ++-- .../src/authentication/components/FlowRow.tsx | 2 +- .../src/authentication/components/UsedBy.tsx | 6 +- .../components/diagram/ButtonEdge.tsx | 2 +- .../components/modals/AddStepModal.tsx | 6 +- .../components/modals/AddSubFlowModal.tsx | 2 +- .../src/authentication/execution-model.ts | 10 +- .../src/authentication/form/CreateFlow.tsx | 6 +- .../authentication/policies/CibaPolicy.tsx | 4 +- .../src/authentication/policies/OtpPolicy.tsx | 2 +- .../policies/PasswordPolicy.tsx | 4 +- .../src/authentication/policies/Policies.tsx | 2 +- .../policies/WebauthnPolicy.tsx | 6 +- .../src/authentication/policies/util.test.ts | 2 +- .../src/authentication/policies/util.ts | 4 +- .../authentication/routes/Authentication.tsx | 2 +- .../src/client-scopes/ChangeTypeDropdown.tsx | 6 +- .../src/client-scopes/ClientScopesSection.tsx | 10 +- .../src/client-scopes/CreateClientScope.tsx | 2 +- .../src/client-scopes/EditClientScope.tsx | 22 ++-- .../src/client-scopes/add/MapperDialog.tsx | 8 +- .../src/client-scopes/details/MapperList.tsx | 4 +- .../client-scopes/details/MappingDetails.tsx | 10 +- .../src/client-scopes/details/ScopeForm.tsx | 20 ++-- .../client-scopes/routes/NewClientScope.tsx | 2 +- js/apps/admin-ui/src/clients/AdvancedTab.tsx | 16 +-- .../admin-ui/src/clients/ClientDetails.tsx | 20 ++-- .../admin-ui/src/clients/ClientSessions.tsx | 2 +- .../src/clients/add/CapabilityConfig.tsx | 12 +- .../src/clients/add/LoginSettings.tsx | 4 +- .../src/clients/add/LoginSettingsPanel.tsx | 10 +- .../admin-ui/src/clients/add/LogoutPanel.tsx | 14 +-- .../admin-ui/src/clients/add/SamlConfig.tsx | 4 +- .../src/clients/add/SamlSignature.tsx | 10 +- .../src/clients/advanced/AdvancedSettings.tsx | 22 ++-- .../advanced/AuthenticationOverrides.tsx | 2 +- .../src/clients/advanced/ClusteringPanel.tsx | 4 +- .../advanced/FineGrainOpenIdConnect.tsx | 34 +++--- .../OpenIdConnectCompatibilityModes.tsx | 12 +- .../authorization/AuthorizationEvaluate.tsx | 18 +-- .../AuthorizationEvaluateResource.tsx | 2 +- .../authorization/AuthorizationExport.tsx | 4 +- .../src/clients/authorization/DetailCell.tsx | 2 +- .../clients/authorization/ImportDialog.tsx | 2 +- .../authorization/KeyBasedAttributeInput.tsx | 4 +- .../clients/authorization/NewPolicyDialog.tsx | 2 +- .../authorization/PermissionDetails.tsx | 14 +-- .../src/clients/authorization/Permissions.tsx | 14 +-- .../src/clients/authorization/Policies.tsx | 12 +- .../clients/authorization/ResourceDetails.tsx | 6 +- .../src/clients/authorization/Resources.tsx | 8 +- .../authorization/ResourcesPolicySelect.tsx | 12 +- .../clients/authorization/ScopeDetails.tsx | 8 +- .../src/clients/authorization/ScopePicker.tsx | 4 +- .../src/clients/authorization/ScopeSelect.tsx | 10 +- .../src/clients/authorization/Scopes.tsx | 8 +- .../src/clients/authorization/Settings.tsx | 4 +- .../authorization/evaluate/Results.tsx | 6 +- .../clients/authorization/policy/Client.tsx | 8 +- .../authorization/policy/ClientScope.tsx | 10 +- .../clients/authorization/policy/Group.tsx | 6 +- .../authorization/policy/PolicyDetails.tsx | 10 +- .../src/clients/authorization/policy/Role.tsx | 8 +- .../src/clients/authorization/policy/Time.tsx | 2 +- .../src/clients/credentials/ClientSecret.tsx | 2 +- .../src/clients/credentials/Credentials.tsx | 8 +- .../src/clients/credentials/SignedJWT.tsx | 2 +- .../admin-ui/src/clients/credentials/X509.tsx | 4 +- .../src/clients/import/ImportForm.tsx | 8 +- .../CreateInitialAccessToken.tsx | 4 +- .../initial-access/InitialAccessTokenList.tsx | 2 +- .../src/clients/keys/ExportSamlKeyDialog.tsx | 4 +- js/apps/admin-ui/src/clients/keys/Keys.tsx | 10 +- .../admin-ui/src/clients/keys/SamlKeys.tsx | 8 +- .../src/clients/keys/SamlKeysDialog.tsx | 8 +- .../registration/AddProviderDialog.tsx | 8 +- .../registration/ClientRegistrationList.tsx | 4 +- .../clients/registration/DetailProvider.tsx | 4 +- .../src/clients/roles/CreateClientRole.tsx | 2 +- .../routes/AddRegistrationProvider.tsx | 2 +- .../src/clients/routes/AuthenticationTab.tsx | 2 +- .../src/clients/routes/ClientRegistration.tsx | 2 +- .../src/clients/routes/ClientScopeTab.tsx | 2 +- .../routes/CreateInitialAccessToken.tsx | 4 +- .../clients/routes/DedicatedScopeDetails.tsx | 2 +- .../admin-ui/src/clients/routes/Mapper.tsx | 2 +- .../src/clients/routes/NewPermission.tsx | 2 +- .../admin-ui/src/clients/routes/NewPolicy.tsx | 2 +- .../src/clients/routes/PermissionDetails.tsx | 4 +- .../src/clients/routes/PolicyDetails.tsx | 4 +- .../admin-ui/src/clients/routes/Resource.tsx | 2 +- .../src/clients/scopes/AddScopeDialog.tsx | 2 +- .../src/clients/scopes/ClientScopes.tsx | 18 +-- .../src/clients/scopes/DecicatedScope.tsx | 6 +- .../src/clients/scopes/DedicatedScopes.tsx | 8 +- .../src/clients/scopes/EvaluateScopes.tsx | 10 +- .../service-account/ServiceAccount.tsx | 6 +- .../admin-ui/src/components/SwitchControl.tsx | 6 +- .../admin-ui/src/components/alert/Alerts.tsx | 8 +- .../bread-crumb/GroupBreadCrumbs.tsx | 2 +- .../bread-crumb/PageBreadCrumbs.tsx | 2 +- .../client-scope/ClientScopeTypes.tsx | 20 ++-- .../src/components/client/ClientSelect.tsx | 2 +- .../confirm-dialog/ConfirmDialog.tsx | 2 +- .../download-dialog/DownloadDialog.tsx | 16 +-- .../dynamic/MultivaluedListComponent.tsx | 2 +- .../src/components/form/FormAccess.tsx | 6 +- .../components/group/GroupPickerDialog.tsx | 8 +- .../patternfly/FileUpload.tsx | 4 +- .../patternfly/FileUploadField.tsx | 12 +- .../components/key-value-form/KeySelect.tsx | 2 +- .../components/key-value-form/ValueSelect.tsx | 2 +- .../key-value-form/key-value-convert.ts | 2 +- .../password-input/PasswordInput.tsx | 2 +- .../permission-tab/PermissionTab.tsx | 10 +- .../realm-selector/RealmSelector.tsx | 10 +- .../role-mapping/AddRoleMappingModal.tsx | 8 +- .../components/role-mapping/RoleMapping.tsx | 6 +- .../src/components/role-mapping/queries.ts | 12 +- .../src/components/role-mapping/resource.ts | 4 +- .../src/components/roles-list/RolesList.tsx | 6 +- .../components/routable-tabs/RoutableTabs.tsx | 2 +- .../src/components/scroll-form/ScrollForm.tsx | 2 +- .../table-toolbar/KeycloakDataTable.tsx | 30 +++-- .../components/time-selector/TimeSelector.tsx | 8 +- .../src/components/users/UserDataTable.tsx | 10 +- .../UserDataTableAttributeSearchForm.tsx | 2 +- .../src/components/users/UserSelect.tsx | 6 +- .../admin-ui/src/context/RealmsContext.tsx | 6 +- js/apps/admin-ui/src/context/RecentRealms.tsx | 6 +- .../admin-ui/src/context/access/Access.tsx | 2 +- .../src/context/auth/admin-ui-endpoint.ts | 4 +- .../context/realm-context/RealmContext.tsx | 4 +- .../admin-ui/src/context/whoami/WhoAmI.tsx | 4 +- js/apps/admin-ui/src/dashboard/Dashboard.tsx | 14 +-- .../admin-ui/src/dashboard/ProviderInfo.tsx | 8 +- js/apps/admin-ui/src/events/AdminEvents.tsx | 12 +- js/apps/admin-ui/src/events/EventsSection.tsx | 10 +- js/apps/admin-ui/src/events/ResourceLinks.tsx | 4 +- .../admin-ui/src/groups/GroupRoleMapping.tsx | 4 +- js/apps/admin-ui/src/groups/GroupTable.tsx | 2 +- js/apps/admin-ui/src/groups/GroupsModal.tsx | 4 +- js/apps/admin-ui/src/groups/GroupsSection.tsx | 2 +- js/apps/admin-ui/src/groups/Members.tsx | 12 +- js/apps/admin-ui/src/groups/MembersModal.tsx | 6 +- .../admin-ui/src/groups/SubGroupsContext.tsx | 4 +- .../groups/components/CheckableTreeView.tsx | 6 +- .../src/groups/components/GroupTree.tsx | 10 +- .../src/groups/components/MoveDialog.tsx | 2 +- js/apps/admin-ui/src/i18n/OverridesBackend.ts | 10 +- .../IdentityProvidersSection.tsx | 6 +- .../identity-providers/ManageOrderDialog.tsx | 2 +- .../add/AddIdentityProvider.tsx | 4 +- .../src/identity-providers/add/AddMapper.tsx | 10 +- .../identity-providers/add/AddMapperForm.tsx | 2 +- .../add/AddOpenIdConnect.tsx | 4 +- .../identity-providers/add/AddSamlConnect.tsx | 2 +- .../add/AdvancedSettings.tsx | 2 +- .../add/DescriptorSettings.tsx | 4 +- .../identity-providers/add/DetailSettings.tsx | 24 ++-- .../add/ExtendedNonDiscoverySettings.tsx | 2 +- .../add/OpenIdConnectSettings.tsx | 2 +- .../add/SamlConnectSettings.tsx | 6 +- .../component/DiscoveryEndpointField.tsx | 6 +- .../component/RedirectUrl.tsx | 2 +- .../identity-providers/routes/AddMapper.tsx | 2 +- .../identity-providers/routes/EditMapper.tsx | 2 +- .../routes/IdentityProvider.tsx | 2 +- .../routes/IdentityProviderCreate.tsx | 2 +- .../routes/IdentityProviderKeycloakOidc.tsx | 2 +- .../routes/IdentityProviderOidc.tsx | 2 +- .../routes/IdentityProviderSaml.tsx | 2 +- .../routes/IdentityProviders.tsx | 4 +- js/apps/admin-ui/src/main.tsx | 2 +- .../src/realm-roles/RealmRoleTabs.tsx | 8 +- .../realm-settings/AddClientProfileModal.tsx | 4 +- .../src/realm-settings/ClientProfileForm.tsx | 16 +-- .../src/realm-settings/DefaultGroupsTab.tsx | 14 +-- .../admin-ui/src/realm-settings/EmailTab.tsx | 2 +- .../src/realm-settings/ExecutorForm.tsx | 20 ++-- .../src/realm-settings/GeneralTab.tsx | 10 +- .../src/realm-settings/LocalizationTab.tsx | 22 ++-- .../admin-ui/src/realm-settings/LoginTab.tsx | 2 +- .../realm-settings/NewAttributeSettings.tsx | 26 +++-- .../NewClientPolicyCondition.tsx | 20 ++-- .../realm-settings/NewClientPolicyForm.tsx | 36 +++--- .../src/realm-settings/PartialExport.tsx | 2 +- .../src/realm-settings/PartialImport.tsx | 14 +-- .../src/realm-settings/PoliciesTab.tsx | 10 +- .../src/realm-settings/ProfilesTab.tsx | 13 ++- .../src/realm-settings/RealmSettingsTabs.tsx | 10 +- .../admin-ui/src/realm-settings/TokensTab.tsx | 8 +- .../src/realm-settings/UserRegistration.tsx | 4 +- .../event-config/AddEventTypesDialog.tsx | 2 +- .../realm-settings/event-config/EventsTab.tsx | 14 +-- .../src/realm-settings/keys/KeysListTab.tsx | 6 +- .../realm-settings/keys/KeysProvidersTab.tsx | 10 +- .../src/realm-settings/keys/KeysTab.tsx | 2 +- .../keys/key-providers/KeyProviderForm.tsx | 6 +- .../realm-settings/routes/AddClientPolicy.tsx | 2 +- .../routes/AddClientProfile.tsx | 2 +- .../realm-settings/routes/AddCondition.tsx | 4 +- .../realm-settings/routes/ClientPolicies.tsx | 2 +- .../realm-settings/routes/ClientProfile.tsx | 2 +- .../routes/EditAttributesGroup.tsx | 4 +- .../routes/EditClientPolicy.tsx | 2 +- .../realm-settings/routes/EditCondition.tsx | 4 +- .../src/realm-settings/routes/KeyProvider.tsx | 2 +- .../routes/NewAttributesGroup.tsx | 4 +- .../security-defences/BruteForceDetection.tsx | 6 +- .../user-profile/AttributesGroupForm.tsx | 4 +- .../user-profile/AttributesGroupTab.tsx | 4 +- .../user-profile/AttributesTab.tsx | 14 ++- .../user-profile/UserProfileContext.tsx | 8 +- .../attribute/AddValidatorDialog.tsx | 2 +- .../attribute/AttributeGeneralSettings.tsx | 10 +- .../attribute/AttributeValidations.tsx | 2 +- .../attribute/ValidatorSelect.tsx | 2 +- js/apps/admin-ui/src/root/AuthWall.tsx | 2 +- .../admin-ui/src/sessions/RevocationModal.tsx | 12 +- .../admin-ui/src/sessions/SessionsSection.tsx | 2 +- .../admin-ui/src/sessions/SessionsTable.tsx | 2 +- .../user-federation/ManagePriorityDialog.tsx | 4 +- .../UserFederationKerberosSettings.tsx | 2 +- .../UserFederationLdapForm.tsx | 2 +- .../UserFederationLdapSettings.tsx | 8 +- .../user-federation/UserFederationSection.tsx | 6 +- .../custom/CustomProviderSettings.tsx | 6 +- .../kerberos/KerberosSettingsRequired.tsx | 4 +- .../ldap/LdapSettingsAdvanced.tsx | 4 +- .../ldap/LdapSettingsConnection.tsx | 6 +- .../ldap/LdapSettingsGeneral.tsx | 12 +- .../ldap/LdapSettingsKerberosIntegration.tsx | 4 +- .../ldap/mappers/LdapMapperDetails.tsx | 20 ++-- .../ldap/mappers/LdapMapperList.tsx | 6 +- .../routes/CustomUserFederation.tsx | 4 +- .../routes/NewCustomUserFederation.tsx | 4 +- .../routes/NewKerberosUserFederation.tsx | 4 +- .../routes/NewLdapUserFederation.tsx | 4 +- .../user-federation/routes/UserFederation.tsx | 2 +- .../routes/UserFederationKerberos.tsx | 4 +- .../routes/UserFederationLdap.tsx | 4 +- .../routes/UserFederationLdapMapper.tsx | 4 +- .../routes/UserFederationsKerberos.tsx | 2 +- .../routes/UserFederationsLdap.tsx | 2 +- .../user-federation/shared/ExtendedHeader.tsx | 4 +- .../user-federation/shared/SettingsCache.tsx | 4 +- js/apps/admin-ui/src/user/EditUser.tsx | 8 +- .../admin-ui/src/user/FederatedUserLink.tsx | 2 +- js/apps/admin-ui/src/user/UserCredentials.tsx | 22 ++-- js/apps/admin-ui/src/user/UserForm.tsx | 2 +- js/apps/admin-ui/src/user/UserGroups.tsx | 14 +-- js/apps/admin-ui/src/user/UserIdPModal.tsx | 2 +- .../src/user/UserIdentityProviderLinks.tsx | 10 +- .../admin-ui/src/user/UserProfileFields.tsx | 6 +- js/apps/admin-ui/src/user/UserRoleMapping.tsx | 4 +- js/apps/admin-ui/src/user/UsersSection.tsx | 2 +- .../user/user-credentials/CredentialRow.tsx | 2 +- .../user/user-credentials/InlineLabelEdit.tsx | 2 +- .../RequiredActionMultiSelect.tsx | 4 +- .../user-credentials/ResetPasswordDialog.tsx | 6 +- js/apps/admin-ui/src/util.ts | 24 ++-- js/apps/admin-ui/src/utils/client-url.ts | 4 +- .../src/utils/getAuthorizationHeaders.ts | 2 +- js/apps/admin-ui/src/utils/types.ts | 2 +- js/apps/admin-ui/src/utils/useFetch.ts | 2 +- js/apps/admin-ui/src/utils/useLocaleSort.ts | 4 +- .../admin-ui/src/utils/useSetTimeout.test.ts | 2 +- js/apps/admin-ui/src/utils/useSetTimeout.ts | 2 +- .../src/resources/agent.ts | 12 +- .../src/resources/clientScopes.ts | 4 +- .../src/resources/clients.ts | 6 +- .../src/resources/resource.ts | 12 +- .../src/resources/users.ts | 4 +- .../keycloak-admin-client/src/utils/auth.ts | 4 +- .../src/utils/fetchWithError.ts | 2 +- .../src/utils/stringifyQueryParams.ts | 2 +- .../keycloak-admin-client/test/auth.spec.ts | 4 +- .../test/authenticationManagement.spec.ts | 18 +-- .../test/clientScopes.spec.ts | 38 +++---- .../test/clients.spec.ts | 68 ++++++------ .../test/components.spec.ts | 2 +- .../test/groupUser.spec.ts | 8 +- .../keycloak-admin-client/test/groups.spec.ts | 4 +- .../keycloak-admin-client/test/idp.spec.ts | 8 +- .../keycloak-admin-client/test/realms.spec.ts | 20 ++-- .../keycloak-admin-client/test/roles.spec.ts | 6 +- .../test/stringifyQueryParams.spec.ts | 4 +- .../keycloak-admin-client/test/users.spec.ts | 16 +-- .../keycloak-masthead/src/DefaultAvatar.tsx | 2 +- js/libs/keycloak-masthead/src/Masthead.tsx | 4 +- .../src/translation/useTranslation.ts | 6 +- js/libs/ui-shared/src/alerts/Alerts.tsx | 4 +- js/libs/ui-shared/src/context/HelpContext.tsx | 2 +- .../ui-shared/src/controls/SelectControl.tsx | 6 +- .../ui-shared/src/controls/SwitchControl.tsx | 6 +- .../src/controls/TextAreaControl.tsx | 6 +- .../ui-shared/src/controls/TextControl.tsx | 6 +- .../ui-shared/src/utils/useRequiredContext.ts | 2 +- js/libs/ui-shared/src/utils/useStorageItem.ts | 4 +- js/libs/ui-shared/src/utils/useStoredState.ts | 8 +- js/package.json | 4 +- js/pnpm-lock.yaml | 30 ++--- 408 files changed, 1535 insertions(+), 1514 deletions(-) diff --git a/js/apps/account-ui/src/account-security/DeviceActivity.tsx b/js/apps/account-ui/src/account-security/DeviceActivity.tsx index f60b4c963701..25d3c6e5bf56 100644 --- a/js/apps/account-ui/src/account-security/DeviceActivity.tsx +++ b/js/apps/account-ui/src/account-security/DeviceActivity.tsx @@ -69,7 +69,7 @@ const DeviceActivity = () => { const signOutSession = async ( session: SessionRepresentation, - device: DeviceRepresentation + device: DeviceRepresentation, ) => { try { await deleteSession(session.id); @@ -233,7 +233,7 @@ const DeviceActivity = () => { - )) + )), )} diff --git a/js/apps/account-ui/src/account-security/LinkedAccounts.tsx b/js/apps/account-ui/src/account-security/LinkedAccounts.tsx index d18790a6300a..803c7e7cbdcd 100644 --- a/js/apps/account-ui/src/account-security/LinkedAccounts.tsx +++ b/js/apps/account-ui/src/account-security/LinkedAccounts.tsx @@ -19,12 +19,12 @@ const LinkedAccounts = () => { const linkedAccounts = useMemo( () => accounts.filter((account) => account.connected), - [accounts] + [accounts], ); const unLinkedAccounts = useMemo( () => accounts.filter((account) => !account.connected), - [accounts] + [accounts], ); return ( diff --git a/js/apps/account-ui/src/account-security/SigningIn.tsx b/js/apps/account-ui/src/account-security/SigningIn.tsx index e8ac936124fa..9ce181d0862d 100644 --- a/js/apps/account-ui/src/account-security/SigningIn.tsx +++ b/js/apps/account-ui/src/account-security/SigningIn.tsx @@ -76,7 +76,7 @@ const SigningIn = () => { usePromise((signal) => getCredentials({ signal }), setCredentials, [key]); const credentialRowCells = ( - credMetadata: CredentialMetadataRepresentation + credMetadata: CredentialMetadataRepresentation, ) => { const credential = credMetadata.credential; const maxWidth = { "--pf-u-max-width--MaxWidth": "300px" } as CSSProperties; @@ -98,7 +98,7 @@ const SigningIn = () => { {{ date: formatDate(new Date(credential.createdDate)) }} - + , ); } return items; @@ -188,7 +188,7 @@ const SigningIn = () => { addAlert( t("successRemovedMessage", { userLabel: label(meta.credential), - }) + }), ); refresh(); } catch (error) { @@ -196,7 +196,7 @@ const SigningIn = () => { t("errorRemovedMessage", { userLabel: label(meta.credential), error, - }).toString() + }).toString(), ); } }} diff --git a/js/apps/account-ui/src/api.ts b/js/apps/account-ui/src/api.ts index ea7ed82244ac..3f9e5a6ef140 100644 --- a/js/apps/account-ui/src/api.ts +++ b/js/apps/account-ui/src/api.ts @@ -7,13 +7,13 @@ import { joinPath } from "./utils/joinPath"; export const fetchResources = async ( params: RequestInit, requestParams: Record, - shared: boolean | undefined = false + shared: boolean | undefined = false, ): Promise<{ data: Resource[]; links: Links }> => { const response = await get( `/resources${shared ? "/shared-with-me?" : "?"}${new URLSearchParams( - requestParams + requestParams, )}`, - params + params, ); let links: Links; @@ -32,11 +32,11 @@ export const fetchResources = async ( export const fetchPermission = async ( params: RequestInit, - resourceId: string + resourceId: string, ): Promise => { const response = await request( `/resources/${resourceId}/permissions`, - params + params, ); return checkResponse(response); }; @@ -44,7 +44,7 @@ export const fetchPermission = async ( export const updateRequest = ( resourceId: string, username: string, - scopes: Scope[] | string[] + scopes: Scope[] | string[], ) => request(`/resources/${resourceId}/permissions`, { method: "put", @@ -53,7 +53,7 @@ export const updateRequest = ( export const updatePermissions = ( resourceId: string, - permissions: Permission[] + permissions: Permission[], ) => request(`/resources/${resourceId}/permissions`, { method: "put", @@ -71,7 +71,7 @@ async function get(path: string, params: RequestInit): Promise { "realms", environment.realm, "account", - path + path, ); const response = await fetch(url, { @@ -90,7 +90,7 @@ async function get(path: string, params: RequestInit): Promise { async function request( path: string, - params: RequestInit + params: RequestInit, ): Promise { const response = await get(path, params); if (response.status !== 204) return response.json(); diff --git a/js/apps/account-ui/src/api/methods.ts b/js/apps/account-ui/src/api/methods.ts index fe431b231841..0ba65ade1233 100644 --- a/js/apps/account-ui/src/api/methods.ts +++ b/js/apps/account-ui/src/api/methods.ts @@ -37,7 +37,7 @@ export async function getSupportedLocales({ } export async function savePersonalInfo( - info: UserRepresentation + info: UserRepresentation, ): Promise { const response = await request("/", { body: info, method: "POST" }); if (!response.ok) { @@ -49,11 +49,11 @@ export async function savePersonalInfo( export async function getPermissionRequests( resourceId: string, - { signal }: CallOptions = {} + { signal }: CallOptions = {}, ): Promise { const response = await request( `/resources/${resourceId}/permissions/requests`, - { signal } + { signal }, ); return parseResponse(response); @@ -110,7 +110,7 @@ export async function unLinkAccount(account: LinkedAccountRepresentation) { export async function linkAccount(account: LinkedAccountRepresentation) { const redirectUri = encodeURIComponent( - joinPath(environment.authUrl, "realms", environment.realm, "account") + joinPath(environment.authUrl, "realms", environment.realm, "account"), ); const response = await request("/linked-accounts/" + account.providerName, { searchParams: { providerId: account.providerName, redirectUri }, diff --git a/js/apps/account-ui/src/api/parse-response.ts b/js/apps/account-ui/src/api/parse-response.ts index 6af206eb3e8e..7599b047727a 100644 --- a/js/apps/account-ui/src/api/parse-response.ts +++ b/js/apps/account-ui/src/api/parse-response.ts @@ -9,7 +9,7 @@ export async function parseResponse(response: Response): Promise { if (!isJSON) { throw new Error( - `Expected response to have a JSON content type, got '${contentType}' instead.` + `Expected response to have a JSON content type, got '${contentType}' instead.`, ); } @@ -48,6 +48,6 @@ function getErrorMessage(data: unknown): string { } throw new Error( - "Unable to retrieve error message from response, no matching key found." + "Unable to retrieve error message from response, no matching key found.", ); } diff --git a/js/apps/account-ui/src/api/request.ts b/js/apps/account-ui/src/api/request.ts index 14fad416e051..dc8de77c605b 100644 --- a/js/apps/account-ui/src/api/request.ts +++ b/js/apps/account-ui/src/api/request.ts @@ -12,15 +12,15 @@ export type RequestOptions = { export async function request( path: string, - { signal, method, searchParams, body }: RequestOptions = {} + { signal, method, searchParams, body }: RequestOptions = {}, ): Promise { const url = new URL( - joinPath(environment.authUrl, "realms", environment.realm, "account", path) + joinPath(environment.authUrl, "realms", environment.realm, "account", path), ); if (searchParams) { Object.entries(searchParams).forEach(([key, value]) => - url.searchParams.set(key, value) + url.searchParams.set(key, value), ); } diff --git a/js/apps/account-ui/src/applications/Applications.tsx b/js/apps/account-ui/src/applications/Applications.tsx index f669af0e5a07..cb3cb84b8ca6 100644 --- a/js/apps/account-ui/src/applications/Applications.tsx +++ b/js/apps/account-ui/src/applications/Applications.tsx @@ -44,13 +44,13 @@ const Applications = () => { usePromise( (signal) => getApplications({ signal }), (clients) => setApplications(clients.map((c) => ({ ...c, open: false }))), - [key] + [key], ); const toggleOpen = (clientId: string) => { setApplications([ ...applications!.map((a) => - a.clientId === clientId ? { ...a, open: !a.open } : a + a.clientId === clientId ? { ...a, open: !a.open } : a, ), ]); }; diff --git a/js/apps/account-ui/src/components/format/format-date.ts b/js/apps/account-ui/src/components/format/format-date.ts index 1193b7409c89..5553edd2ca24 100644 --- a/js/apps/account-ui/src/components/format/format-date.ts +++ b/js/apps/account-ui/src/components/format/format-date.ts @@ -16,13 +16,13 @@ export default function useFormatter() { return { formatDate: function ( date: Date, - options: Intl.DateTimeFormatOptions | undefined = DATE_AND_TIME_FORMAT + options: Intl.DateTimeFormatOptions | undefined = DATE_AND_TIME_FORMAT, ) { return date.toLocaleString("en", options); }, formatTime: function ( time: number, - options: Intl.DateTimeFormatOptions | undefined = TIME_FORMAT + options: Intl.DateTimeFormatOptions | undefined = TIME_FORMAT, ) { return new Intl.DateTimeFormat("en", options).format(time); }, diff --git a/js/apps/account-ui/src/components/formatter/format-date.ts b/js/apps/account-ui/src/components/formatter/format-date.ts index a0c5cb9f81d5..534c087896ec 100644 --- a/js/apps/account-ui/src/components/formatter/format-date.ts +++ b/js/apps/account-ui/src/components/formatter/format-date.ts @@ -15,13 +15,13 @@ export default function useFormatter() { return { formatDate: function ( date: Date, - options: Intl.DateTimeFormatOptions | undefined = DATE_AND_TIME_FORMAT + options: Intl.DateTimeFormatOptions | undefined = DATE_AND_TIME_FORMAT, ) { return date.toLocaleString("en", options); }, formatTime: function ( time: number, - options: Intl.DateTimeFormatOptions | undefined = TIME_FORMAT + options: Intl.DateTimeFormatOptions | undefined = TIME_FORMAT, ) { return new Intl.DateTimeFormat("en", options).format(time); }, diff --git a/js/apps/account-ui/src/groups/Groups.tsx b/js/apps/account-ui/src/groups/Groups.tsx index de452bda4819..333bc1ef7b59 100644 --- a/js/apps/account-ui/src/groups/Groups.tsx +++ b/js/apps/account-ui/src/groups/Groups.tsx @@ -27,13 +27,13 @@ const Groups = () => { getParents( el, groups, - groups.map(({ path }) => path) - ) + groups.map(({ path }) => path), + ), ); } setGroups(groups); }, - [directMembership] + [directMembership], ); const getParents = (el: Group, groups: Group[], groupsPaths: string[]) => { diff --git a/js/apps/account-ui/src/main.tsx b/js/apps/account-ui/src/main.tsx index 9ec90591bc77..beb3ee08296c 100644 --- a/js/apps/account-ui/src/main.tsx +++ b/js/apps/account-ui/src/main.tsx @@ -25,5 +25,5 @@ const root = createRoot(container!); root.render( - + , ); diff --git a/js/apps/account-ui/src/personal-info/LocaleSelector.tsx b/js/apps/account-ui/src/personal-info/LocaleSelector.tsx index be8bc077bb50..27832c362ff3 100644 --- a/js/apps/account-ui/src/personal-info/LocaleSelector.tsx +++ b/js/apps/account-ui/src/personal-info/LocaleSelector.tsx @@ -24,8 +24,8 @@ export const LocaleSelector = () => { locales.map((locale) => ({ key: locale, value: localeToDisplayName(locale) || "", - })) - ) + })), + ), ); return ( diff --git a/js/apps/account-ui/src/personal-info/PersonalInfo.tsx b/js/apps/account-ui/src/personal-info/PersonalInfo.tsx index 83023f6ed058..200cee47f496 100644 --- a/js/apps/account-ui/src/personal-info/PersonalInfo.tsx +++ b/js/apps/account-ui/src/personal-info/PersonalInfo.tsx @@ -48,7 +48,7 @@ const PersonalInfo = () => { (personalInfo) => { setUserProfileMetadata(personalInfo.userProfileMetadata); reset(personalInfo); - } + }, ); const onSubmit = async (user: UserRepresentation) => { @@ -61,7 +61,7 @@ const PersonalInfo = () => { (error as FieldError[]).forEach((e) => { const params = Object.assign( {}, - e.params.map((p) => (isBundleKey(p) ? unWrap(p) : p)) + e.params.map((p) => (isBundleKey(p) ? unWrap(p) : p)), ); setError(fieldName(e.field) as keyof UserRepresentation, { message: t(e.errorMessage as TFuncKey, { diff --git a/js/apps/account-ui/src/resources/EditTheResource.tsx b/js/apps/account-ui/src/resources/EditTheResource.tsx index 6f02e8149277..5a3681d9a40f 100644 --- a/js/apps/account-ui/src/resources/EditTheResource.tsx +++ b/js/apps/account-ui/src/resources/EditTheResource.tsx @@ -39,8 +39,8 @@ export const EditTheResource = ({ try { await Promise.all( permissions.map((permission) => - updatePermissions(resource._id, [permission]) - ) + updatePermissions(resource._id, [permission]), + ), ); addAlert(t("updateSuccess")); onClose(); diff --git a/js/apps/account-ui/src/resources/PermissionRequest.tsx b/js/apps/account-ui/src/resources/PermissionRequest.tsx index 066e13ec8342..407cdb414feb 100644 --- a/js/apps/account-ui/src/resources/PermissionRequest.tsx +++ b/js/apps/account-ui/src/resources/PermissionRequest.tsx @@ -40,12 +40,12 @@ export const PermissionRequest = ({ const approveDeny = async ( shareRequest: Permission, - approve: boolean = false + approve: boolean = false, ) => { try { const permissions = await fetchPermission({}, resource._id); const { scopes, username } = permissions.find( - (p) => p.username === shareRequest.username + (p) => p.username === shareRequest.username, )!; await updateRequest( @@ -53,7 +53,7 @@ export const PermissionRequest = ({ username, approve ? [...(scopes as string[]), ...(shareRequest.scopes as string[])] - : scopes + : scopes, ); addAlert(t("shareSuccess")); toggle(); diff --git a/js/apps/account-ui/src/resources/ResourcesTab.tsx b/js/apps/account-ui/src/resources/ResourcesTab.tsx index eac345e953eb..075359512d2e 100644 --- a/js/apps/account-ui/src/resources/ResourcesTab.tsx +++ b/js/apps/account-ui/src/resources/ResourcesTab.tsx @@ -71,8 +71,8 @@ export const ResourcesTab = () => { await Promise.all( result.data.map( async (r) => - (r.shareRequests = await getPermissionRequests(r._id, { signal })) - ) + (r.shareRequests = await getPermissionRequests(r._id, { signal })), + ), ); return result; }, @@ -80,7 +80,7 @@ export const ResourcesTab = () => { setResources(data); setLinks(links); }, - [params, key] + [params, key], ); if (!resources) { @@ -102,7 +102,7 @@ export const ResourcesTab = () => { ({ username, scopes: [], - } as Permission) + }) as Permission, )!; await updatePermissions(resource._id, permissions); setDetails({}); @@ -115,7 +115,7 @@ export const ResourcesTab = () => { const toggleOpen = async ( id: string, field: keyof PermissionDetail, - open: boolean + open: boolean, ) => { const permissions = await fetchPermissions(id); @@ -162,7 +162,7 @@ export const ResourcesTab = () => { toggleOpen( resource._id, "rowOpen", - !details[resource._id]?.rowOpen + !details[resource._id]?.rowOpen, ), }} /> diff --git a/js/apps/account-ui/src/resources/ShareTheResource.tsx b/js/apps/account-ui/src/resources/ShareTheResource.tsx index 383b80f13325..22fe4404b506 100644 --- a/js/apps/account-ui/src/resources/ShareTheResource.tsx +++ b/js/apps/account-ui/src/resources/ShareTheResource.tsx @@ -70,7 +70,7 @@ export const ShareTheResource = ({ }); const isDisabled = watchFields.every( - ({ value }) => value.trim().length === 0 + ({ value }) => value.trim().length === 0, ); const addShare = async ({ usernames, permissions }: FormValues) => { @@ -79,8 +79,8 @@ export const ShareTheResource = ({ usernames .filter(({ value }) => value !== "") .map(({ value: username }) => - updateRequest(resource._id, username, permissions) - ) + updateRequest(resource._id, username, permissions), + ), ); addAlert(t("shareSuccess")); onClose(); @@ -175,7 +175,7 @@ export const ShareTheResource = ({ remove(index)}> {field.value} - ) + ), )} )} diff --git a/js/apps/account-ui/src/root/PageNav.tsx b/js/apps/account-ui/src/root/PageNav.tsx index b2138828c6b9..d4fd853fbb5b 100644 --- a/js/apps/account-ui/src/root/PageNav.tsx +++ b/js/apps/account-ui/src/root/PageNav.tsx @@ -99,7 +99,7 @@ function NavMenuItem({ menuItem }: NavMenuItemProps) { const { pathname } = useLocation(); const isActive = useMemo( () => matchMenuItem(pathname, menuItem), - [pathname, menuItem] + [pathname, menuItem], ); if ("path" in menuItem) { diff --git a/js/apps/account-ui/src/root/Root.tsx b/js/apps/account-ui/src/root/Root.tsx index ad1200135d2a..5f70cb2872da 100644 --- a/js/apps/account-ui/src/root/Root.tsx +++ b/js/apps/account-ui/src/root/Root.tsx @@ -52,7 +52,7 @@ export const Root = () => { signOut: t("signOut"), unknownUser: t("unknownUser"), }), - [t] + [t], ); return ( diff --git a/js/apps/account-ui/src/utils/usePromise.ts b/js/apps/account-ui/src/utils/usePromise.ts index d9e40b06f84e..c05e485988d2 100644 --- a/js/apps/account-ui/src/utils/usePromise.ts +++ b/js/apps/account-ui/src/utils/usePromise.ts @@ -45,7 +45,7 @@ export type PromiseResolvedFn = (value: T) => void; export function usePromise( factory: PromiseFactoryFn, callback: PromiseResolvedFn, - deps: DependencyList = [] + deps: DependencyList = [], ) { useEffect(() => { const controller = new AbortController(); diff --git a/js/apps/admin-ui/cypress/e2e/authentication_policies.spec.ts b/js/apps/admin-ui/cypress/e2e/authentication_policies.spec.ts index 8050c7450ebd..66559fdf1e17 100644 --- a/js/apps/admin-ui/cypress/e2e/authentication_policies.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/authentication_policies.spec.ts @@ -23,7 +23,7 @@ describe("Policies", () => { otpPoliciesPage.checkSupportedApplications( "FreeOTP", "Google Authenticator", - "Microsoft Authenticator" + "Microsoft Authenticator", ); otpPoliciesPage.setPolicyType("hotp").increaseInitialCounter().save(); masthead.checkNotificationMessage("OTP policy successfully updated"); @@ -48,7 +48,7 @@ describe("Policies", () => { }); webauthnPage.webAuthnPolicyCreateTimeout(30).save(); masthead.checkNotificationMessage( - "Updated webauthn policies successfully" + "Updated webauthn policies successfully", ); }); @@ -61,11 +61,11 @@ describe("Policies", () => { webAuthnPolicyRequireResidentKey: "Yes", webAuthnPolicyUserVerificationRequirement: "Preferred", }, - true + true, ) .save(); masthead.checkNotificationMessage( - "Updated webauthn policies successfully" + "Updated webauthn policies successfully", ); }); }); diff --git a/js/apps/admin-ui/cypress/e2e/authentication_policies_ciba.spec.ts b/js/apps/admin-ui/cypress/e2e/authentication_policies_ciba.spec.ts index 578d1a572edb..e32afe92e599 100644 --- a/js/apps/admin-ui/cypress/e2e/authentication_policies_ciba.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/authentication_policies_ciba.spec.ts @@ -28,7 +28,7 @@ describe("Authentication - Policies - CIBA", () => { it("displays the initial state", () => { Select.assertSelectedItem( CIBAPolicyPage.getBackchannelTokenDeliveryModeSelect(), - "Poll" + "Poll", ); CIBAPolicyPage.getExpiresInput().should("have.value", "120"); CIBAPolicyPage.getIntervalInput().should("have.value", "5"); @@ -65,7 +65,7 @@ describe("Authentication - Policies - CIBA", () => { // Select new values for fields. Select.selectItem( CIBAPolicyPage.getBackchannelTokenDeliveryModeSelect(), - "Ping" + "Ping", ); CIBAPolicyPage.getExpiresInput().clear().type("140"); CIBAPolicyPage.getIntervalInput().clear().type("20"); @@ -77,7 +77,7 @@ describe("Authentication - Policies - CIBA", () => { // Assert values are saved. Select.assertSelectedItem( CIBAPolicyPage.getBackchannelTokenDeliveryModeSelect(), - "Ping" + "Ping", ); CIBAPolicyPage.getExpiresInput().should("have.value", "140"); CIBAPolicyPage.getIntervalInput().should("have.value", "20"); diff --git a/js/apps/admin-ui/cypress/e2e/authentication_test.spec.ts b/js/apps/admin-ui/cypress/e2e/authentication_test.spec.ts index f7eda8c6741f..0426629291ba 100644 --- a/js/apps/admin-ui/cypress/e2e/authentication_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/authentication_test.spec.ts @@ -68,7 +68,7 @@ describe("Authentication test", () => { listingPage.clickRowDetails("Browser").clickDetailMenu("Duplicate"); duplicateFlowModal.fill("browser"); masthead.checkNotificationMessage( - "Could not duplicate flow: New flow alias name already exists" + "Could not duplicate flow: New flow alias name already exists", ); }); @@ -85,7 +85,7 @@ describe("Authentication test", () => { detailPage.expectPriorityChange(fromRow, () => { detailPage.moveRowTo( fromRow, - `[data-testid="Identity Provider Redirector"]` + `[data-testid="Identity Provider Redirector"]`, ); }); }); @@ -124,7 +124,7 @@ describe("Authentication test", () => { listingPage.goToItemDetails("Copy of browser"); detailPage.addExecution( "Copy of browser forms", - "reset-credentials-choose-user" + "reset-credentials-choose-user", ); masthead.checkNotificationMessage("Flow successfully updated"); @@ -135,7 +135,7 @@ describe("Authentication test", () => { listingPage.goToItemDetails("Copy of browser"); detailPage.addCondition( "Copy of browser Browser - Conditional OTP", - "conditional-user-role" + "conditional-user-role", ); masthead.checkNotificationMessage("Flow successfully updated"); @@ -146,7 +146,7 @@ describe("Authentication test", () => { listingPage.goToItemDetails("Copy of browser"); detailPage.addSubFlow( "Copy of browser Browser - Conditional OTP", - flowName + flowName, ); masthead.checkNotificationMessage("Flow successfully updated"); @@ -173,7 +173,7 @@ describe("Authentication test", () => { detailPage.fillCreateForm( flowName, "Some nice description about what this flow does so that we can use it later", - "Client flow" + "Client flow", ); masthead.checkNotificationMessage("Flow created"); detailPage.addSubFlowToEmpty(flowName, "EmptySubFlow"); @@ -293,7 +293,7 @@ describe("Accessibility tests for authentication", () => { detailPage.fillCreateForm( flowName, "Some nice description about what this flow does", - "Client flow" + "Client flow", ); cy.checkA11y(); }); diff --git a/js/apps/admin-ui/cypress/e2e/client_authorization_test.spec.ts b/js/apps/admin-ui/cypress/e2e/client_authorization_test.spec.ts index a3a45a135eff..a084d9ac10b3 100644 --- a/js/apps/admin-ui/cypress/e2e/client_authorization_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/client_authorization_test.spec.ts @@ -31,7 +31,7 @@ describe("Client authentication subtab", () => { authorizationServicesEnabled: true, serviceAccountsEnabled: true, standardFlowEnabled: true, - }) + }), ); after(() => { @@ -99,7 +99,7 @@ describe("Client authentication subtab", () => { masthead.checkNotificationMessage( "Authorization scope created successfully", - true + true, ); authenticationTab.goToScopesSubTab(); listingPage.itemExist("The scope"); @@ -109,7 +109,7 @@ describe("Client authentication subtab", () => { authenticationTab.goToPoliciesSubTab(); cy.intercept( "GET", - "/admin/realms/master/clients/*/authz/resource-server/policy/regex/*" + "/admin/realms/master/clients/*/authz/resource-server/policy/regex/*", ).as("get"); policiesSubTab .createPolicy("regex") @@ -141,7 +141,7 @@ describe("Client authentication subtab", () => { authenticationTab.goToPoliciesSubTab(); cy.intercept( "GET", - "/admin/realms/master/clients/*/authz/resource-server/policy/client/*" + "/admin/realms/master/clients/*/authz/resource-server/policy/client/*", ).as("get"); policiesSubTab .createPolicy("client") @@ -168,11 +168,11 @@ describe("Client authentication subtab", () => { }); permissionsSubTab.selectResource("Default Resource").formUtils().save(); cy.intercept( - "/admin/realms/master/clients/*/authz/resource-server/resource?first=0&max=10&permission=false" + "/admin/realms/master/clients/*/authz/resource-server/resource?first=0&max=10&permission=false", ).as("load"); masthead.checkNotificationMessage( "Successfully created the permission", - true + true, ); authenticationTab.formUtils().cancel(); }); @@ -191,7 +191,7 @@ describe("Client authentication subtab", () => { masthead.checkNotificationMessage( "Successfully exported authorization details.", - true + true, ); }); diff --git a/js/apps/admin-ui/cypress/e2e/client_registration_policies.spec.ts b/js/apps/admin-ui/cypress/e2e/client_registration_policies.spec.ts index 313b04b28c26..29263f07f904 100644 --- a/js/apps/admin-ui/cypress/e2e/client_registration_policies.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/client_registration_policies.spec.ts @@ -54,7 +54,7 @@ describe("Client registration policies subtab", () => { clientRegistrationPage.modalUtils().confirmModal(); masthead.checkNotificationMessage( - "Client registration policy deleted successfully" + "Client registration policy deleted successfully", ); listingPage.itemExist("policy 2", false); }); diff --git a/js/apps/admin-ui/cypress/e2e/client_scopes_test.spec.ts b/js/apps/admin-ui/cypress/e2e/client_scopes_test.spec.ts index 337d1912354d..7370ae2158e9 100644 --- a/js/apps/admin-ui/cypress/e2e/client_scopes_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/client_scopes_test.spec.ts @@ -220,7 +220,7 @@ describe("Client Scopes test", () => { .checkModalMessage(modalMessageDeleteConfirmation) .confirmModal(); masthead.checkNotificationMessage( - notificationMessageDeletionConfirmation + notificationMessageDeletionConfirmation, ); listingPage.checkInSearchBarChangeTypeToButtonIsDisabled(); }); @@ -235,7 +235,7 @@ describe("Client Scopes test", () => { .checkModalMessage(modalMessageDeleteConfirmation) .confirmModal(); masthead.checkNotificationMessage( - notificationMessageDeletionConfirmation + notificationMessageDeletionConfirmation, ); listingPage.checkInSearchBarChangeTypeToButtonIsDisabled(); }); @@ -252,7 +252,7 @@ describe("Client Scopes test", () => { .checkModalMessage(modalMessageDeleteConfirmation) .confirmModal(); masthead.checkNotificationMessage( - notificationMessageDeletionConfirmation + notificationMessageDeletionConfirmation, ); listingPage.checkInSearchBarChangeTypeToButtonIsDisabled(); }); @@ -272,7 +272,7 @@ describe("Client Scopes test", () => { createClientScopePage.fillClientScopeData("address").save(); masthead.checkNotificationMessage( - "Could not create client scope: 'Client Scope address already exists'" + "Could not create client scope: 'Client Scope address already exists'", ); }); @@ -456,7 +456,7 @@ describe("Client Scopes test", () => { mappersTab.addMappersByConfiguration( predefinedMapper, - predefinedMapperName + predefinedMapperName, ); cy.checkA11y(); diff --git a/js/apps/admin-ui/cypress/e2e/clients_saml_advanced.spec.ts b/js/apps/admin-ui/cypress/e2e/clients_saml_advanced.spec.ts index 488b888b8285..dbca71533867 100644 --- a/js/apps/admin-ui/cypress/e2e/clients_saml_advanced.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/clients_saml_advanced.spec.ts @@ -52,7 +52,7 @@ describe("Clients Saml advanced tab", () => { advancedTab.termsOfServiceUrl("not a url").saveFineGrain(); masthead.checkNotificationMessage( - "Client could not be updated: Terms of service URL is not a valid URL" + "Client could not be updated: Terms of service URL is not a valid URL", ); }); }); diff --git a/js/apps/admin-ui/cypress/e2e/clients_saml_test.spec.ts b/js/apps/admin-ui/cypress/e2e/clients_saml_test.spec.ts index a97c9660398c..93130eda0f2a 100644 --- a/js/apps/admin-ui/cypress/e2e/clients_saml_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/clients_saml_test.spec.ts @@ -89,7 +89,7 @@ describe("Clients SAML tests", () => { it("should disable client signature", () => { cy.intercept( - "admin/realms/master/clients/*/certificates/saml.signing" + "admin/realms/master/clients/*/certificates/saml.signing", ).as("load"); cy.findByTestId("clientSignature").click({ force: true }); @@ -106,7 +106,7 @@ describe("Clients SAML tests", () => { cy.findByTestId("generate").click(); masthead.checkNotificationMessage( - "New key pair and certificate generated successfully" + "New key pair and certificate generated successfully", ); modalUtils.confirmModal(); diff --git a/js/apps/admin-ui/cypress/e2e/clients_test.spec.ts b/js/apps/admin-ui/cypress/e2e/clients_test.spec.ts index ede7daf40bd5..97d4040f4aa9 100644 --- a/js/apps/admin-ui/cypress/e2e/clients_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/clients_test.spec.ts @@ -61,7 +61,7 @@ describe("Clients test", () => { await adminClient.createClientScope(clientScope); await adminClient.addDefaultClientScopeInClient( clientScopeName + i, - clientId + clientId, ); } clientScope.name = clientScopeNameDefaultType; @@ -310,7 +310,7 @@ describe("Clients test", () => { commonPage .masthead() .checkNotificationMessage( - "Could not create client: 'Client account already exists'" + "Could not create client: 'Client account already exists'", ); }); @@ -459,7 +459,7 @@ describe("Clients test", () => { cy.findByTestId("importClient").click(); cy.findByTestId("realm-file").selectFile( "cypress/fixtures/partial-import-test-data/import-identical-client.json", - { action: "drag-drop" } + { action: "drag-drop" }, ); cy.wait(1000); @@ -470,7 +470,7 @@ describe("Clients test", () => { .masthead() .checkNotificationMessage( "Could not import client: Client identical already exists", - true + true, ); }); @@ -489,7 +489,7 @@ describe("Clients test", () => { clientId: client, protocol: "openid-connect", publicClient: false, - }) + }), ); beforeEach(() => { @@ -563,7 +563,7 @@ describe("Clients test", () => { .masthead() .checkNotificationMessage( `Could not create role: Role with name ${itemId} already exists`, - true + true, ); }); @@ -595,7 +595,7 @@ describe("Clients test", () => { // Add associated client role associatedRolesPage.addAssociatedRoleFromSearchBar( "manage-account", - true + true, ); commonPage .masthead() @@ -606,7 +606,7 @@ describe("Clients test", () => { // Add associated client role associatedRolesPage.addAssociatedRoleFromSearchBar( "manage-consent", - true + true, ); commonPage .masthead() @@ -807,7 +807,7 @@ describe("Clients test", () => { authorizationServicesEnabled: true, serviceAccountsEnabled: true, standardFlowEnabled: true, - }) + }), ); beforeEach(() => { @@ -968,7 +968,7 @@ describe("Clients test", () => { protocol: "openid-connect", clientId: keysName, publicClient: false, - }) + }), ); beforeEach(() => { @@ -991,7 +991,7 @@ describe("Clients test", () => { commonPage .masthead() .checkNotificationMessage( - "New key pair and certificate generated successfully" + "New key pair and certificate generated successfully", ); }); }); @@ -1036,7 +1036,7 @@ describe("Clients test", () => { protocol: "openid-connect", publicClient: false, bearerOnly: true, - }) + }), ); beforeEach(() => { diff --git a/js/apps/admin-ui/cypress/e2e/events_test.spec.ts b/js/apps/admin-ui/cypress/e2e/events_test.spec.ts index 7cdda14ed4b8..d08377735fc5 100644 --- a/js/apps/admin-ui/cypress/e2e/events_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/events_test.spec.ts @@ -43,13 +43,13 @@ describe.skip("Events tests", () => { before(async () => { const result = await adminClient.createUser( - eventsTestUser.userRepresentation + eventsTestUser.userRepresentation, ); eventsTestUser.eventsTestUserId = result.id!; }); after(() => - adminClient.deleteUser(eventsTestUser.userRepresentation.username) + adminClient.deleteUser(eventsTestUser.userRepresentation.username), ); describe("User events list", () => { @@ -175,8 +175,8 @@ describe.skip("Events tests", () => { adminClient.loginUser( eventsTestUser.userRepresentation.username, eventsTestUser.userRepresentation.credentials[0].value, - eventsTestUserClientId - ) + eventsTestUserClientId, + ), ); userEventsTab .searchUserEventByUserId(eventsTestUser.eventsTestUserId) diff --git a/js/apps/admin-ui/cypress/e2e/group_test.spec.ts b/js/apps/admin-ui/cypress/e2e/group_test.spec.ts index b955b25ab36c..df1d29c988d9 100644 --- a/js/apps/admin-ui/cypress/e2e/group_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/group_test.spec.ts @@ -46,7 +46,7 @@ describe("Group test", () => { return { id: user.id!, username: username + index }; }); return user; - }) + }), ); }); @@ -133,7 +133,7 @@ describe("Group test", () => { it("Delete groups from search bar", () => { cy.wrap(null).then(() => - adminClient.createGroup("group_multiple_deletion_test") + adminClient.createGroup("group_multiple_deletion_test"), ); cy.reload(); groupPage @@ -151,7 +151,7 @@ describe("Group test", () => { range(5).map((index) => { adminClient.addUserToGroup( users[index].id!, - createdGroups[index % 3].id + createdGroups[index % 3].id, ); }), adminClient.createUser({ username: "new", enabled: true }), @@ -267,7 +267,7 @@ describe("Group test", () => { childGroupsTab .createGroup(predefinedGroups[2], false) .assertNotificationCouldNotCreateGroupWithDuplicatedName( - predefinedGroups[2] + predefinedGroups[2], ); }); @@ -317,7 +317,7 @@ describe("Group test", () => { range(5).map((index) => { adminClient.addUserToGroup( users[index].id!, - createdGroups[index % 3].id + createdGroups[index % 3].id, ); }), adminClient.createGroup(emptyGroup), diff --git a/js/apps/admin-ui/cypress/e2e/i18n_test.spec.ts b/js/apps/admin-ui/cypress/e2e/i18n_test.spec.ts index cbc1c1ccfad3..b364963555d2 100644 --- a/js/apps/admin-ui/cypress/e2e/i18n_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/i18n_test.spec.ts @@ -142,7 +142,7 @@ describe("i18n tests", () => { addLocalization( "en", "user-federation:addProvider_other", - "addProvider_other en: {{provider}}" + "addProvider_other en: {{provider}}", ); updateUserLocale("en"); @@ -159,20 +159,22 @@ describe("i18n tests", () => { function updateUserLocale(locale: string) { cy.wrap(null).then(() => - adminClient.updateUser(usernameI18nId, { attributes: { locale: locale } }) + adminClient.updateUser(usernameI18nId, { + attributes: { locale: locale }, + }), ); } function addCommonRealmSettingsLocalizationText( locale: string, - value: string + value: string, ) { addLocalization(locale, "common:realmSettings", value); } function addLocalization(locale: string, key: string, value: string) { cy.wrap(null).then(() => - adminClient.addLocalizationText(locale, key, value) + adminClient.addLocalizationText(locale, key, value), ); } }); diff --git a/js/apps/admin-ui/cypress/e2e/identity_providers_oidc_test.spec.ts b/js/apps/admin-ui/cypress/e2e/identity_providers_oidc_test.spec.ts index 88e08e110264..cf65cc11ab8c 100644 --- a/js/apps/admin-ui/cypress/e2e/identity_providers_oidc_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/identity_providers_oidc_test.spec.ts @@ -81,16 +81,16 @@ describe("OIDC identity provider test", () => { providerBaseAdvancedSettingsPage.assertOIDCPKCESwitch(); //Client Authentication providerBaseAdvancedSettingsPage.assertOIDCClientAuthentication( - ClientAuthentication.basicAuth + ClientAuthentication.basicAuth, ); providerBaseAdvancedSettingsPage.assertOIDCClientAuthentication( - ClientAuthentication.jwt + ClientAuthentication.jwt, ); providerBaseAdvancedSettingsPage.assertOIDCClientAuthentication( - ClientAuthentication.jwtPrivKey + ClientAuthentication.jwtPrivKey, ); providerBaseAdvancedSettingsPage.assertOIDCClientAuthentication( - ClientAuthentication.post + ClientAuthentication.post, ); //Client assertion signature algorithm Object.entries(ClientAssertionSigningAlg).forEach(([, value]) => { @@ -103,7 +103,7 @@ describe("OIDC identity provider test", () => { providerBaseAdvancedSettingsPage.selectPromptOption(PromptSelect.login); providerBaseAdvancedSettingsPage.selectPromptOption(PromptSelect.select); providerBaseAdvancedSettingsPage.selectPromptOption( - PromptSelect.unspecified + PromptSelect.unspecified, ); //Advanced Settings providerBaseAdvancedSettingsPage.assertAdvancedSettings(); diff --git a/js/apps/admin-ui/cypress/e2e/identity_providers_saml_test.spec.ts b/js/apps/admin-ui/cypress/e2e/identity_providers_saml_test.spec.ts index 99d55644a530..38bfc5f9669d 100644 --- a/js/apps/admin-ui/cypress/e2e/identity_providers_saml_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/identity_providers_saml_test.spec.ts @@ -81,7 +81,7 @@ describe("SAML identity provider test", () => { addMapperPage.goToMappersTab(); addMapperPage.addMapper(); addMapperPage.addUsernameTemplateImporterMapper( - "SAML Username Template Importer Mapper" + "SAML Username Template Importer Mapper", ); masthead.checkNotificationMessage(createMapperSuccessMsg, true); }); @@ -92,7 +92,7 @@ describe("SAML identity provider test", () => { addMapperPage.goToMappersTab(); addMapperPage.addMapper(); addMapperPage.addHardcodedUserSessionAttrMapper( - "Hardcoded User Session Attribute" + "Hardcoded User Session Attribute", ); masthead.checkNotificationMessage(createMapperSuccessMsg, true); }); diff --git a/js/apps/admin-ui/cypress/e2e/identity_providers_test.spec.ts b/js/apps/admin-ui/cypress/e2e/identity_providers_test.spec.ts index b5ad819cb599..797c99dc673c 100644 --- a/js/apps/admin-ui/cypress/e2e/identity_providers_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/identity_providers_test.spec.ts @@ -64,7 +64,7 @@ describe("Identity provider test", () => { return true; } return false; - } + }, ); return instance; } @@ -104,8 +104,8 @@ describe("Identity provider test", () => { after(async () => { await Promise.all( socialLoginIdentityProviders.map((idp) => - adminClient.deleteIdentityProvider(idp.alias) - ) + adminClient.deleteIdentityProvider(idp.alias), + ), ); }); @@ -318,7 +318,7 @@ describe("Identity provider test", () => { advancedSettings.assertStoreTokensSwitchTurnedOn(false); advancedSettings.assertAcceptsPromptNoneForwardFromClientSwitchTurnedOn( - false + false, ); advancedSettings.assertDisableUserInfoSwitchTurnedOn(false); advancedSettings.assertTrustEmailSwitchTurnedOn(false); @@ -338,7 +338,7 @@ describe("Identity provider test", () => { advancedSettings.ensureAdvancedSettingsAreVisible(); advancedSettings.assertStoreTokensSwitchTurnedOn(true); advancedSettings.assertAcceptsPromptNoneForwardFromClientSwitchTurnedOn( - true + true, ); advancedSettings.assertDisableUserInfoSwitchTurnedOn(true); advancedSettings.assertTrustEmailSwitchTurnedOn(true); @@ -358,19 +358,19 @@ describe("Identity provider test", () => { cy.findByTestId("jump-link-advanced-settings").click(); advancedSettings.assertStoreTokensSwitchTurnedOn(true); advancedSettings.assertAcceptsPromptNoneForwardFromClientSwitchTurnedOn( - true + true, ); advancedSettings.clickStoreTokensSwitch(); advancedSettings.clickAcceptsPromptNoneForwardFromClientSwitch(); advancedSettings.ensureAdvancedSettingsAreVisible(); advancedSettings.assertStoreTokensSwitchTurnedOn(false); advancedSettings.assertAcceptsPromptNoneForwardFromClientSwitchTurnedOn( - false + false, ); cy.findByTestId("idp-details-revert").click(); advancedSettings.assertStoreTokensSwitchTurnedOn(true); advancedSettings.assertAcceptsPromptNoneForwardFromClientSwitchTurnedOn( - true + true, ); }); diff --git a/js/apps/admin-ui/cypress/e2e/masthead_test.spec.ts b/js/apps/admin-ui/cypress/e2e/masthead_test.spec.ts index 43d2b5697ede..376bcf8f1ac8 100644 --- a/js/apps/admin-ui/cypress/e2e/masthead_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/masthead_test.spec.ts @@ -48,7 +48,7 @@ describe("Masthead tests", () => { cy.origin(href, () => { cy.get("#header").should( "contain.text", - "Server Administration Guide" + "Server Administration Guide", ); }); }); diff --git a/js/apps/admin-ui/cypress/e2e/partial_export_test.spec.ts b/js/apps/admin-ui/cypress/e2e/partial_export_test.spec.ts index 914837d7ca6a..4fd912afb641 100644 --- a/js/apps/admin-ui/cypress/e2e/partial_export_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/partial_export_test.spec.ts @@ -48,7 +48,7 @@ describe("Partial realm export", () => { modal.includeGroupsAndRolesSwitch().click({ force: true }); modal.exportButton().click(); cy.readFile( - Cypress.config("downloadsFolder") + "/realm-export.json" + Cypress.config("downloadsFolder") + "/realm-export.json", ).should("exist"); modal.exportButton().should("not.exist"); }); diff --git a/js/apps/admin-ui/cypress/e2e/partial_import_test.spec.ts b/js/apps/admin-ui/cypress/e2e/partial_import_test.spec.ts index 82d79dc4e3cb..46247c2f0816 100644 --- a/js/apps/admin-ui/cypress/e2e/partial_import_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/partial_import_test.spec.ts @@ -25,7 +25,7 @@ describe("Partial import test", () => { Promise.all([ adminClient.createRealm(TEST_REALM), adminClient.createRealm(TEST_REALM_2), - ]) + ]), ); after(async () => { diff --git a/js/apps/admin-ui/cypress/e2e/realm_roles_test.spec.ts b/js/apps/admin-ui/cypress/e2e/realm_roles_test.spec.ts index 8a0f43c4033c..b75025598d0b 100644 --- a/js/apps/admin-ui/cypress/e2e/realm_roles_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/realm_roles_test.spec.ts @@ -35,7 +35,7 @@ describe("Realm roles test", () => { // The error should inform about duplicated name/id (THIS MESSAGE DOES NOT HAVE QUOTES AS THE OTHERS) masthead.checkNotificationMessage( "Could not create role: Role with name admin already exists", - true + true, ); }); @@ -85,7 +85,7 @@ describe("Realm roles test", () => { listingPage.itemExist(defaultRole).deleteItem(defaultRole); masthead.checkNotificationMessage( "You cannot delete a default role.", - true + true, ); }); @@ -122,7 +122,7 @@ describe("Realm roles test", () => { // Add associated client role associatedRolesPage.addAssociatedRoleFromSearchBar( "manage-account-links", - true + true, ); masthead.checkNotificationMessage("Associated roles have been added", true); }); @@ -159,7 +159,7 @@ describe("Realm roles test", () => { masthead.checkNotificationMessage( "Scope mapping successfully removed", - true + true, ); }); @@ -178,7 +178,7 @@ describe("Realm roles test", () => { masthead.checkNotificationMessage( "Scope mapping successfully removed", - true + true, ); }); @@ -209,7 +209,7 @@ describe("Realm roles test", () => { masthead.checkNotificationMessage( "Scope mapping successfully removed", - true + true, ); listingPage.removeItem("offline_access"); sidebarPage.waitForPageLoad(); @@ -218,7 +218,7 @@ describe("Realm roles test", () => { masthead.checkNotificationMessage( "Scope mapping successfully removed", - true + true, ); }); @@ -231,7 +231,7 @@ describe("Realm roles test", () => { adminClient.createRealmRole({ name: editRoleName, description, - }) + }), ); after(() => adminClient.deleteRealmRole(editRoleName)); diff --git a/js/apps/admin-ui/cypress/e2e/realm_settings_client_policies_test.spec.ts b/js/apps/admin-ui/cypress/e2e/realm_settings_client_policies_test.spec.ts index 8bd6ee2ecab2..0240a19fb335 100644 --- a/js/apps/admin-ui/cypress/e2e/realm_settings_client_policies_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/realm_settings_client_policies_test.spec.ts @@ -48,7 +48,7 @@ describe("Realm settings client policies tab tests", () => { realmSettingsPage.createNewClientPolicyFromEmptyState( "Test", - "Test Description" + "Test Description", ); masthead.checkNotificationMessage("New policy created"); cy.wait("@save"); @@ -88,7 +88,7 @@ describe("Realm settings client policies tab tests", () => { modalUtils .checkModalTitle("Delete condition?") .checkModalMessage( - "This action will permanently delete client-roles. This cannot be undone." + "This action will permanently delete client-roles. This cannot be undone.", ) .checkConfirmButtonText("Delete") .cancelButtonContains("Cancel") @@ -111,7 +111,7 @@ describe("Realm settings client policies tab tests", () => { realmSettingsPage.deleteClientPolicyItemFromTable("Test"); modalUtils .checkModalMessage( - "This action will permanently delete the policy Test. This cannot be undone." + "This action will permanently delete the policy Test. This cannot be undone.", ) .cancelModal(); realmSettingsPage.checkElementInList("Test"); @@ -135,7 +135,7 @@ describe("Realm settings client policies tab tests", () => { realmSettingsPage.createNewClientPolicyFromEmptyState( "Test", - "Test Description" + "Test Description", ); masthead.checkNotificationMessage("New policy created"); cy.wait("@save"); @@ -147,7 +147,7 @@ describe("Realm settings client policies tab tests", () => { realmSettingsPage.createNewClientPolicyFromList( "Test", "Test Again Description", - true + true, ); realmSettingsPage.shouldShowErrorWhenDuplicate(); @@ -169,7 +169,7 @@ describe("Realm settings client policies tab tests", () => { cy.intercept("PUT", url).as("save"); realmSettingsPage.createNewClientPolicyFromEmptyState( "Test again", - "Test Again Description" + "Test Again Description", ); masthead.checkNotificationMessage("New policy created"); sidebarPage.waitForPageLoad(); diff --git a/js/apps/admin-ui/cypress/e2e/realm_settings_client_profiles_test.spec.ts b/js/apps/admin-ui/cypress/e2e/realm_settings_client_profiles_test.spec.ts index 8256d9f1c9ef..28dc764e374b 100644 --- a/js/apps/admin-ui/cypress/e2e/realm_settings_client_profiles_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/realm_settings_client_profiles_test.spec.ts @@ -87,7 +87,7 @@ describe("Realm settings client profiles tab tests", () => { .saveClientProfileCreation(); cy.wait("@save"); masthead.checkNotificationMessage( - "Could not create client profile: 'proposed client profile name duplicated.'" + "Could not create client profile: 'proposed client profile name duplicated.'", ); }); @@ -158,7 +158,7 @@ describe("Realm settings client profiles tab tests", () => { .checkModalMessage( "This action will permanently delete the profile " + editedProfileName + - ". This cannot be undone." + ". This cannot be undone.", ) .cancelModal(); realmSettingsPage.checkElementInList(editedProfileName); diff --git a/js/apps/admin-ui/cypress/e2e/realm_settings_events_test.spec.ts b/js/apps/admin-ui/cypress/e2e/realm_settings_events_test.spec.ts index a2d66a9eabbf..8a8f000b9d1a 100644 --- a/js/apps/admin-ui/cypress/e2e/realm_settings_events_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/realm_settings_events_test.spec.ts @@ -108,13 +108,13 @@ describe("Realm settings events tab tests", () => { realmSettingsPage.clearEvents("user"); modalUtils .checkModalMessage( - "If you clear all events of this realm, all records will be permanently cleared in the database" + "If you clear all events of this realm, all records will be permanently cleared in the database", ) .confirmModal(); masthead.checkNotificationMessage("The user events have been cleared"); const events = ["Client info", "Client info error"]; cy.intercept("GET", `/admin/realms/${realmName}/events/config`).as( - "fetchConfig" + "fetchConfig", ); realmSettingsPage.addUserEvents(events).clickAdd(); masthead.checkNotificationMessage("Successfully saved configuration"); @@ -254,7 +254,7 @@ describe("Realm settings events tab tests", () => { cy.get("#kc-l-supported-locales").click(); cy.intercept("GET", `/admin/realms/${realmName}/localization/en*`).as( - "load" + "load", ); cy.findByTestId("localization-tab-save").click(); @@ -263,7 +263,7 @@ describe("Realm settings events tab tests", () => { addBundle(); masthead.checkNotificationMessage( - "Success! The message bundle has been added." + "Success! The message bundle has been added.", ); realmSettingsPage.setDefaultLocale("dansk"); cy.findByTestId("localization-tab-save").click(); @@ -315,46 +315,46 @@ describe("Realm settings events tab tests", () => { cy.findByTestId(realmSettingsPage.ssoSessionIdleInput).should( "have.value", - 1 + 1, ); cy.findByTestId(realmSettingsPage.ssoSessionMaxInput).should( "have.value", - 2 + 2, ); cy.findByTestId(realmSettingsPage.ssoSessionIdleRememberMeInput).should( "have.value", - 3 + 3, ); cy.findByTestId(realmSettingsPage.ssoSessionMaxRememberMeInput).should( "have.value", - 4 + 4, ); cy.findByTestId(realmSettingsPage.clientSessionIdleInput).should( "have.value", - 5 + 5, ); cy.findByTestId(realmSettingsPage.clientSessionMaxInput).should( "have.value", - 6 + 6, ); cy.findByTestId(realmSettingsPage.offlineSessionIdleInput).should( "have.value", - 7 + 7, ); cy.findByTestId(realmSettingsPage.offlineSessionMaxSwitch).should( "have.value", - "on" + "on", ); cy.findByTestId(realmSettingsPage.loginTimeoutInput).should( "have.value", - 9 + 9, ); cy.findByTestId(realmSettingsPage.loginActionTimeoutInput).should( "have.value", - 10 + 10, ); }); @@ -376,42 +376,42 @@ describe("Realm settings events tab tests", () => { cy.findByTestId(realmSettingsPage.accessTokenLifespanInput).should( "have.value", - 1 + 1, ); cy.findByTestId(realmSettingsPage.accessTokenLifespanImplicitInput).should( "have.value", - 2 + 2, ); cy.findByTestId(realmSettingsPage.clientLoginTimeoutInput).should( "have.value", - 3 + 3, ); cy.findByTestId(realmSettingsPage.userInitiatedActionLifespanInput).should( "have.value", - 4 + 4, ); cy.findByTestId(realmSettingsPage.defaultAdminInitatedInput).should( "have.value", - 5 + 5, ); cy.findByTestId(realmSettingsPage.emailVerificationInput).should( "have.value", - 6 + 6, ); cy.findByTestId(realmSettingsPage.idpEmailVerificationInput).should( "have.value", - 7 + 7, ); cy.findByTestId(realmSettingsPage.forgotPasswordInput).should( "have.value", - 8 + 8, ); cy.findByTestId(realmSettingsPage.executeActionsInput).should( "have.value", - 9 + 9, ); }); }); diff --git a/js/apps/admin-ui/cypress/e2e/realm_settings_general_tab_test.spec.ts b/js/apps/admin-ui/cypress/e2e/realm_settings_general_tab_test.spec.ts index 508962f3df65..8b0f91b173d5 100644 --- a/js/apps/admin-ui/cypress/e2e/realm_settings_general_tab_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/realm_settings_general_tab_test.spec.ts @@ -32,13 +32,13 @@ describe("Realm settings general tab tests", () => { sidebarPage.goToRealmSettings(); realmSettingsPage.toggleSwitch( realmSettingsPage.managedAccessSwitch, - false + false, ); realmSettingsPage.save(realmSettingsPage.generalSaveBtn); masthead.checkNotificationMessage("Realm successfully updated", true); realmSettingsPage.toggleSwitch( realmSettingsPage.managedAccessSwitch, - false + false, ); realmSettingsPage.save(realmSettingsPage.generalSaveBtn); masthead.checkNotificationMessage("Realm successfully updated", true); @@ -127,8 +127,8 @@ describe("Realm settings general tab tests", () => { "have.attr", "href", `${Cypress.env( - "KEYCLOAK_SERVER" - )}/realms/${realmName}/.well-known/openid-configuration` + "KEYCLOAK_SERVER", + )}/realms/${realmName}/.well-known/openid-configuration`, ) .should("have.attr", "target", "_blank") .should("have.attr", "rel", "noreferrer noopener"); @@ -152,8 +152,8 @@ describe("Realm settings general tab tests", () => { "have.attr", "href", `${Cypress.env( - "KEYCLOAK_SERVER" - )}/realms/${realmName}/protocol/saml/descriptor` + "KEYCLOAK_SERVER", + )}/realms/${realmName}/protocol/saml/descriptor`, ) .should("have.attr", "target", "_blank") .should("have.attr", "rel", "noreferrer noopener"); diff --git a/js/apps/admin-ui/cypress/e2e/realm_settings_tabs_test.spec.ts b/js/apps/admin-ui/cypress/e2e/realm_settings_tabs_test.spec.ts index d06b4d4befea..4be9e9063aaf 100644 --- a/js/apps/admin-ui/cypress/e2e/realm_settings_tabs_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/realm_settings_tabs_test.spec.ts @@ -39,7 +39,7 @@ describe("Realm settings tabs tests", () => { cy.findByTestId(realmSettingsPage.userProfileTab).should("not.exist"); realmSettingsPage.toggleSwitch( realmSettingsPage.profileEnabledSwitch, - false + false, ); realmSettingsPage.save(realmSettingsPage.generalSaveBtn); masthead.checkNotificationMessage("Realm successfully updated"); @@ -75,12 +75,12 @@ describe("Realm settings tabs tests", () => { // Check other values cy.findByTestId(realmSettingsPage.emailAsUsernameSwitch).should( "have.value", - "off" + "off", ); cy.findByTestId(realmSettingsPage.verifyEmailSwitch).should( "have.value", - "off" + "off", ); }); diff --git a/js/apps/admin-ui/cypress/e2e/realm_settings_user_profile_tab.spec.ts b/js/apps/admin-ui/cypress/e2e/realm_settings_user_profile_tab.spec.ts index f198ac17aaa6..2e1f8ded488f 100644 --- a/js/apps/admin-ui/cypress/e2e/realm_settings_user_profile_tab.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/realm_settings_user_profile_tab.spec.ts @@ -30,7 +30,7 @@ describe("User profile tabs", () => { before(() => adminClient.createRealm(realmName, { attributes: { userProfileEnabled: "true" }, - }) + }), ); after(() => adminClient.deleteRealm(realmName)); @@ -67,7 +67,7 @@ describe("User profile tabs", () => { .createAttribute(attributeName, "Display name") .saveAttributeCreation(); masthead.checkNotificationMessage( - "Success! User Profile configuration has been saved." + "Success! User Profile configuration has been saved.", ); }); @@ -79,7 +79,7 @@ describe("User profile tabs", () => { .editAttribute("Edited display name") .saveAttributeCreation(); masthead.checkNotificationMessage( - "Success! User Profile configuration has been saved." + "Success! User Profile configuration has been saved.", ); }); @@ -101,7 +101,7 @@ describe("User profile tabs", () => { cy.wrap(null).then(() => adminClient.patchUserProfile(realmName, { groups: [{ name: "Test" }], - }) + }), ); getUserProfileTab(); @@ -131,7 +131,7 @@ describe("User profile tabs", () => { getJsonEditorTab(); userProfileTab.typeJSON(removedThree).saveJSON(); masthead.checkNotificationMessage( - "User profile settings successfully updated." + "User profile settings successfully updated.", ); }); }); diff --git a/js/apps/admin-ui/cypress/e2e/realm_test.spec.ts b/js/apps/admin-ui/cypress/e2e/realm_test.spec.ts index 9da137bfa0ec..43c36a6f2b1a 100644 --- a/js/apps/admin-ui/cypress/e2e/realm_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/realm_test.spec.ts @@ -29,9 +29,9 @@ describe("Realm tests", () => { after(() => Promise.all( [testRealmName, newRealmName, editedRealmName].map((realm) => - adminClient.deleteRealm(realm) - ) - ) + adminClient.deleteRealm(realm), + ), + ), ); it("should fail creating Master realm", () => { @@ -39,7 +39,7 @@ describe("Realm tests", () => { createRealmPage.fillRealmName("master").createRealm(); masthead.checkNotificationMessage( - "Could not create realm Conflict detected. See logs for details" + "Could not create realm Conflict detected. See logs for details", ); createRealmPage.cancelRealmCreation(); }); diff --git a/js/apps/admin-ui/cypress/e2e/sessions_test.spec.ts b/js/apps/admin-ui/cypress/e2e/sessions_test.spec.ts index bde2dd99ef87..19705897ca81 100644 --- a/js/apps/admin-ui/cypress/e2e/sessions_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/sessions_test.spec.ts @@ -73,7 +73,7 @@ describe("Sessions test", () => { commonPage .masthead() .checkNotificationMessage( - "No push sent. No admin URI configured or no registered cluster nodes available" + "No push sent. No admin URI configured or no registered cluster nodes available", ); }); }); diff --git a/js/apps/admin-ui/cypress/e2e/user_fed_kerberos_test.spec.ts b/js/apps/admin-ui/cypress/e2e/user_fed_kerberos_test.spec.ts index b1902b5231c5..75b3620f0c41 100644 --- a/js/apps/admin-ui/cypress/e2e/user_fed_kerberos_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/user_fed_kerberos_test.spec.ts @@ -74,7 +74,7 @@ describe("User Fed Kerberos tests", () => { firstKerberosName, firstKerberosRealm, firstKerberosPrincipal, - firstKerberosKeytab + firstKerberosKeytab, ); providersPage.save(provider); @@ -216,7 +216,7 @@ describe("User Fed Kerberos tests", () => { secondKerberosName, secondKerberosRealm, secondKerberosPrincipal, - secondKerberosKeytab + secondKerberosKeytab, ); providersPage.save(provider); diff --git a/js/apps/admin-ui/cypress/e2e/user_fed_ldap_hardcoded_mapper_test.spec.ts b/js/apps/admin-ui/cypress/e2e/user_fed_ldap_hardcoded_mapper_test.spec.ts index a5c984aae312..ab736f86ab66 100644 --- a/js/apps/admin-ui/cypress/e2e/user_fed_ldap_hardcoded_mapper_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/user_fed_ldap_hardcoded_mapper_test.spec.ts @@ -96,7 +96,7 @@ describe("User Fed LDAP mapper tests", () => { truststoreSpiAlways, connectionTimeoutTwoSecs, bindDnCnDc, - bindCredsValid + bindCredsValid, ); providersPage.toggleSwitch(providersPage.enableStartTls); providersPage.toggleSwitch(providersPage.connectionPooling); @@ -107,7 +107,7 @@ describe("User Fed LDAP mapper tests", () => { firstUserLdapAtt, firstRdnLdapAtt, firstUuidLdapAtt, - firstUserObjClasses + firstUserObjClasses, ); providersPage.save(provider); masthead.checkNotificationMessage(providerCreatedSuccess); diff --git a/js/apps/admin-ui/cypress/e2e/user_fed_ldap_mapper_test.spec.ts b/js/apps/admin-ui/cypress/e2e/user_fed_ldap_mapper_test.spec.ts index 9fcb3dcb6c4f..c0df367fe065 100644 --- a/js/apps/admin-ui/cypress/e2e/user_fed_ldap_mapper_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/user_fed_ldap_mapper_test.spec.ts @@ -99,7 +99,7 @@ describe("User Fed LDAP mapper tests", () => { truststoreSpiAlways, connectionTimeoutTwoSecs, bindDnCnDc, - bindCredsValid + bindCredsValid, ); providersPage.toggleSwitch(providersPage.enableStartTls); providersPage.toggleSwitch(providersPage.connectionPooling); @@ -110,7 +110,7 @@ describe("User Fed LDAP mapper tests", () => { firstUserLdapAtt, firstRdnLdapAtt, firstUuidLdapAtt, - firstUserObjClasses + firstUserObjClasses, ); providersPage.save(provider); masthead.checkNotificationMessage(providerCreatedSuccess); diff --git a/js/apps/admin-ui/cypress/e2e/user_fed_ldap_test.spec.ts b/js/apps/admin-ui/cypress/e2e/user_fed_ldap_test.spec.ts index 124af90004a6..1f000c68a4b7 100644 --- a/js/apps/admin-ui/cypress/e2e/user_fed_ldap_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/user_fed_ldap_test.spec.ts @@ -120,7 +120,7 @@ describe("User Federation LDAP tests", () => { truststoreSpiNever, connectionTimeoutTwoSecs, bindDnCnOnly, - bindCredsInvalid + bindCredsInvalid, ); providersPage.fillLdapSearchingData( editModeReadOnly, @@ -131,7 +131,7 @@ describe("User Federation LDAP tests", () => { firstUserObjClasses, firstUserLdapFilter, searchScopeOneLevel, - firstReadTimeout + firstReadTimeout, ); providersPage.save(provider); masthead.checkNotificationMessage(createdSuccessMessage); @@ -159,7 +159,7 @@ describe("User Federation LDAP tests", () => { secondUserLdapAtt, secondRdnLdapAtt, secondUuidLdapAtt, - secondUserObjClasses + secondUserObjClasses, ); providersPage.save(provider); masthead.checkNotificationMessage(savedSuccessMessage); @@ -221,7 +221,7 @@ describe("User Federation LDAP tests", () => { connectionUrlInvalid, bindTypeNone, truststoreSpiNever, - connectionTimeoutTwoSecs + connectionTimeoutTwoSecs, ); providersPage.toggleSwitch(providersPage.enableStartTls); providersPage.toggleSwitch(providersPage.connectionPooling); @@ -234,15 +234,15 @@ describe("User Federation LDAP tests", () => { providersPage.clickExistingCard(firstLdapName); providersPage.verifyTextField( providersPage.connectionUrlInput, - connectionUrlInvalid + connectionUrlInvalid, ); providersPage.verifyTextField( providersPage.connectionTimeoutInput, - connectionTimeoutTwoSecs + connectionTimeoutTwoSecs, ); providersPage.verifySelect( providersPage.truststoreSpiInput, - truststoreSpiNever + truststoreSpiNever, ); providersPage.verifySelect(providersPage.bindTypeInput, bindTypeNone); providersPage.verifyToggle(providersPage.enableStartTls, "on"); @@ -271,7 +271,7 @@ describe("User Federation LDAP tests", () => { truststoreSpiAlways, connectionTimeoutTwoSecs, bindDnCnDc, - bindCredsValid + bindCredsValid, ); providersPage.toggleSwitch(providersPage.enableStartTls); providersPage.toggleSwitch(providersPage.connectionPooling); @@ -296,11 +296,11 @@ describe("User Federation LDAP tests", () => { providersPage.fillTextField( providersPage.ldapKerberosRealmInput, - kerberosRealm + kerberosRealm, ); providersPage.fillTextField( providersPage.ldapServerPrincipalInput, - serverPrincipal + serverPrincipal, ); providersPage.fillTextField(providersPage.ldapKeyTabInput, keyTab); @@ -312,11 +312,11 @@ describe("User Federation LDAP tests", () => { providersPage.clickExistingCard(firstLdapName); providersPage.verifyTextField( providersPage.ldapKerberosRealmInput, - kerberosRealm + kerberosRealm, ); providersPage.verifyTextField( providersPage.ldapServerPrincipalInput, - serverPrincipal + serverPrincipal, ); providersPage.verifyTextField(providersPage.ldapKeyTabInput, keyTab); providersPage.verifyToggle(providersPage.allowKerberosAuth, "on"); @@ -336,11 +336,11 @@ describe("User Federation LDAP tests", () => { providersPage.fillTextField(providersPage.ldapBatchSizeInput, batchSize); providersPage.fillTextField( providersPage.ldapFullSyncPeriodInput, - fullSyncPeriod + fullSyncPeriod, ); providersPage.fillTextField( providersPage.ldapUsersSyncPeriodInput, - userSyncPeriod + userSyncPeriod, ); providersPage.save(provider); @@ -352,11 +352,11 @@ describe("User Federation LDAP tests", () => { providersPage.verifyTextField(providersPage.ldapBatchSizeInput, batchSize); providersPage.verifyTextField( providersPage.ldapFullSyncPeriodInput, - fullSyncPeriod + fullSyncPeriod, ); providersPage.verifyTextField( providersPage.ldapUsersSyncPeriodInput, - userSyncPeriod + userSyncPeriod, ); providersPage.verifyToggle(providersPage.periodicFullSync, "on"); providersPage.verifyToggle(providersPage.periodicUsersSync, "on"); @@ -376,7 +376,7 @@ describe("User Federation LDAP tests", () => { secondUserObjClasses, secondUserLdapFilter, searchScopeSubtree, - secondReadTimeout + secondReadTimeout, ); providersPage.toggleSwitch(providersPage.ldapPagination); @@ -389,39 +389,39 @@ describe("User Federation LDAP tests", () => { providersPage.verifySelect( providersPage.ldapEditModeInput, - editModeWritable + editModeWritable, ); providersPage.verifyTextField( providersPage.ldapUsersDnInput, - secondUsersDn + secondUsersDn, ); providersPage.verifyTextField( providersPage.ldapUserLdapAttInput, - secondUserLdapAtt + secondUserLdapAtt, ); providersPage.verifyTextField( providersPage.ldapRdnLdapAttInput, - secondRdnLdapAtt + secondRdnLdapAtt, ); providersPage.verifyTextField( providersPage.ldapUuidLdapAttInput, - secondUuidLdapAtt + secondUuidLdapAtt, ); providersPage.verifyTextField( providersPage.ldapUserObjClassesInput, - secondUserObjClasses + secondUserObjClasses, ); providersPage.verifyTextField( providersPage.ldapUserLdapFilter, - secondUserLdapFilter + secondUserLdapFilter, ); providersPage.verifySelect( providersPage.ldapSearchScopeInput, - searchScopeSubtree + searchScopeSubtree, ); providersPage.verifyTextField( providersPage.ldapReadTimeout, - secondReadTimeout + secondReadTimeout, ); providersPage.verifyToggle(providersPage.ldapPagination, "on"); @@ -452,7 +452,7 @@ describe("User Federation LDAP tests", () => { providersPage.verifySelect( providersPage.ldapEditModeInput, - editModeUnsynced + editModeUnsynced, ); }); @@ -542,7 +542,7 @@ describe("User Federation LDAP tests", () => { truststoreSpiNever, connectionTimeoutTwoSecs, bindDnCnOnly, - bindCredsInvalid + bindCredsInvalid, ); providersPage.fillLdapSearchingData( editModeWritable, @@ -550,7 +550,7 @@ describe("User Federation LDAP tests", () => { secondUserLdapAtt, secondRdnLdapAtt, secondUuidLdapAtt, - secondUserObjClasses + secondUserObjClasses, ); providersPage.save(provider); masthead.checkNotificationMessage(createdSuccessMessage); diff --git a/js/apps/admin-ui/cypress/e2e/users_test.spec.ts b/js/apps/admin-ui/cypress/e2e/users_test.spec.ts index e2bf657c76f1..935285c546a1 100644 --- a/js/apps/admin-ui/cypress/e2e/users_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/users_test.spec.ts @@ -242,7 +242,7 @@ describe("User creation", () => { enabled: true, }), identityProviders.forEach((idp) => - adminClient.createIdentityProvider(idp.displayName, idp.alias) + adminClient.createIdentityProvider(idp.displayName, idp.alias), ), ]); }); @@ -251,8 +251,8 @@ describe("User creation", () => { await adminClient.deleteUser(usernameIdpLinksTest); await Promise.all( identityProviders.map((idp) => - adminClient.deleteIdentityProvider(idp.alias) - ) + adminClient.deleteIdentityProvider(idp.alias), + ), ); }); @@ -267,7 +267,7 @@ describe("User creation", () => { if (linkedIdpsCount == 0) { identityProviderLinksTab.assertNoIdentityProvidersLinkedMessageExist( - true + true, ); } identityProviderLinksTab @@ -285,7 +285,7 @@ describe("User creation", () => { .assertAvailableIdentityProviderExist($idp.testName, false); if (availableIdpsCount - 1 == 0) { identityProviderLinksTab.assertNoAvailableIdentityProvidersMessageExist( - true + true, ); } }); @@ -295,8 +295,8 @@ describe("User creation", () => { cy.wrap(null).then(() => adminClient.unlinkAccountIdentityProvider( usernameIdpLinksTest, - identityProviders[0].displayName - ) + identityProviders[0].displayName, + ), ); sidebarPage.goToUsers(); @@ -306,15 +306,15 @@ describe("User creation", () => { cy.wrap(null).then(() => adminClient.linkAccountIdentityProvider( usernameIdpLinksTest, - identityProviders[0].displayName - ) + identityProviders[0].displayName, + ), ); identityProviderLinksTab .clickLinkAccount(identityProviders[0].testName) .assertLinkAccountModalTitleEqual(identityProviders[0].testName) .assertLinkAccountModalIdentityProviderInputEqual( - identityProviders[0].testName + identityProviders[0].testName, ) .typeLinkAccountModalUserId("testUserId") .typeLinkAccountModalUsername("testUsername") @@ -329,7 +329,7 @@ describe("User creation", () => { if (availableIdpsCount == 0) { identityProviderLinksTab.assertNoAvailableIdentityProvidersMessageExist( - true + true, ); } identityProviderLinksTab @@ -344,7 +344,7 @@ describe("User creation", () => { .assertAvailableIdentityProviderExist($idp.testName, true); if (linkedIdpsCount - 1 == 0) { identityProviderLinksTab.assertNoIdentityProvidersLinkedMessageExist( - true + true, ); } }); @@ -358,7 +358,7 @@ describe("User creation", () => { .clickEmptyStateResetBtn() .fillResetCredentialForm(); masthead.checkNotificationMessage( - "Failed: Failed to send execute actions email" + "Failed: Failed to send execute actions email", ); }); @@ -370,7 +370,7 @@ describe("User creation", () => { .fillResetCredentialForm(); masthead.checkNotificationMessage( - "Failed: Failed to send execute actions email" + "Failed: Failed to send execute actions email", ); }); @@ -383,7 +383,7 @@ describe("User creation", () => { .clickEditConfirmationBtn(); masthead.checkNotificationMessage( - "The user label has been changed successfully." + "The user label has been changed successfully.", ); }); @@ -404,7 +404,7 @@ describe("User creation", () => { modalUtils.checkModalTitle("Delete credentials?").confirmModal(); masthead.checkNotificationMessage( - "The credentials has been deleted successfully." + "The credentials has been deleted successfully.", ); }); diff --git a/js/apps/admin-ui/cypress/support/forms/FormValidation.ts b/js/apps/admin-ui/cypress/support/forms/FormValidation.ts index e06cdc0c7d33..7c9bfdf2fcb2 100644 --- a/js/apps/admin-ui/cypress/support/forms/FormValidation.ts +++ b/js/apps/admin-ui/cypress/support/forms/FormValidation.ts @@ -5,21 +5,21 @@ export default class FormValidation { static assertMinValue( chain: Cypress.Chainable>, - minValue: number + minValue: number, ) { this.#getHelperText(chain).should( "have.text", - `Must be greater than ${minValue}` + `Must be greater than ${minValue}`, ); } static assertMaxValue( chain: Cypress.Chainable>, - maxValue: number + maxValue: number, ) { this.#getHelperText(chain).should( "have.text", - `Must be less than ${maxValue}` + `Must be less than ${maxValue}`, ); } diff --git a/js/apps/admin-ui/cypress/support/forms/Select.ts b/js/apps/admin-ui/cypress/support/forms/Select.ts index 51f6b66bb390..47734c52b640 100644 --- a/js/apps/admin-ui/cypress/support/forms/Select.ts +++ b/js/apps/admin-ui/cypress/support/forms/Select.ts @@ -1,14 +1,14 @@ export default class Select { static assertSelectedItem( chain: Cypress.Chainable>, - itemName: string + itemName: string, ) { chain.should("have.text", itemName); } static selectItem( chain: Cypress.Chainable>, - itemName: string + itemName: string, ) { chain.click(); this.#getSelectMenu(chain).contains(itemName).click(); diff --git a/js/apps/admin-ui/cypress/support/pages/CommonElements.ts b/js/apps/admin-ui/cypress/support/pages/CommonElements.ts index 2c3bf7852dbf..7d8b89bc5ff6 100644 --- a/js/apps/admin-ui/cypress/support/pages/CommonElements.ts +++ b/js/apps/admin-ui/cypress/support/pages/CommonElements.ts @@ -43,13 +43,13 @@ export default class CommonElements { checkElementIsDisabled( element: Cypress.Chainable>, - disabled: boolean + disabled: boolean, ) { element.then(($btn) => { if ($btn.hasClass("pf-m-disabled")) { element.should( (!disabled ? "not." : "") + "have.class", - "pf-m-disabled" + "pf-m-disabled", ); } else { element.should((!disabled ? "not." : "") + "have.attr", "disabled"); diff --git a/js/apps/admin-ui/cypress/support/pages/LoginPage.ts b/js/apps/admin-ui/cypress/support/pages/LoginPage.ts index b3a134636fee..10453f706587 100644 --- a/js/apps/admin-ui/cypress/support/pages/LoginPage.ts +++ b/js/apps/admin-ui/cypress/support/pages/LoginPage.ts @@ -37,7 +37,7 @@ export default class LoginPage { validate() { cy.get('[role="progressbar"]').should("not.exist"); }, - } + }, ); } } diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/Masthead.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/Masthead.ts index 5e81613be25e..dbf47f2e4f9a 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/Masthead.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/Masthead.ts @@ -80,7 +80,7 @@ export default class Masthead extends CommonElements { .document() .then(({ documentElement }) => documentElement.getBoundingClientRect()) .then(({ width }) => - cy.get(width < 1024 ? this.userDrpDwnKebab : this.userDrpDwn) + cy.get(width < 1024 ? this.userDrpDwnKebab : this.userDrpDwn), ); } diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/SidebarPage.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/SidebarPage.ts index a45049d113df..3e0fd4b7f903 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/SidebarPage.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/SidebarPage.ts @@ -21,7 +21,7 @@ export default class SidebarPage extends CommonElements { cy.findByTestId(this.realmsDrpDwn).click(); cy.get('[data-testid="realmSelector"] li').should( "have.length", - length + 1 // account for button + length + 1, // account for button ); cy.findByTestId(this.realmsDrpDwn).click({ force: true }); } diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/components/FormPage.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/components/FormPage.ts index d94b1f51fd6a..457e6424d0ca 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/components/FormPage.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/components/FormPage.ts @@ -28,7 +28,7 @@ export default class FormPage extends CommonElements { checkSaveButtonIsDisabled(disabled: boolean) { this.checkElementIsDisabled( cy.get(this.primaryBtn).contains("Save"), - disabled + disabled, ); return this; } diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/components/PageObject.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/components/PageObject.ts index 2c7209e3c238..69eab07fe476 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/components/PageObject.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/components/PageObject.ts @@ -22,7 +22,7 @@ export default class PageObject { protected assertIsVisible( element: Cypress.Chainable, - isVisible: boolean + isVisible: boolean, ) { element.should((!isVisible ? "not." : "") + "be.visible"); return this; @@ -30,13 +30,13 @@ export default class PageObject { protected assertIsEnabled( element: Cypress.Chainable, - isEnabled = true + isEnabled = true, ) { element.then(($btn) => { if ($btn.hasClass("pf-m-disabled")) { element.should( (isEnabled ? "not." : "") + "have.class", - "pf-m-disabled" + "pf-m-disabled", ); } else { element.should((isEnabled ? "not." : "") + "have.attr", "disabled"); @@ -61,7 +61,7 @@ export default class PageObject { protected assertSwitchStateOn( element?: Cypress.Chainable, - isOn = true + isOn = true, ) { (element ?? cy.get(this.switchInput)) .parent() @@ -76,7 +76,7 @@ export default class PageObject { protected assertDropdownMenuIsOpen( isOpen = true, - element?: Cypress.Chainable + element?: Cypress.Chainable, ) { this.assertExist(element ?? cy.get(this.drpDwnMenuList), isOpen); return this; @@ -85,13 +85,13 @@ export default class PageObject { protected assertDropdownMenuIsClosed(element?: Cypress.Chainable) { return this.assertDropdownMenuIsOpen( false, - element ?? cy.get(this.drpDwnMenuList) + element ?? cy.get(this.drpDwnMenuList), ); } protected clickDropdownMenuItem( itemName: string, - element?: Cypress.Chainable + element?: Cypress.Chainable, ) { (element ?? cy.get(this.drpDwnMenuItem).contains(itemName)).click(); return this; @@ -99,7 +99,7 @@ export default class PageObject { protected clickDropdownMenuToggleButton( itemName: string, - element?: Cypress.Chainable + element?: Cypress.Chainable, ) { element = element ?? @@ -110,7 +110,7 @@ export default class PageObject { protected openDropdownMenu( itemName: string, - element?: Cypress.Chainable + element?: Cypress.Chainable, ) { element = element ?? @@ -122,7 +122,7 @@ export default class PageObject { protected closeDropdownMenu( itemName: string, - element?: Cypress.Chainable + element?: Cypress.Chainable, ) { element = element ?? @@ -135,19 +135,19 @@ export default class PageObject { protected assertDropdownMenuItemIsSelected( itemName: string, isSelected: boolean, - element?: Cypress.Chainable + element?: Cypress.Chainable, ) { element = element ?? cy.get(this.drpDwnMenuItem); this.assertExist( element.contains(itemName).find(this.selectItemSelectedIcon), - isSelected + isSelected, ); return this; } protected assertDropdownMenuHasItems( items: string[], - element?: Cypress.Chainable + element?: Cypress.Chainable, ) { const initialElement = element; for (const item of items) { @@ -159,7 +159,7 @@ export default class PageObject { protected assertDropdownMenuHasLabels( items: string[], - element?: Cypress.Chainable + element?: Cypress.Chainable, ) { const initialElement = element; for (const item of items) { @@ -171,7 +171,7 @@ export default class PageObject { protected assertDropdownMenuItemsEqualTo( number: number, - element?: Cypress.Chainable + element?: Cypress.Chainable, ) { element = element ?? cy.get(this.drpDwnMenuList); element.find(this.drpDwnMenuItem).should(($item) => { @@ -182,7 +182,7 @@ export default class PageObject { protected assertSelectMenuIsOpen( isOpen = true, - element?: Cypress.Chainable + element?: Cypress.Chainable, ) { element = element ?? cy.get(this.selectMenuList); return this.assertDropdownMenuIsOpen(isOpen, element); @@ -195,7 +195,7 @@ export default class PageObject { protected clickSelectMenuItem( itemName: string, - element?: Cypress.Chainable + element?: Cypress.Chainable, ) { element = element ?? @@ -205,7 +205,7 @@ export default class PageObject { protected clickSelectMenuToggleButton( itemName: string, - element?: Cypress.Chainable + element?: Cypress.Chainable, ) { element = element ?? @@ -224,7 +224,7 @@ export default class PageObject { protected closeSelectMenu( itemName: string, - element?: Cypress.Chainable + element?: Cypress.Chainable, ) { element = element ?? @@ -237,7 +237,7 @@ export default class PageObject { protected assertSelectMenuItemIsSelected( itemName: string, isSelected: boolean, - element?: Cypress.Chainable + element?: Cypress.Chainable, ) { element = element ?? cy.get(this.selectMenuItem); return this.assertDropdownMenuItemIsSelected(itemName, isSelected, element); @@ -245,7 +245,7 @@ export default class PageObject { protected assertSelectMenuHasItems( items: string[], - element?: Cypress.Chainable + element?: Cypress.Chainable, ) { const initialElement = element; for (const item of items) { @@ -257,7 +257,7 @@ export default class PageObject { protected assertSelectMenuItemsEqualTo( number: number, - element?: Cypress.Chainable + element?: Cypress.Chainable, ) { element = element ?? cy.get(this.selectMenuList); element.find(this.selectMenuItem).should(($item) => { @@ -320,11 +320,11 @@ export default class PageObject { protected assertChipGroupItemExist( groupName: string, itemName: string, - exist: boolean + exist: boolean, ) { this.assertExist( this.getChipGroup(groupName).contains(this.chipItem, itemName), - exist + exist, ); return this; } diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/components/TabPage.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/components/TabPage.ts index 9d88c94a591a..3834d88cc0ae 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/components/TabPage.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/components/TabPage.ts @@ -32,7 +32,7 @@ export default class TabPage extends CommonElements { checkTabExists( tabName: string, exists: boolean, - index: number | undefined = 0 + index: number | undefined = 0, ) { const condition = exists ? "exist" : "not.exist"; this.getTab(tabName, index).should(condition); diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/components/TablePage.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/components/TablePage.ts index e7ad6d155ecf..dd3655ee90cc 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/components/TablePage.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/components/TablePage.ts @@ -27,7 +27,7 @@ export default class TablePage extends CommonElements { selectRowItemCheckbox(itemName: string) { cy.get( - (this.tableInModal ? ".pf-c-modal-box.pf-m-md " : "") + this.tableRowItem + (this.tableInModal ? ".pf-c-modal-box.pf-m-md " : "") + this.tableRowItem, ) .contains(itemName) .parentsUntil("tbody") @@ -38,7 +38,7 @@ export default class TablePage extends CommonElements { clickRowItemLink(itemName: string) { cy.get( - (this.tableInModal ? ".pf-c-modal-box.pf-m-md " : "") + this.tableRowItem + (this.tableInModal ? ".pf-c-modal-box.pf-m-md " : "") + this.tableRowItem, ) .contains(itemName) .click(); @@ -47,7 +47,7 @@ export default class TablePage extends CommonElements { selectRowItemAction(itemName: string, actionItemName: string) { cy.get( - (this.tableInModal ? ".pf-c-modal-box.pf-m-md " : "") + this.tableRowItem + (this.tableInModal ? ".pf-c-modal-box.pf-m-md " : "") + this.tableRowItem, ) .contains(itemName) .parentsUntil("tbody") @@ -63,7 +63,7 @@ export default class TablePage extends CommonElements { this.tableRowItem + ":nth-child(" + row + - ")" + ")", ) .find("td:nth-child(" + column + ")") .type(value); @@ -76,7 +76,7 @@ export default class TablePage extends CommonElements { this.tableRowItem + ":nth-child(" + row + - ")" + ")", ) .find("td:nth-child(" + column + ") " + appendChildren) .click(); @@ -86,10 +86,10 @@ export default class TablePage extends CommonElements { clickRowItemByItemName( itemName: string, column: number, - appendChildren?: string + appendChildren?: string, ) { cy.get( - (this.tableInModal ? ".pf-c-modal-box.pf-m-md " : "") + this.tableRowItem + (this.tableInModal ? ".pf-c-modal-box.pf-m-md " : "") + this.tableRowItem, ) .find("td:nth-child(" + column + ") " + appendChildren) .contains(itemName) @@ -100,7 +100,7 @@ export default class TablePage extends CommonElements { clickHeaderItem(column: number, appendChildren?: string) { cy.get( (this.tableInModal ? ".pf-c-modal-box.pf-m-md " : "") + - this.tableHeaderRowItem + this.tableHeaderRowItem, ) .find("td:nth-child(" + column + ") " + appendChildren) .click(); @@ -109,7 +109,7 @@ export default class TablePage extends CommonElements { checkRowItemsEqualTo(amount: number) { cy.get( - (this.tableInModal ? ".pf-c-modal-box.pf-m-md " : "") + this.tableRowItem + (this.tableInModal ? ".pf-c-modal-box.pf-m-md " : "") + this.tableRowItem, ) .its("length") .should("be.eq", amount); @@ -118,7 +118,7 @@ export default class TablePage extends CommonElements { checkRowItemsGreaterThan(amount: number) { cy.get( - (this.tableInModal ? ".pf-c-modal-box.pf-m-md " : "") + this.tableRowItem + (this.tableInModal ? ".pf-c-modal-box.pf-m-md " : "") + this.tableRowItem, ) .its("length") .should("be.gt", amount); @@ -127,7 +127,7 @@ export default class TablePage extends CommonElements { checkRowItemExists(itemName: string, exist = true) { cy.get( - (this.tableInModal ? ".pf-c-modal-box.pf-m-md " : "") + this.tableRowItem + (this.tableInModal ? ".pf-c-modal-box.pf-m-md " : "") + this.tableRowItem, ) .contains(itemName) .should((!exist ? "not." : "") + "exist"); @@ -136,7 +136,7 @@ export default class TablePage extends CommonElements { checkRowItemValueByItemName(itemName: string, column: number, value: string) { cy.get( - (this.tableInModal ? ".pf-c-modal-box.pf-m-md " : "") + this.tableRowItem + (this.tableInModal ? ".pf-c-modal-box.pf-m-md " : "") + this.tableRowItem, ) .contains(itemName) .parentsUntil("tbody") @@ -149,14 +149,14 @@ export default class TablePage extends CommonElements { row: number, column: number, value: string, - appendChildren?: string + appendChildren?: string, ) { cy.get( (this.tableInModal ? ".pf-c-modal-box.pf-m-md " : "") + this.tableRowItem + ":nth-child(" + row + - ")" + ")", ) .find("td:nth-child(" + column + ") " + appendChildren) .should("have.text", value) diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/configure/realm_settings/PartialImportModal.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/configure/realm_settings/PartialImportModal.ts index f88aa6ee93e8..5ee0898e97b4 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/configure/realm_settings/PartialImportModal.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/configure/realm_settings/PartialImportModal.ts @@ -9,7 +9,7 @@ export default class GroupModal { typeResourceFile = (filename: string) => { cy.get("#partial-import-file-filename").selectFile( "cypress/fixtures/partial-import-test-data/" + filename, - { action: "drag-drop" } + { action: "drag-drop" }, ); }; diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/RoleMappingTab.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/RoleMappingTab.ts index 404ba6c736df..679bbfedc571 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/RoleMappingTab.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/RoleMappingTab.ts @@ -30,7 +30,7 @@ export default class RoleMappingTab { assignRole(notEmpty = true) { cy.findByTestId( - notEmpty ? this.assignEmptyRoleBtn(this.type) : this.assignRoleBtn + notEmpty ? this.assignEmptyRoleBtn(this.type) : this.assignRoleBtn, ).click(); return this; } diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/authentication/FlowDetail.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/authentication/FlowDetail.ts index 693a0a7c0c71..a3b60314333d 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/authentication/FlowDetail.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/authentication/FlowDetail.ts @@ -26,7 +26,7 @@ export default class FlowDetails { const executionId = rowDetails.children().attr("data-id"); cy.intercept( "POST", - `/admin/realms/test*/authentication/executions/${executionId}/lower-priority` + `/admin/realms/test*/authentication/executions/${executionId}/lower-priority`, ).as("priority"); callback(); cy.wait("@priority"); @@ -90,7 +90,7 @@ export default class FlowDetails { private fillSubFlowModal(subFlowName: string, name: string) { cy.get(".pf-c-modal-box__title-text").contains( - "Add step to " + subFlowName + "Add step to " + subFlowName, ); cy.findByTestId("name").type(name); cy.findByTestId("modal-add").click(); @@ -99,7 +99,7 @@ export default class FlowDetails { fillCreateForm( name: string, description: string, - type: "Basic flow" | "Client flow" + type: "Basic flow" | "Client flow", ) { cy.findByTestId("name").type(name); cy.findByTestId("description").type(description); diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/authentication/OTPPolicies.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/authentication/OTPPolicies.ts index 5a5f971e589f..dbaaf5ef3ce8 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/authentication/OTPPolicies.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/authentication/OTPPolicies.ts @@ -17,7 +17,7 @@ export default class OTPPolicies { checkSupportedApplications(...supportedApplications: string[]) { cy.findByTestId("supportedApplications").should( "have.text", - supportedApplications.join("") + supportedApplications.join(""), ); return this; } diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/authentication/WebAuthnPolicies.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/authentication/WebAuthnPolicies.ts index ff5350f94d06..1f38e854579c 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/authentication/WebAuthnPolicies.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/authentication/WebAuthnPolicies.ts @@ -21,7 +21,7 @@ export default class WebAuthnPolicies { cy.get( `#${ isPasswordLess ? prop.replace("Policy", "PolicyPasswordless") : prop - }` + }`, ) .click() .parent() diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/client_scopes/CreateClientScopePage.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/client_scopes/CreateClientScopePage.ts index 2a6bfaad1fcd..db1f4240ef1c 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/client_scopes/CreateClientScopePage.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/client_scopes/CreateClientScopePage.ts @@ -43,7 +43,7 @@ export default class CreateClientScopePage extends CommonPage { name: string, description = "", consentScreenText = "", - displayOrder = "" + displayOrder = "", ) { cy.get(this.clientScopeNameInput).clear(); diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/CreateClientPage.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/CreateClientPage.ts index b08225017281..56a3a652be61 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/CreateClientPage.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/CreateClientPage.ts @@ -73,7 +73,7 @@ export default class CreateClientPage extends CommonPage { name = "", description = "", alwaysDisplay?: boolean, - frontchannelLogout?: boolean + frontchannelLogout?: boolean, ) { cy.get(this.clientIdInput).clear(); diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/client_details/CreatePermissionPage.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/client_details/CreatePermissionPage.ts index ea4c42206008..7a5949208939 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/client_details/CreatePermissionPage.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/client_details/CreatePermissionPage.ts @@ -4,7 +4,7 @@ import type PolicyRepresentation from "@keycloak/keycloak-admin-client/lib/defs/ export default class CreatePermissionPage extends CommonPage { fillPermissionForm(permission: PolicyRepresentation) { Object.entries(permission).map(([key, value]) => - cy.get(`#${key}`).type(value) + cy.get(`#${key}`).type(value), ); return this; } diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/client_details/tabs/AdvancedSamlTab.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/client_details/tabs/AdvancedSamlTab.ts index 072fbca6b3d7..074230f81c17 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/client_details/tabs/AdvancedSamlTab.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/client_details/tabs/AdvancedSamlTab.ts @@ -20,7 +20,7 @@ export class AdvancedSamlTab extends PageObject { checkTermsOfServiceUrl(termsOfServiceUrl: string) { cy.findAllByTestId(this.termsOfServiceUrlId).should( "have.value", - termsOfServiceUrl + termsOfServiceUrl, ); return this; } diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/client_details/tabs/AdvancedTab.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/client_details/tabs/AdvancedTab.ts index 2f4a7c56b750..224646088600 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/client_details/tabs/AdvancedTab.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/client_details/tabs/AdvancedTab.ts @@ -75,7 +75,7 @@ export default class AdvancedTab extends PageObject { new Date().toLocaleString("en-US", { dateStyle: "long", timeStyle: "short", - }) + }), ); return this; @@ -89,7 +89,7 @@ export default class AdvancedTab extends PageObject { checkTestClusterAvailability(active: boolean) { cy.get(this.testClusterAvailability).should( (active ? "not." : "") + "have.class", - "pf-m-disabled" + "pf-m-disabled", ); return this; } @@ -132,7 +132,7 @@ export default class AdvancedTab extends PageObject { checkAccessTokenSignatureAlgorithm(algorithm: string) { cy.get(this.accessTokenSignatureAlgorithmInput).should( "have.text", - algorithm + algorithm, ); return this; } @@ -175,7 +175,7 @@ export default class AdvancedTab extends PageObject { .parent() .click(); this.assertSwitchStateOn( - cy.get(this.useRefreshTokenForClientCredentialsGrantSwitch) + cy.get(this.useRefreshTokenForClientCredentialsGrantSwitch), ); cy.get(this.useLowerCaseBearerTypeSwitch).parent().click(); this.assertSwitchStateOn(cy.get(this.useLowerCaseBearerTypeSwitch)); @@ -191,7 +191,7 @@ export default class AdvancedTab extends PageObject { .parent() .click(); this.assertSwitchStateOff( - cy.get(this.useRefreshTokenForClientCredentialsGrantSwitch) + cy.get(this.useRefreshTokenForClientCredentialsGrantSwitch), ); } @@ -220,7 +220,7 @@ export default class AdvancedTab extends PageObject { cy.get(this.oAuthMutualSwitch).scrollIntoView(); this.assertSwitchStateOn(cy.get(this.oAuthMutualSwitch)); this.assertSwitchStateOn( - cy.get(this.pushedAuthorizationRequestRequiredSwitch) + cy.get(this.pushedAuthorizationRequestRequiredSwitch), ); return this; } @@ -228,7 +228,7 @@ export default class AdvancedTab extends PageObject { checkAdvancedSwitchesOff() { this.assertSwitchStateOff(cy.get(this.oAuthMutualSwitch)); this.assertSwitchStateOff( - cy.get(this.pushedAuthorizationRequestRequiredSwitch) + cy.get(this.pushedAuthorizationRequestRequiredSwitch), ); return this; } diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/client_details/tabs/KeysTab.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/client_details/tabs/KeysTab.ts index c77e0b0149dd..28f5d4c7cfd3 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/client_details/tabs/KeysTab.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/client_details/tabs/KeysTab.ts @@ -28,7 +28,7 @@ export default class KeysTab extends CommonPage { archiveFormat: string, keyAlias: string, keyPassword: string, - storePassword: string + storePassword: string, ) { cy.get("#archiveFormat").click(); cy.findAllByRole("option").contains(archiveFormat).click(); diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/client_details/tabs/authorization_subtabs/PoliciesTab.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/client_details/tabs/authorization_subtabs/PoliciesTab.ts index 5eb8d421cfa4..04085a3631c7 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/client_details/tabs/authorization_subtabs/PoliciesTab.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/client_details/tabs/authorization_subtabs/PoliciesTab.ts @@ -16,7 +16,7 @@ export default class PoliciesTab extends CommonPage { fillBasePolicyForm(policy: { [key: string]: string }) { Object.entries(policy).map(([key, value]) => - cy.findByTestId(key).type(value) + cy.findByTestId(key).type(value), ); return this; } diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/tabs/InitialAccessTokenTab.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/tabs/InitialAccessTokenTab.ts index eacf8845a53f..957531cd60b4 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/tabs/InitialAccessTokenTab.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/clients/tabs/InitialAccessTokenTab.ts @@ -60,7 +60,7 @@ export default class InitialAccessTokenTab extends CommonPage { checkExpirationGreaterThanZeroError() { cy.get(this.expirationText).should( "have.text", - "Value should should be greater or equal to 1" + "Value should should be greater or equal to 1", ); return this; } diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/events/tabs/AdminEventsTab.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/events/tabs/AdminEventsTab.ts index d7398114c518..3911530b85e8 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/events/tabs/AdminEventsTab.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/events/tabs/AdminEventsTab.ts @@ -57,7 +57,7 @@ export default class AdminEventsTab extends PageObject { public assertAdminSearchDropdownMenuHasLabels() { super.assertDropdownMenuHasLabels( - Object.values(AdminEventsTabSearchFormFieldsLabel) + Object.values(AdminEventsTabSearchFormFieldsLabel), ); return this; } @@ -204,7 +204,7 @@ export default class AdminEventsTab extends PageObject { public assertResourceTypesChipGroupExist(exist: boolean) { super.assertChipGroupExist( AdminEventsTabSearchFormFieldsLabel.ResourceTypes, - exist + exist, ); return this; } @@ -212,7 +212,7 @@ export default class AdminEventsTab extends PageObject { public assertOperationTypesChipGroupExist(exist: boolean) { super.assertChipGroupExist( AdminEventsTabSearchFormFieldsLabel.OperationTypes, - exist + exist, ); return this; } @@ -220,19 +220,19 @@ export default class AdminEventsTab extends PageObject { public assertResourcePathChipGroupExist(exist: boolean) { super.assertChipGroupExist( AdminEventsTabSearchFormFieldsLabel.ResourcePath, - exist + exist, ); return this; } public assertResourcePathChipGroupItemExist( itemName: string, - exist: boolean + exist: boolean, ) { super.assertChipGroupItemExist( AdminEventsTabSearchFormFieldsLabel.ResourcePath, itemName, - exist + exist, ); return this; } @@ -240,7 +240,7 @@ export default class AdminEventsTab extends PageObject { public assertRealmChipGroupExist(exist: boolean) { super.assertChipGroupExist( AdminEventsTabSearchFormFieldsLabel.Realm, - exist + exist, ); return this; } @@ -248,7 +248,7 @@ export default class AdminEventsTab extends PageObject { public assertClientChipGroupExist(exist: boolean) { super.assertChipGroupExist( AdminEventsTabSearchFormFieldsLabel.Client, - exist + exist, ); return this; } @@ -261,7 +261,7 @@ export default class AdminEventsTab extends PageObject { public assertIpAddressChipGroupExist(exist: boolean) { super.assertChipGroupExist( AdminEventsTabSearchFormFieldsLabel.IpAddress, - exist + exist, ); return this; } @@ -269,7 +269,7 @@ export default class AdminEventsTab extends PageObject { public assertDateFromChipGroupExist(exist: boolean) { super.assertChipGroupExist( AdminEventsTabSearchFormFieldsLabel.DateFrom, - exist + exist, ); return this; } @@ -277,7 +277,7 @@ export default class AdminEventsTab extends PageObject { public assertDateToChipGroupExist(exist: boolean) { super.assertChipGroupExist( AdminEventsTabSearchFormFieldsLabel.DateTo, - exist + exist, ); return this; } diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/events/tabs/UserEventsTab.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/events/tabs/UserEventsTab.ts index ff99f3a034fa..9b8d99858fa7 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/events/tabs/UserEventsTab.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/events/tabs/UserEventsTab.ts @@ -36,7 +36,7 @@ export default class UserEventsTab extends PageObject { public openSearchUserEventDropdownMenu() { super.openDropdownMenu( "", - cy.findByTestId(this.searchUserEventDrpDwnToggle) + cy.findByTestId(this.searchUserEventDrpDwnToggle), ); return this; } @@ -63,7 +63,7 @@ export default class UserEventsTab extends PageObject { public assertUserSearchDropdownMenuHasLabels() { super.assertDropdownMenuHasLabels( - Object.values(UserEventsTabSearchFormFieldsLabel) + Object.values(UserEventsTabSearchFormFieldsLabel), ); return this; } @@ -161,7 +161,7 @@ export default class UserEventsTab extends PageObject { public removeEventTypeChipGroupItem(itemName: string) { super.removeChipGroupItem( UserEventsTabSearchFormFieldsLabel.EventType, - itemName + itemName, ); return this; } @@ -170,7 +170,7 @@ export default class UserEventsTab extends PageObject { super.assertChipGroupItemExist( UserEventsTabSearchFormFieldsLabel.EventType, itemName, - exist + exist, ); return this; } @@ -178,7 +178,7 @@ export default class UserEventsTab extends PageObject { public assertUserIdChipGroupExist(exist: boolean) { super.assertChipGroupExist( UserEventsTabSearchFormFieldsLabel.UserId, - exist + exist, ); return this; } @@ -186,7 +186,7 @@ export default class UserEventsTab extends PageObject { public assertEventTypeChipGroupExist(exist: boolean) { super.assertChipGroupExist( UserEventsTabSearchFormFieldsLabel.EventType, - exist + exist, ); return this; } @@ -194,7 +194,7 @@ export default class UserEventsTab extends PageObject { public assertClientChipGroupExist(exist: boolean) { super.assertChipGroupExist( UserEventsTabSearchFormFieldsLabel.Client, - exist + exist, ); return this; } @@ -202,7 +202,7 @@ export default class UserEventsTab extends PageObject { public assertDateFromChipGroupExist(exist: boolean) { super.assertChipGroupExist( UserEventsTabSearchFormFieldsLabel.DateFrom, - exist + exist, ); return this; } @@ -210,7 +210,7 @@ export default class UserEventsTab extends PageObject { public assertDateToChipGroupExist(exist: boolean) { super.assertChipGroupExist( UserEventsTabSearchFormFieldsLabel.DateTo, - exist + exist, ); return this; } @@ -218,7 +218,7 @@ export default class UserEventsTab extends PageObject { public assertIpAddressChipGroupExist(exist: boolean) { super.assertChipGroupExist( UserEventsTabSearchFormFieldsLabel.IpAddress, - exist + exist, ); return this; } diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/groups/GroupPage.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/groups/GroupPage.ts index d654baa45a1e..5af8356ac7de 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/groups/GroupPage.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/groups/GroupPage.ts @@ -104,7 +104,7 @@ export default class GroupPage extends PageObject { public moveGroupItemAction( groupName: string, - destinationGroupName: string[] + destinationGroupName: string[], ) { listingPage.clickRowDetails(groupName); listingPage.clickDetailMenu("Move to"); @@ -176,18 +176,18 @@ export default class GroupPage extends PageObject { public assertNotificationCouldNotCreateGroupWithEmptyName() { masthead.checkNotificationMessage( - "Could not create group Group name is missing" + "Could not create group Group name is missing", ); return this; } public assertNotificationCouldNotCreateGroupWithDuplicatedName( - groupName: string + groupName: string, ) { masthead.checkNotificationMessage( "Could not create group Top level group named '" + groupName + - "' already exists." + "' already exists.", ); return this; } diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/groups/group_details/GroupDetailPage.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/groups/group_details/GroupDetailPage.ts index ed6e6595180c..0fbdc63de7d3 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/groups/group_details/GroupDetailPage.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/groups/group_details/GroupDetailPage.ts @@ -53,7 +53,7 @@ export default class GroupDetailPage extends GroupPage { super.openDropdownMenu("", cy.findByTestId(this.actionDrpDwnButton)); super.clickDropdownMenuItem( "", - cy.findByTestId(this.actionDrpDwnItemRenameGroup) + cy.findByTestId(this.actionDrpDwnItemRenameGroup), ); return this; } @@ -62,7 +62,7 @@ export default class GroupDetailPage extends GroupPage { super.openDropdownMenu("", cy.findByTestId(this.actionDrpDwnButton)); super.clickDropdownMenuItem( "", - cy.findByTestId(this.actionDrpDwnItemDeleteGroup) + cy.findByTestId(this.actionDrpDwnItemDeleteGroup), ); modalUtils.confirmModal(); return this; diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/groups/group_details/tabs/MembersTab.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/groups/group_details/tabs/MembersTab.ts index 12e88baaf129..6e23b454c476 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/groups/group_details/tabs/MembersTab.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/groups/group_details/tabs/MembersTab.ts @@ -61,14 +61,14 @@ export default class MembersTab extends GroupDetailPage { public assertNotificationUserAddedToTheGroup(amount: number) { masthead.checkNotificationMessage( - `${amount} user${amount > 1 ? "s" : ""} added to the group` + `${amount} user${amount > 1 ? "s" : ""} added to the group`, ); return this; } public assertNotificationUserLeftTheGroup(amount: number) { masthead.checkNotificationMessage( - `${amount} user${amount > 1 ? "s" : ""} left the group` + `${amount} user${amount > 1 ? "s" : ""} left the group`, ); return this; } diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/AddMapperPage.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/AddMapperPage.ts index 46b8c67b17a8..f430e45cdb72 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/AddMapperPage.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/AddMapperPage.ts @@ -81,7 +81,7 @@ export default class AddMapperPage { cy.findByTestId(this.socialProfileJSONfieldPath).clear(); cy.findByTestId(this.socialProfileJSONfieldPath).type( - "social profile JSON field path" + "social profile JSON field path", ); cy.findByTestId(this.userAttributeName).clear(); @@ -175,7 +175,7 @@ export default class AddMapperPage { cy.findByTestId(this.userSessionAttributeValue).clear(); cy.findByTestId(this.userSessionAttributeValue).type( - "user session attribute value" + "user session attribute value", ); this.saveNewMapper(); @@ -323,7 +323,7 @@ export default class AddMapperPage { cy.findByTestId(this.socialProfileJSONfieldPath).clear(); cy.findByTestId(this.socialProfileJSONfieldPath).type( - "social profile JSON field path edited" + "social profile JSON field path edited", ); cy.findByTestId(this.userAttributeName).clear(); diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/CreateProviderPage.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/CreateProviderPage.ts index ba3b47b6b552..1176e365d37a 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/CreateProviderPage.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/CreateProviderPage.ts @@ -43,7 +43,7 @@ export default class CreateProviderPage { checkAddButtonDisabled(disabled = true) { cy.findByTestId(this.addButton).should( - !disabled ? "not." : "" + "be.disabled" + !disabled ? "not." : "" + "be.disabled", ); return this; } @@ -125,7 +125,7 @@ export default class CreateProviderPage { shouldBeSuccessful() { cy.findByTestId(this.discoveryEndpoint).should( "have.class", - "pf-m-success" + "pf-m-success", ); return this; } diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/ProviderBaseAdvancedSettingsPage.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/ProviderBaseAdvancedSettingsPage.ts index 46f02d2302d3..e8395c038348 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/ProviderBaseAdvancedSettingsPage.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/ProviderBaseAdvancedSettingsPage.ts @@ -156,7 +156,7 @@ export default class ProviderBaseGeneralSettingsPage extends PageObject { cy.get(this.firstLoginFlowSelect).click(); super.clickSelectMenuItem( loginFlowOption, - cy.get(".pf-c-select__menu-item").contains(loginFlowOption) + cy.get(".pf-c-select__menu-item").contains(loginFlowOption), ); return this; } @@ -165,18 +165,18 @@ export default class ProviderBaseGeneralSettingsPage extends PageObject { cy.get(this.postLoginFlowSelect).click(); super.clickSelectMenuItem( loginFlowOption, - cy.get(".pf-c-select__menu-item").contains(loginFlowOption) + cy.get(".pf-c-select__menu-item").contains(loginFlowOption), ); return this; } public selectClientAssertSignAlg( - clientAssertionSigningAlg: ClientAssertionSigningAlg + clientAssertionSigningAlg: ClientAssertionSigningAlg, ) { cy.get(this.clientAssertionSigningAlg).click(); super.clickSelectMenuItem( clientAssertionSigningAlg, - cy.get(".pf-c-select__menu-item").contains(clientAssertionSigningAlg) + cy.get(".pf-c-select__menu-item").contains(clientAssertionSigningAlg), ); return this; } @@ -185,7 +185,7 @@ export default class ProviderBaseGeneralSettingsPage extends PageObject { cy.get(this.syncModeSelect).click(); super.clickSelectMenuItem( syncModeOption, - cy.get(".pf-c-select__menu-item").contains(syncModeOption) + cy.get(".pf-c-select__menu-item").contains(syncModeOption), ); return this; } @@ -194,7 +194,7 @@ export default class ProviderBaseGeneralSettingsPage extends PageObject { cy.get(this.promptSelect).click(); super.clickSelectMenuItem( promptOption, - cy.get(".pf-c-select__menu-item").contains(promptOption).parent() + cy.get(".pf-c-select__menu-item").contains(promptOption).parent(), ); return this; } @@ -222,7 +222,7 @@ export default class ProviderBaseGeneralSettingsPage extends PageObject { public assertAcceptsPromptNoneForwardFromClientSwitchTurnedOn(isOn: boolean) { super.assertSwitchStateOn( cy.get(this.acceptsPromptNoneForwardFromClientSwitch).parent(), - isOn + isOn, ); return this; } @@ -230,7 +230,7 @@ export default class ProviderBaseGeneralSettingsPage extends PageObject { public assertDisableUserInfoSwitchTurnedOn(isOn: boolean) { super.assertSwitchStateOn( cy.get(this.disableUserInfoSwitch).parent(), - isOn + isOn, ); return this; } @@ -248,7 +248,7 @@ export default class ProviderBaseGeneralSettingsPage extends PageObject { public assertHideOnLoginPageSwitchTurnedOn(isOn: boolean) { super.assertSwitchStateOn( cy.get(this.hideOnLoginPageSwitch).parent(), - isOn + isOn, ); return this; } @@ -269,14 +269,14 @@ export default class ProviderBaseGeneralSettingsPage extends PageObject { } public assertFirstLoginFlowSelectOptionEqual( - loginFlowOption: LoginFlowOption + loginFlowOption: LoginFlowOption, ) { cy.get(this.firstLoginFlowSelect).should("have.text", loginFlowOption); return this; } public assertPostLoginFlowSelectOptionEqual( - loginFlowOption: LoginFlowOption + loginFlowOption: LoginFlowOption, ) { cy.get(this.postLoginFlowSelect).should("have.text", loginFlowOption); return this; @@ -288,11 +288,11 @@ export default class ProviderBaseGeneralSettingsPage extends PageObject { } public assertClientAssertSigAlgSelectOptionEqual( - clientAssertionSigningAlg: ClientAssertionSigningAlg + clientAssertionSigningAlg: ClientAssertionSigningAlg, ) { cy.get(this.clientAssertionSigningAlg).should( "have.text", - clientAssertionSigningAlg + clientAssertionSigningAlg, ); return this; } @@ -305,7 +305,7 @@ export default class ProviderBaseGeneralSettingsPage extends PageObject { this.clickSaveBtn(); masthead.checkNotificationMessage( "Could not update the provider The url [" + url + "_url] is malformed", - true + true, ); this.clickRevertBtn(); //cy.findByTestId(url + "Url").contains @@ -382,7 +382,7 @@ export default class ProviderBaseGeneralSettingsPage extends PageObject { this.clickAcceptsPromptNoneForwardFromClientSwitch(); super.assertSwitchStateOn( - cy.get(this.acceptsPromptNoneForwardFromClientSwitch) + cy.get(this.acceptsPromptNoneForwardFromClientSwitch), ); return this; } @@ -413,12 +413,12 @@ export default class ProviderBaseGeneralSettingsPage extends PageObject { this.assertHideOnLoginPageSwitchTurnedOn(false); this.assertFirstLoginFlowSelectOptionEqual( - LoginFlowOption.firstBrokerLogin + LoginFlowOption.firstBrokerLogin, ); this.assertPostLoginFlowSelectOptionEqual(LoginFlowOption.none); this.assertSyncModeSelectOptionEqual(SyncModeOption.import); this.assertClientAssertSigAlgSelectOptionEqual( - ClientAssertionSigningAlg.algorithmNotSpecified + ClientAssertionSigningAlg.algorithmNotSpecified, ); return this; } diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/ProviderBaseGeneralSettingsPage.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/ProviderBaseGeneralSettingsPage.ts index d87e37f864be..503af469a00b 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/ProviderBaseGeneralSettingsPage.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/ProviderBaseGeneralSettingsPage.ts @@ -122,12 +122,12 @@ export default class ProviderBaseGeneralSettingsPage extends PageObject { protected assertCommonFilledDataEqual(idpName: string) { cy.get(this.clientIdInput).should( "have.value", - this.testData["ClientId"] + idpName + this.testData["ClientId"] + idpName, ); cy.get(this.clientSecretInput).should("contain.value", "****"); cy.get(this.displayOrderInput).should( "have.value", - this.testData["DisplayOrder"] + this.testData["DisplayOrder"], ); return this; } diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/social/ProviderFacebookGeneralSettings.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/social/ProviderFacebookGeneralSettings.ts index 0f6b434c0899..86fba88ceca4 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/social/ProviderFacebookGeneralSettings.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/social/ProviderFacebookGeneralSettings.ts @@ -15,7 +15,7 @@ export default class ProviderFacebookGeneralSettings extends ProviderBaseGeneral public assertAdditionalUsersProfileFieldsInputEqual(value: string) { cy.findByTestId(this.additionalUsersProfileFieldsInput).should( "have.value", - value + value, ); return this; } @@ -23,7 +23,7 @@ export default class ProviderFacebookGeneralSettings extends ProviderBaseGeneral public fillData(idpName: string) { this.fillCommonFields(idpName); this.typeAdditionalUsersProfileFieldsInput( - idpName + additionalUsersProfile_input_test_value + idpName + additionalUsersProfile_input_test_value, ); return this; } @@ -31,7 +31,7 @@ export default class ProviderFacebookGeneralSettings extends ProviderBaseGeneral public assertFilledDataEqual(idpName: string) { this.assertCommonFilledDataEqual(idpName); this.assertAdditionalUsersProfileFieldsInputEqual( - idpName + additionalUsersProfile_input_test_value + idpName + additionalUsersProfile_input_test_value, ); return this; } diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/social/ProviderGoogleGeneralSettings.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/social/ProviderGoogleGeneralSettings.ts index 780bc9be4d4d..d36e0f67e71a 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/social/ProviderGoogleGeneralSettings.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/social/ProviderGoogleGeneralSettings.ts @@ -36,7 +36,7 @@ export default class ProviderGoogleGeneralSettings extends ProviderBaseGeneralSe public assertRequestRefreshTokenSwitchTurnedOn(isOn: boolean) { super.assertSwitchStateOn( cy.findByTestId(this.requestRefreshTokenSwitch), - isOn + isOn, ); return this; } diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/social/ProviderOpenshiftGeneralSettings.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/social/ProviderOpenshiftGeneralSettings.ts index a394e668178a..7cb760404a20 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/social/ProviderOpenshiftGeneralSettings.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/identity_providers/social/ProviderOpenshiftGeneralSettings.ts @@ -27,7 +27,7 @@ export default class ProviderOpenshiftGeneralSettings extends ProviderBaseGenera public fillData(idpName: string) { this.fillCommonFields(idpName); cy.findByTestId(this.baseUrlInput).type( - idpName + base_url_input_test_value + idpName + base_url_input_test_value, ); return this; } diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/providers/ProviderPage.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/providers/ProviderPage.ts index 7c98d8c7ada2..67a38bdd77b4 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/providers/ProviderPage.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/providers/ProviderPage.ts @@ -139,7 +139,7 @@ export default class ProviderPage { verifyChangedHourInput(expected: string, unexpected: string) { expect(cy.get(this.cacheHourInput).contains(expected).should("exist")); expect( - cy.get(this.cacheHourInput).contains(unexpected).should("not.exist") + cy.get(this.cacheHourInput).contains(unexpected).should("not.exist"), ); return this; } @@ -161,7 +161,7 @@ export default class ProviderPage { name: string, realm: string, principal: string, - keytab: string + keytab: string, ) { if (name) { cy.findByTestId(this.kerberosNameInput).clear().type(name); @@ -229,7 +229,7 @@ export default class ProviderPage { truststoreSpi?: string, connectionTimeout?: string, bindDn?: string, - bindCreds?: string + bindCreds?: string, ) { cy.findByTestId(this.connectionUrlInput).clear().type(connectionUrl); @@ -263,7 +263,7 @@ export default class ProviderPage { userObjClasses?: string, userLdapFilter?: string, searchScope?: string, - readTimeout?: string + readTimeout?: string, ) { cy.get(this.ldapEditModeInput).click(); cy.get(this.ldapEditModeList).contains(editMode).click(); diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/realm_roles/AssociatedRolesPage.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/realm_roles/AssociatedRolesPage.ts index 382c480daa84..585cc4f7af46 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/realm_roles/AssociatedRolesPage.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/realm_roles/AssociatedRolesPage.ts @@ -27,7 +27,7 @@ export default class AssociatedRolesPage { cy.findByTestId(this.compositeRoleBadge).should( "contain.text", - "Composite" + "Composite", ); return this; @@ -86,7 +86,7 @@ export default class AssociatedRolesPage { isRemoveAssociatedRolesBtnDisabled() { cy.findByTestId(this.removeRolesButton).should( "have.class", - "pf-m-disabled" + "pf-m-disabled", ); return this; } diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/realm_settings/RealmSettingsPage.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/realm_settings/RealmSettingsPage.ts index 0f2fc1eed7df..30cab822d931 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/realm_settings/RealmSettingsPage.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/realm_settings/RealmSettingsPage.ts @@ -258,7 +258,7 @@ export default class RealmSettingsPage extends CommonPage { disableRealm() { cy.get(this.modalDialogTitle).contains("Disable realm?"); cy.get(this.modalDialogBodyText).contains( - "User and clients can't access the realm if it's disabled. Are you sure you want to continue?" + "User and clients can't access the realm if it's disabled. Are you sure you want to continue?", ); cy.findByTestId(this.modalConfirm).contains("Disable").click(); } @@ -386,7 +386,7 @@ export default class RealmSettingsPage extends CommonPage { cy.get(this.alertMessage).should( "be.visible", - "Success. The provider has been deleted." + "Success. The provider has been deleted.", ); return this; } @@ -492,7 +492,7 @@ export default class RealmSettingsPage extends CommonPage { changeTimeUnit( unit: "Minutes" | "Hours" | "Days", inputType: string, - listType: string + listType: string, ) { switch (unit) { case "Minutes": @@ -518,38 +518,38 @@ export default class RealmSettingsPage extends CommonPage { this.changeTimeUnit( "Minutes", this.ssoSessionIdleSelectMenu, - this.ssoSessionIdleSelectMenuList + this.ssoSessionIdleSelectMenuList, ); cy.findByTestId(this.ssoSessionMaxInput).clear().type("2"); this.changeTimeUnit( "Hours", this.ssoSessionMaxSelectMenu, - this.ssoSessionMaxSelectMenuList + this.ssoSessionMaxSelectMenuList, ); cy.findByTestId(this.ssoSessionIdleRememberMeInput).clear().type("3"); this.changeTimeUnit( "Days", this.ssoSessionIdleRememberMeSelectMenu, - this.ssoSessionIdleRememberMeSelectMenuList + this.ssoSessionIdleRememberMeSelectMenuList, ); cy.findByTestId(this.ssoSessionMaxRememberMeInput).clear().type("4"); this.changeTimeUnit( "Minutes", this.ssoSessionMaxRememberMeSelectMenu, - this.ssoSessionMaxRememberMeSelectMenuList + this.ssoSessionMaxRememberMeSelectMenuList, ); cy.findByTestId(this.clientSessionIdleInput).clear().type("5"); this.changeTimeUnit( "Hours", this.clientSessionIdleSelectMenu, - this.clientSessionIdleSelectMenuList + this.clientSessionIdleSelectMenuList, ); cy.findByTestId(this.clientSessionMaxInput).clear().type("6"); this.changeTimeUnit( "Days", this.clientSessionMaxSelectMenu, - this.clientSessionMaxSelectMenuList + this.clientSessionMaxSelectMenuList, ); cy.findByTestId(this.offlineSessionIdleInput).clear().type("7"); @@ -559,13 +559,13 @@ export default class RealmSettingsPage extends CommonPage { this.changeTimeUnit( "Minutes", this.loginTimeoutSelectMenu, - this.loginTimeoutSelectMenuList + this.loginTimeoutSelectMenuList, ); cy.findByTestId(this.loginActionTimeoutInput).clear().type("10"); this.changeTimeUnit( "Days", this.loginActionTimeoutSelectMenu, - this.loginActionTimeoutSelectMenuList + this.loginActionTimeoutSelectMenuList, ); } @@ -579,61 +579,61 @@ export default class RealmSettingsPage extends CommonPage { this.changeTimeUnit( "Days", this.accessTokenLifespanSelectMenu, - this.accessTokenLifespanSelectMenuList + this.accessTokenLifespanSelectMenuList, ); cy.findByTestId(this.accessTokenLifespanImplicitInput).clear().type("2"); this.changeTimeUnit( "Minutes", this.accessTokenLifespanImplicitSelectMenu, - this.accessTokenLifespanImplicitSelectMenuList + this.accessTokenLifespanImplicitSelectMenuList, ); cy.findByTestId(this.clientLoginTimeoutInput).clear().type("3"); this.changeTimeUnit( "Hours", this.clientLoginTimeoutSelectMenu, - this.clientLoginTimeoutSelectMenuList + this.clientLoginTimeoutSelectMenuList, ); cy.findByTestId(this.userInitiatedActionLifespanInput).clear().type("4"); this.changeTimeUnit( "Minutes", this.userInitiatedActionLifespanSelectMenu, - this.userInitiatedActionLifespanSelectMenuList + this.userInitiatedActionLifespanSelectMenuList, ); cy.findByTestId(this.defaultAdminInitatedInput).clear().type("5"); this.changeTimeUnit( "Days", this.defaultAdminInitatedInputSelectMenu, - this.defaultAdminInitatedInputSelectMenuList + this.defaultAdminInitatedInputSelectMenuList, ); cy.findByTestId(this.emailVerificationInput).clear().type("6"); this.changeTimeUnit( "Days", this.emailVerificationSelectMenu, - this.emailVerificationSelectMenuList + this.emailVerificationSelectMenuList, ); cy.findByTestId(this.idpEmailVerificationInput).clear().type("7"); this.changeTimeUnit( "Days", this.idpEmailVerificationSelectMenu, - this.idpEmailVerificationSelectMenuList + this.idpEmailVerificationSelectMenuList, ); cy.findByTestId(this.forgotPasswordInput).clear().type("8"); this.changeTimeUnit( "Days", this.forgotPasswordSelectMenu, - this.forgotPasswordSelectMenuList + this.forgotPasswordSelectMenuList, ); cy.findByTestId(this.executeActionsInput).clear().type("9"); this.changeTimeUnit( "Days", this.executeActionsSelectMenu, - this.executeActionsSelectMenuList + this.executeActionsSelectMenuList, ); } @@ -675,7 +675,7 @@ export default class RealmSettingsPage extends CommonPage { cy.findByTestId(this.eventListenersSaveBtn).click(); cy.get(this.alertMessage).should( "be.visible", - "Event listener has been updated." + "Event listener has been updated.", ); } @@ -684,7 +684,7 @@ export default class RealmSettingsPage extends CommonPage { cy.findByTestId(this.eventListenersSaveBtn).click({ force: true }); cy.get(this.alertMessage).should( "be.visible", - "Event listener has been updated." + "Event listener has been updated.", ); cy.get(this.eventListenersDrpDwn).should("not.have.text", "email"); } @@ -773,7 +773,7 @@ export default class RealmSettingsPage extends CommonPage { cy.findByTestId(this.jsonEditorSaveBtn).click(); cy.get(this.alertMessage).should( "be.visible", - "The client profiles configuration was updated" + "The client profiles configuration was updated", ); cy.findByTestId(this.formViewProfilesView).check(); cy.get("table").should("be.visible").contains("td", "Test"); @@ -792,7 +792,7 @@ export default class RealmSettingsPage extends CommonPage { cy.findByTestId(this.saveNewClientProfileBtn).click(); cy.get(this.alertMessage).should( "be.visible", - "Client profile updated successfully" + "Client profile updated successfully", ); } @@ -810,7 +810,7 @@ export default class RealmSettingsPage extends CommonPage { shouldShowErrorWhenDuplicate() { cy.get("form").should( "not.have.text", - "The name must be unique within the realm" + "The name must be unique within the realm", ); } @@ -820,7 +820,7 @@ export default class RealmSettingsPage extends CommonPage { cy.findByTestId(this.reloadBtn).click(); cy.findByTestId(this.newClientProfileNameInput).should( "have.value", - "Edit" + "Edit", ); } @@ -828,7 +828,7 @@ export default class RealmSettingsPage extends CommonPage { cy.get(this.clientProfileTwo).click(); cy.get('h2[class*="kc-emptyExecutors"]').should( "have.text", - "No executors configured" + "No executors configured", ); } @@ -842,7 +842,7 @@ export default class RealmSettingsPage extends CommonPage { cy.get(this.addExecutorCancelBtn).click(); cy.get('h2[class*="kc-emptyExecutors"]').should( "have.text", - "No executors configured" + "No executors configured", ); } @@ -856,11 +856,11 @@ export default class RealmSettingsPage extends CommonPage { cy.findByTestId(this.addExecutorSaveBtn).click(); cy.get(this.alertMessage).should( "be.visible", - "Success! Executor created successfully" + "Success! Executor created successfully", ); cy.get('ul[class*="pf-c-data-list"]').should( "have.text", - "secure-ciba-signed-authn-req" + "secure-ciba-signed-authn-req", ); } @@ -869,22 +869,22 @@ export default class RealmSettingsPage extends CommonPage { cy.get('svg[class*="kc-executor-trash-icon"]').click(); cy.get(this.modalDialogTitle).contains("Delete executor?"); cy.get(this.modalDialogBodyText).contains( - "The action will permanently delete secure-ciba-signed-authn-req. This cannot be undone." + "The action will permanently delete secure-ciba-signed-authn-req. This cannot be undone.", ); cy.findByTestId(this.modalConfirm).contains("Delete"); cy.get(this.deleteDialogCancelBtn).contains("Cancel").click(); cy.get('ul[class*="pf-c-data-list"]').should( "have.text", - "secure-ciba-signed-authn-req" + "secure-ciba-signed-authn-req", ); } openProfileDetails(name: string) { cy.intercept( - `/admin/realms/${this.realmName}/client-policies/profiles*` + `/admin/realms/${this.realmName}/client-policies/profiles*`, ).as("profilesFetch"); cy.get( - 'a[href*="realm-settings/client-policies/' + name + '/edit-profile"]' + 'a[href*="realm-settings/client-policies/' + name + '/edit-profile"]', ).click(); cy.wait("@profilesFetch"); return this; @@ -892,7 +892,7 @@ export default class RealmSettingsPage extends CommonPage { editExecutor(availablePeriod?: number) { cy.intercept( - `/admin/realms/${this.realmName}/client-policies/profiles*` + `/admin/realms/${this.realmName}/client-policies/profiles*`, ).as("profilesFetch"); cy.get(this.editExecutorBtn).click(); cy.wait("@profilesFetch"); @@ -917,7 +917,7 @@ export default class RealmSettingsPage extends CommonPage { checkExecutorNotInList() { cy.get('ul[class*="pf-c-data-list"]').should( "have.text", - "secure-ciba-signed-authn-req" + "secure-ciba-signed-authn-req", ); return this; } @@ -925,7 +925,7 @@ export default class RealmSettingsPage extends CommonPage { checkAvailablePeriodExecutor(value: number) { cy.findByTestId(this.availablePeriodExecutorFld).should( "have.value", - value + value, ); return this; } @@ -937,7 +937,7 @@ export default class RealmSettingsPage extends CommonPage { cy.findByTestId(this.addExecutorSaveBtn).click(); cy.get(this.alertMessage).should( "be.visible", - "Executor updated successfully" + "Executor updated successfully", ); } @@ -946,13 +946,13 @@ export default class RealmSettingsPage extends CommonPage { cy.get('svg[class*="kc-executor-trash-icon"]').click(); cy.get(this.modalDialogTitle).contains("Delete executor?"); cy.get(this.modalDialogBodyText).contains( - "The action will permanently delete secure-ciba-signed-authn-req. This cannot be undone." + "The action will permanently delete secure-ciba-signed-authn-req. This cannot be undone.", ); cy.findByTestId(this.modalConfirm).contains("Delete"); cy.findByTestId(this.modalConfirm).click(); cy.get('h2[class*="kc-emptyExecutors"]').should( "have.text", - "No executors configured" + "No executors configured", ); } @@ -985,7 +985,7 @@ export default class RealmSettingsPage extends CommonPage { cy.get(this.alertMessage).should( "be.visible", - "The client policy configuration was updated" + "The client policy configuration was updated", ); cy.findByTestId(this.formViewSelectPolicies).check(); cy.get("table").should("be.visible").contains("td", "Test"); @@ -997,7 +997,7 @@ export default class RealmSettingsPage extends CommonPage { cy.findByTestId(this.jsonEditorReloadBtn).contains("Reload"); cy.findByTestId(this.formViewSelectPolicies).check(); cy.findByTestId(this.createPolicyEmptyStateBtn).contains( - "Create client policy" + "Create client policy", ); } @@ -1024,7 +1024,7 @@ export default class RealmSettingsPage extends CommonPage { createNewClientPolicyFromList( name: string, description: string, - cancel?: boolean + cancel?: boolean, ) { cy.findByTestId(this.createPolicyBtn).click(); cy.findByTestId(this.newClientPolicyNameInput).type(name); @@ -1049,7 +1049,7 @@ export default class RealmSettingsPage extends CommonPage { cy.get(this.clientPolicy).click(); cy.get('h2[class*="kc-emptyConditions"]').should( "have.text", - "No conditions configured" + "No conditions configured", ); } @@ -1063,7 +1063,7 @@ export default class RealmSettingsPage extends CommonPage { cy.findByTestId(this.addConditionCancelBtn).click(); cy.get('h2[class*="kc-emptyConditions"]').should( "have.text", - "No conditions configured" + "No conditions configured", ); } @@ -1079,7 +1079,7 @@ export default class RealmSettingsPage extends CommonPage { cy.findByTestId(this.addConditionSaveBtn).click(); cy.get(this.alertMessage).should( "be.visible", - "Success! Condition created successfully" + "Success! Condition created successfully", ); cy.get('ul[class*="pf-c-data-list"]').should("have.text", "client-roles"); } @@ -1105,7 +1105,7 @@ export default class RealmSettingsPage extends CommonPage { cy.findByTestId(this.addConditionSaveBtn).click(); cy.get(this.alertMessage).should( "be.visible", - "Success! Condition created successfully" + "Success! Condition created successfully", ); cy.get('ul[class*="pf-c-data-list"]').contains("client-scopes"); } @@ -1121,7 +1121,7 @@ export default class RealmSettingsPage extends CommonPage { cy.findByTestId(this.addConditionSaveBtn).click(); cy.get(this.alertMessage).should( "be.visible", - "Success! Condition updated successfully" + "Success! Condition updated successfully", ); } @@ -1135,7 +1135,7 @@ export default class RealmSettingsPage extends CommonPage { cy.findByTestId(this.addConditionSaveBtn).click(); cy.get(this.alertMessage).should( "be.visible", - "Success! Condition updated successfully" + "Success! Condition updated successfully", ); } @@ -1155,13 +1155,13 @@ export default class RealmSettingsPage extends CommonPage { cy.findByTestId(this.deleteClientScopesConditionBtn).click(); cy.get(this.modalDialogTitle).contains("Delete condition?"); cy.get(this.modalDialogBodyText).contains( - "This action will permanently delete client-scopes. This cannot be undone." + "This action will permanently delete client-scopes. This cannot be undone.", ); cy.findByTestId(this.modalConfirm).contains("Delete"); cy.findByTestId(this.modalConfirm).click({ force: true }); cy.get('h2[class*="kc-emptyConditions"]').should( "have.text", - "No conditions configured" + "No conditions configured", ); } @@ -1183,7 +1183,7 @@ export default class RealmSettingsPage extends CommonPage { createNewClientPolicyFromEmptyState( name: string, description: string, - cancel?: boolean + cancel?: boolean, ) { cy.findByTestId(this.createPolicyEmptyStateBtn).click(); cy.findByTestId(this.newClientPolicyNameInput).type(name); diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/realm_settings/tabs/realmsettings_events_subtabs/AdminEventsSettingsTab.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/realm_settings/tabs/realmsettings_events_subtabs/AdminEventsSettingsTab.ts index 2e378645fd16..7513bff1af12 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/realm_settings/tabs/realmsettings_events_subtabs/AdminEventsSettingsTab.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/realm_settings/tabs/realmsettings_events_subtabs/AdminEventsSettingsTab.ts @@ -40,7 +40,7 @@ export default class AdminEventsSettingsTab extends PageObject { { waitForRealm, waitForConfig } = { waitForRealm: true, waitForConfig: false, - } + }, ) { waitForRealm && cy.intercept("/admin/realms/*").as("saveRealm"); waitForConfig && diff --git a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/users/user_details/tabs/IdentityProviderLinksTab.ts b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/users/user_details/tabs/IdentityProviderLinksTab.ts index 03b3e46a48aa..e8207827e4cd 100644 --- a/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/users/user_details/tabs/IdentityProviderLinksTab.ts +++ b/js/apps/admin-ui/cypress/support/pages/admin-ui/manage/users/user_details/tabs/IdentityProviderLinksTab.ts @@ -62,7 +62,7 @@ export default class IdentityProviderLinksTab { public assertNoIdentityProvidersLinkedMessageExist(exist: boolean) { cy.get(this.linkedProvidersSection).should( (exist ? "" : "not.") + "contain.text", - "No identity providers linked. Choose one from the list below." + "No identity providers linked. Choose one from the list below.", ); return this; @@ -71,7 +71,7 @@ export default class IdentityProviderLinksTab { public assertNoAvailableIdentityProvidersMessageExist(exist: boolean) { cy.get(this.availableProvidersSection).should( (exist ? "" : "not.") + "contain.text", - "No available identity providers." + "No available identity providers.", ); return this; @@ -92,7 +92,7 @@ export default class IdentityProviderLinksTab { public assertLinkAccountModalIdentityProviderInputEqual(idpName: string) { cy.findByTestId(this.linkAccountModalIdentityProviderInput).should( "have.value", - idpName + idpName, ); return this; @@ -106,7 +106,7 @@ export default class IdentityProviderLinksTab { public assertNotificationAlreadyLinkedError() { masthead.checkNotificationMessage( - "Could not link identity provider User is already linked with provider" + "Could not link identity provider User is already linked with provider", ); return this; @@ -144,7 +144,7 @@ export default class IdentityProviderLinksTab { public assertLinkedIdentityProviderExist(idpName: string, exist: boolean) { cy.get(this.linkedProvidersSection).should( (exist ? "" : "not.") + "contain.text", - idpName + idpName, ); return this; @@ -153,7 +153,7 @@ export default class IdentityProviderLinksTab { public assertAvailableIdentityProviderExist(idpName: string, exist: boolean) { cy.get(this.availableProvidersSection).should( (exist ? "" : "not.") + "contain.text", - idpName + idpName, ); return this; diff --git a/js/apps/admin-ui/cypress/support/util/AdminClient.ts b/js/apps/admin-ui/cypress/support/util/AdminClient.ts index 60707027bb1a..506276e36627 100644 --- a/js/apps/admin-ui/cypress/support/util/AdminClient.ts +++ b/js/apps/admin-ui/cypress/support/util/AdminClient.ts @@ -83,7 +83,7 @@ class AdminClient { } else { parentGroup = await this.client.groups.createChildGroup( { id: parentGroup.id }, - { name: group } + { name: group }, ); } createdGroups.push(parentGroup); @@ -107,7 +107,7 @@ class AdminClient { if (!createdUser) { throw new Error( - "Unable to create user, created user could not be found." + "Unable to create user, created user could not be found.", ); } @@ -177,7 +177,7 @@ class AdminClient { async addDefaultClientScopeInClient( clientScopeName: string, - clientId: string + clientId: string, ) { await this.login(); const scope = await this.client.clientScopes.findOneByName({ @@ -192,7 +192,7 @@ class AdminClient { async removeDefaultClientScopeInClient( clientScopeName: string, - clientId: string + clientId: string, ) { await this.login(); const scope = await this.client.clientScopes.findOneByName({ @@ -211,7 +211,7 @@ class AdminClient { const currentProfile = await this.client.users.getProfile({ realm }); await this.client.users.updateProfile( - merge(currentProfile, payload, { realm }) + merge(currentProfile, payload, { realm }), ); } @@ -247,7 +247,7 @@ class AdminClient { async unlinkAccountIdentityProvider( username: string, - idpDisplayName: string + idpDisplayName: string, ) { await this.login(); const user = await this.client.users.find({ username }); @@ -282,7 +282,7 @@ class AdminClient { await this.login(); await this.client.realms.addLocalization( { realm: this.client.realmName, selectedLocale: locale, key: key }, - value + value, ); } @@ -296,8 +296,8 @@ class AdminClient { this.client.realms.deleteRealmLocalizationTexts({ realm: this.client.realmName, selectedLocale: locale, - }) - ) + }), + ), ); } } diff --git a/js/apps/admin-ui/cypress/support/util/grantClipboardAccess.ts b/js/apps/admin-ui/cypress/support/util/grantClipboardAccess.ts index daf2ebeada0d..86a12a14da5b 100644 --- a/js/apps/admin-ui/cypress/support/util/grantClipboardAccess.ts +++ b/js/apps/admin-ui/cypress/support/util/grantClipboardAccess.ts @@ -8,6 +8,6 @@ export default function grantClipboardAccess() { permissions: ["clipboardReadWrite", "clipboardSanitizedWrite"], origin: window.location.origin, }, - }) + }), ); } diff --git a/js/apps/admin-ui/src/PageNav.tsx b/js/apps/admin-ui/src/PageNav.tsx index e0acfa2bef97..bf70e84acfea 100644 --- a/js/apps/admin-ui/src/PageNav.tsx +++ b/js/apps/admin-ui/src/PageNav.tsx @@ -25,7 +25,7 @@ const LeftNav = ({ title, path }: LeftNavProps) => { const { hasAccess } = useAccess(); const { realm } = useRealm(); const route = routes.find( - (route) => route.path.replace(/\/:.+?(\?|(?:(?!\/).)*|$)/g, "") === path + (route) => route.path.replace(/\/:.+?(\?|(?:(?!\/).)*|$)/g, "") === path, ); const accessAllowed = @@ -76,13 +76,13 @@ export const PageNav = () => { "query-groups", "query-users", "query-clients", - "view-events" + "view-events", ); const showConfigure = hasSomeAccess( "view-realm", "query-clients", - "view-identity-providers" + "view-identity-providers", ); const isOnAddRealm = !!useMatch(AddRealmRoute.path); diff --git a/js/apps/admin-ui/src/authentication/AuthenticationSection.tsx b/js/apps/admin-ui/src/authentication/AuthenticationSection.tsx index 88d4a28e1fdd..c9b0c2da9cc5 100644 --- a/js/apps/admin-ui/src/authentication/AuthenticationSection.tsx +++ b/js/apps/admin-ui/src/authentication/AuthenticationSection.tsx @@ -103,12 +103,12 @@ export default function AuthenticationSection() { const loader = async () => { const flowsRequest = await fetch( `${addTrailingSlash( - adminClient.baseUrl + adminClient.baseUrl, )}admin/realms/${realmName}/ui-ext/authentication-management/flows`, { method: "GET", headers: getAuthorizationHeaders(await adminClient.getAccessToken()), - } + }, ); const flows = await flowsRequest.json(); @@ -118,7 +118,7 @@ export default function AuthenticationSection() { return sortBy( localeSort(flows, mapByKey("alias")), - (flow) => flow.usedBy?.type + (flow) => flow.usedBy?.type, ); }; diff --git a/js/apps/admin-ui/src/authentication/BindFlowDialog.tsx b/js/apps/admin-ui/src/authentication/BindFlowDialog.tsx index 1de58f840132..8b480d5c7531 100644 --- a/js/apps/admin-ui/src/authentication/BindFlowDialog.tsx +++ b/js/apps/admin-ui/src/authentication/BindFlowDialog.tsx @@ -41,7 +41,7 @@ export const BindFlowDialog = ({ flowAlias, onClose }: BindFlowDialogProps) => { try { await adminClient.realms.update( { realm }, - { ...realmRep, [bindingType]: flowAlias } + { ...realmRep, [bindingType]: flowAlias }, ); addAlert(t("updateFlowSuccess"), AlertVariant.success); } catch (error) { diff --git a/js/apps/admin-ui/src/authentication/DuplicateFlowModal.tsx b/js/apps/admin-ui/src/authentication/DuplicateFlowModal.tsx index 434bde53dfb1..46126e4eb062 100644 --- a/js/apps/admin-ui/src/authentication/DuplicateFlowModal.tsx +++ b/js/apps/admin-ui/src/authentication/DuplicateFlowModal.tsx @@ -58,7 +58,7 @@ export const DuplicateFlowModal = ({ newFlow.description = form.description; await adminClient.authenticationManagement.updateFlow( { flowId: newFlow.id! }, - newFlow + newFlow, ); } addAlert(t("copyFlowSuccess"), AlertVariant.success); @@ -68,7 +68,7 @@ export const DuplicateFlowModal = ({ id: newFlow.id!, usedBy: "notInUse", builtIn: newFlow.builtIn ? "builtIn" : undefined, - }) + }), ); } catch (error) { addError("authentication:copyFlowError", error); diff --git a/js/apps/admin-ui/src/authentication/EditFlowModal.tsx b/js/apps/admin-ui/src/authentication/EditFlowModal.tsx index a6081083d781..4677d8d0919a 100644 --- a/js/apps/admin-ui/src/authentication/EditFlowModal.tsx +++ b/js/apps/admin-ui/src/authentication/EditFlowModal.tsx @@ -32,7 +32,7 @@ export const EditFlowModal = ({ flow, toggleDialog }: EditFlowModalProps) => { try { await adminClient.authenticationManagement.updateFlow( { flowId: flow.id! }, - { ...flow, ...formValues } + { ...flow, ...formValues }, ); addAlert(t("updateFlowSuccess"), AlertVariant.success); } catch (error) { diff --git a/js/apps/admin-ui/src/authentication/FlowDetails.tsx b/js/apps/admin-ui/src/authentication/FlowDetails.tsx index fe05af7cc1fb..d8fa2df61b1d 100644 --- a/js/apps/admin-ui/src/authentication/FlowDetails.tsx +++ b/js/apps/admin-ui/src/authentication/FlowDetails.tsx @@ -47,7 +47,7 @@ import { toAuthentication } from "./routes/Authentication"; import type { FlowParams } from "./routes/Flow"; export const providerConditionFilter = ( - value: AuthenticationProviderRepresentation + value: AuthenticationProviderRepresentation, ) => value.displayName?.startsWith("Condition "); export default function FlowDetails() { @@ -93,12 +93,12 @@ export default function FlowDetails() { setFlow(flow); setExecutionList(new ExecutionList(executions)); }, - [key] + [key], ); const executeChange = async ( ex: AuthenticationFlowRepresentation, - change: LevelChange | IndexChange + change: LevelChange | IndexChange, ) => { try { let id = ex.id!; @@ -136,7 +136,7 @@ export default function FlowDetails() { try { await adminClient.authenticationManagement.updateExecution( { flow: flow?.alias! }, - ex + ex, ); refresh(); addAlert(t("updateFlowSuccess"), AlertVariant.success); @@ -147,7 +147,7 @@ export default function FlowDetails() { const addExecution = async ( name: string, - type: AuthenticationProviderRepresentation + type: AuthenticationProviderRepresentation, ) => { try { await adminClient.authenticationManagement.addExecutionToFlow({ @@ -163,7 +163,7 @@ export default function FlowDetails() { const addFlow = async ( flow: string, - { name, description = "", type, provider }: Flow + { name, description = "", type, provider }: Flow, ) => { try { await adminClient.authenticationManagement.addFlowToFlow({ @@ -359,18 +359,18 @@ export default function FlowDetails() { onDragFinish={(order) => { const withoutHeaderId = order.slice(1); setLiveText( - t("common:onDragFinish", { list: dragged?.displayName }) + t("common:onDragFinish", { list: dragged?.displayName }), ); const change = executionList.getChange( dragged!, - withoutHeaderId + withoutHeaderId, ); executeChange(dragged!, change); }} onDragStart={(id) => { const item = executionList.findExecution(id)!; setLiveText( - t("common:onDragStart", { item: item.displayName }) + t("common:onDragStart", { item: item.displayName }), ); setDragged(item); if (!item.isCollapsed) { @@ -380,7 +380,7 @@ export default function FlowDetails() { }} onDragMove={() => setLiveText( - t("common:onDragMove", { item: dragged?.displayName }) + t("common:onDragMove", { item: dragged?.displayName }), ) } onDragCancel={() => setLiveText(t("common:onDragCancel"))} diff --git a/js/apps/admin-ui/src/authentication/RequiredActions.tsx b/js/apps/admin-ui/src/authentication/RequiredActions.tsx index 27bfb7d45a10..d81067f442cc 100644 --- a/js/apps/admin-ui/src/authentication/RequiredActions.tsx +++ b/js/apps/admin-ui/src/authentication/RequiredActions.tsx @@ -51,7 +51,7 @@ export const RequiredActions = () => { ]; }, (actions) => setActions(actions), - [key] + [key], ); const isUnregisteredAction = (data: DataType): boolean => { @@ -60,14 +60,14 @@ export const RequiredActions = () => { const updateAction = async ( action: DataType, - field: "enabled" | "defaultAction" + field: "enabled" | "defaultAction", ) => { try { if (field in action) { action[field] = !action[field]; await adminClient.authenticationManagement.updateRequiredAction( { alias: action.alias! }, - action + action, ); } else if (isUnregisteredAction(action)) { await adminClient.authenticationManagement.registerRequiredAction({ @@ -84,7 +84,7 @@ export const RequiredActions = () => { const executeMove = async ( action: RequiredActionProviderRepresentation, - times: number + times: number, ) => { try { const alias = action.alias!; @@ -93,13 +93,13 @@ export const RequiredActions = () => { await adminClient.authenticationManagement.lowerRequiredActionPriority( { alias, - } + }, ); } else { await adminClient.authenticationManagement.raiseRequiredActionPriority( { alias, - } + }, ); } } diff --git a/js/apps/admin-ui/src/authentication/components/AddFlowDropdown.tsx b/js/apps/admin-ui/src/authentication/components/AddFlowDropdown.tsx index 8b93e27cc7d7..4582e7206a25 100644 --- a/js/apps/admin-ui/src/authentication/components/AddFlowDropdown.tsx +++ b/js/apps/admin-ui/src/authentication/components/AddFlowDropdown.tsx @@ -19,7 +19,7 @@ type AddFlowDropdownProps = { execution: ExpandableExecution; onAddExecution: ( execution: ExpandableExecution, - type: AuthenticationProviderRepresentation + type: AuthenticationProviderRepresentation, ) => void; onAddFlow: (execution: ExpandableExecution, flow: Flow) => void; }; @@ -41,7 +41,7 @@ export const AddFlowDropdown = ({ flowId: execution.flowId!, }), ({ providerId }) => setProviderId(providerId), - [] + [], ); return ( diff --git a/js/apps/admin-ui/src/authentication/components/DraggableTable.tsx b/js/apps/admin-ui/src/authentication/components/DraggableTable.tsx index bc39d7df2446..5b29d4c1a634 100644 --- a/js/apps/admin-ui/src/authentication/components/DraggableTable.tsx +++ b/js/apps/admin-ui/src/authentication/components/DraggableTable.tsx @@ -58,7 +58,7 @@ export function DraggableTable({ const itemOrder: string[] = useMemo( () => data.map((d) => get(d, keyField)), - [data] + [data], ); const onDragStart = (evt: ReactDragEvent) => { @@ -150,13 +150,13 @@ export function DraggableTable({ } else { const dragId = curListItem.id; const draggingToItemIndex = Array.from( - bodyRef.current?.children || [] + bodyRef.current?.children || [], ).findIndex((item) => item.id === dragId); if (draggingToItemIndex !== state.draggingToItemIndex) { const tempItemOrder = moveItem( itemOrder, state.draggedItemId, - draggingToItemIndex + draggingToItemIndex, ); move(tempItemOrder); @@ -241,7 +241,7 @@ export function DraggableTable({ items={actions.map(({ isActionable, ...action }) => isActionable ? { ...action, isDisabled: !isActionable(row) } - : action + : action, )} rowData={row!} /> diff --git a/js/apps/admin-ui/src/authentication/components/ExecutionConfigModal.tsx b/js/apps/admin-ui/src/authentication/components/ExecutionConfigModal.tsx index 706c4712306b..d6aef070051d 100644 --- a/js/apps/admin-ui/src/authentication/components/ExecutionConfigModal.tsx +++ b/js/apps/admin-ui/src/authentication/components/ExecutionConfigModal.tsx @@ -76,7 +76,7 @@ export const ExecutionConfigModal = ({ setConfigDescription(configDescription); setConfig(config); }, - [] + [], ); useEffect(() => { @@ -101,7 +101,7 @@ export const ExecutionConfigModal = ({ config: changedConfig.config, }; const { id } = await adminClient.authenticationManagement.createConfig( - newConfig + newConfig, ); setConfig({ ...newConfig.config, id, alias: newConfig.alias }); } diff --git a/js/apps/admin-ui/src/authentication/components/FlowDiagram.tsx b/js/apps/admin-ui/src/authentication/components/FlowDiagram.tsx index e1d79744d921..7b2117f5d726 100644 --- a/js/apps/admin-ui/src/authentication/components/FlowDiagram.tsx +++ b/js/apps/admin-ui/src/authentication/components/FlowDiagram.tsx @@ -39,7 +39,7 @@ const createEdge = (fromNode: string, toNode: string): Edge => ({ data: { onEdgeClick: ( evt: ReactMouseEvent, - id: string + id: string, ) => { evt.stopPropagation(); alert(`hello ${id}`); @@ -72,7 +72,7 @@ const renderParallelNodes = (execution: ExpandableExecution): Node[] => [ const renderParallelEdges = ( start: AuthenticationExecutionInfoRepresentation, execution: ExpandableExecution, - end: AuthenticationExecutionInfoRepresentation + end: AuthenticationExecutionInfoRepresentation, ): Edge[] => [ createEdge(start.id!, execution.id!), createEdge(execution.id!, end.id!), @@ -88,7 +88,7 @@ const renderSequentialEdges = ( end: AuthenticationExecutionInfoRepresentation, prefExecution: ExpandableExecution, isFirst: boolean, - isLast: boolean + isLast: boolean, ): Edge[] => { const edges: Edge[] = []; @@ -135,7 +135,7 @@ const renderSubFlowEdges = ( execution: ExpandableExecution, start: AuthenticationExecutionInfoRepresentation, end: AuthenticationExecutionInfoRepresentation, - prefExecution?: ExpandableExecution + prefExecution?: ExpandableExecution, ): Edge[] => { const edges: Edge[] = []; @@ -146,8 +146,8 @@ const renderSubFlowEdges = ( prefExecution && prefExecution.requirement !== "ALTERNATIVE" ? prefExecution.id! : start.id!, - execution.id! - ) + execution.id!, + ), ); edges.push(createEdge(endSubFlowId, end.id!)); @@ -155,7 +155,7 @@ const renderSubFlowEdges = ( renderFlowEdges(execution, execution.executionList || [], { ...execution, id: endSubFlowId, - }) + }), ); }; @@ -184,7 +184,7 @@ const renderFlowNodes = (executionList: ExpandableExecution[]): Node[] => { const renderFlowEdges = ( start: AuthenticationExecutionInfoRepresentation, executionList: ExpandableExecution[], - end: AuthenticationExecutionInfoRepresentation + end: AuthenticationExecutionInfoRepresentation, ): Edge[] => { let elements: Edge[] = []; @@ -192,7 +192,7 @@ const renderFlowEdges = ( const execution = executionList[index]; if (execution.executionList) { elements = elements.concat( - renderSubFlowEdges(execution, start, end, executionList[index - 1]) + renderSubFlowEdges(execution, start, end, executionList[index - 1]), ); } else { if ( @@ -208,8 +208,8 @@ const renderFlowEdges = ( end, executionList[index - 1], index === 0, - index === executionList.length - 1 - ) + index === executionList.length - 1, + ), ); } } @@ -254,7 +254,7 @@ function renderEdges(expandableList: ExpandableExecution[]): Edge[] { return getLayoutedEdges( renderFlowEdges({ id: "start" }, expandableList, { id: "end", - }) + }), ); } diff --git a/js/apps/admin-ui/src/authentication/components/FlowRow.tsx b/js/apps/admin-ui/src/authentication/components/FlowRow.tsx index e7f8ecf5070b..6bd6993c3c32 100644 --- a/js/apps/admin-ui/src/authentication/components/FlowRow.tsx +++ b/js/apps/admin-ui/src/authentication/components/FlowRow.tsx @@ -32,7 +32,7 @@ type FlowRowProps = { onRowChange: (execution: ExpandableExecution) => void; onAddExecution: ( execution: ExpandableExecution, - type: AuthenticationProviderRepresentation + type: AuthenticationProviderRepresentation, ) => void; onAddFlow: (execution: ExpandableExecution, flow: Flow) => void; onDelete: (execution: ExpandableExecution) => void; diff --git a/js/apps/admin-ui/src/authentication/components/UsedBy.tsx b/js/apps/admin-ui/src/authentication/components/UsedBy.tsx index 2c4eee56ba71..ae28cc71fad3 100644 --- a/js/apps/admin-ui/src/authentication/components/UsedBy.tsx +++ b/js/apps/admin-ui/src/authentication/components/UsedBy.tsx @@ -41,7 +41,7 @@ const UsedByModal = ({ id, isSpecificClient, onClose }: UsedByModalProps) => { const loader = async ( first?: number, max?: number, - search?: string + search?: string, ): Promise<{ name: string }[]> => { const result = await fetchUsedBy({ id, @@ -99,7 +99,7 @@ export const UsedBy = ({ authType: { id, usedBy }, realm }: UsedByProps) => { const [open, toggle] = useToggle(); const key = Object.entries(realm).find( - (e) => e[1] === usedBy?.values[0] + (e) => e[1] === usedBy?.values[0], )?.[0]; return ( @@ -123,7 +123,7 @@ export const UsedBy = ({ authType: { id, usedBy }, realm }: UsedByProps) => { "appliedBy" + (usedBy.type === "SPECIFIC_CLIENTS" ? "Clients" - : "Providers") + : "Providers"), )}{" "} {usedBy.values.map((used, index) => ( <> diff --git a/js/apps/admin-ui/src/authentication/components/diagram/ButtonEdge.tsx b/js/apps/admin-ui/src/authentication/components/diagram/ButtonEdge.tsx index 550afa3149c8..7e6ad6e484ce 100644 --- a/js/apps/admin-ui/src/authentication/components/diagram/ButtonEdge.tsx +++ b/js/apps/admin-ui/src/authentication/components/diagram/ButtonEdge.tsx @@ -12,7 +12,7 @@ export type ButtonEdgeProps = EdgeProps & { data: { onEdgeClick: ( evt: ReactMouseEvent, - id: string + id: string, ) => void; }; }; diff --git a/js/apps/admin-ui/src/authentication/components/modals/AddStepModal.tsx b/js/apps/admin-ui/src/authentication/components/modals/AddStepModal.tsx index f6cf078513ec..126710aa7af5 100644 --- a/js/apps/admin-ui/src/authentication/components/modals/AddStepModal.tsx +++ b/js/apps/admin-ui/src/authentication/components/modals/AddStepModal.tsx @@ -87,7 +87,7 @@ export const AddStepModal = ({ name, type, onSelect }: AddStepModalProps) => { } }, (providers) => setProviders(providers), - [] + [], ); const page = useMemo( @@ -95,10 +95,10 @@ export const AddStepModal = ({ name, type, onSelect }: AddStepModalProps) => { localeSort(providers ?? [], mapByKey("displayName")) .filter( (p) => - p.displayName?.includes(search) || p.description?.includes(search) + p.displayName?.includes(search) || p.description?.includes(search), ) .slice(first, first + max + 1), - [providers, search, first, max] + [providers, search, first, max], ); return ( diff --git a/js/apps/admin-ui/src/authentication/components/modals/AddSubFlowModal.tsx b/js/apps/admin-ui/src/authentication/components/modals/AddSubFlowModal.tsx index 1b0570313c38..96fb59f7fd79 100644 --- a/js/apps/admin-ui/src/authentication/components/modals/AddSubFlowModal.tsx +++ b/js/apps/admin-ui/src/authentication/components/modals/AddSubFlowModal.tsx @@ -56,7 +56,7 @@ export const AddSubFlowModal = ({ useFetch( () => adminClient.authenticationManagement.getFormProviders(), setFormProviders, - [] + [], ); useEffect(() => { diff --git a/js/apps/admin-ui/src/authentication/execution-model.ts b/js/apps/admin-ui/src/authentication/execution-model.ts index cbf010ec04dc..ca9e12c10fb4 100644 --- a/js/apps/admin-ui/src/authentication/execution-model.ts +++ b/js/apps/admin-ui/src/authentication/execution-model.ts @@ -21,7 +21,7 @@ export class LevelChange extends IndexChange { constructor( oldIndex: number, newIndex: number, - parent?: ExpandableExecution + parent?: ExpandableExecution, ) { super(oldIndex, newIndex); this.parent = parent; @@ -46,7 +46,7 @@ export class ExecutionList { private transformToExpandableList( currentIndex: number, currentLevel: number, - execution: ExpandableExecution + execution: ExpandableExecution, ) { for (let index = currentIndex; index < this.list.length; index++) { const ex = this.list[index]; @@ -82,7 +82,7 @@ export class ExecutionList { findExecution( id: string, - list?: ExpandableExecution[] + list?: ExpandableExecution[], ): ExpandableExecution | undefined { let found = (list || this.expandableList).find((ex) => ex.id === id); if (!found) { @@ -113,7 +113,7 @@ export class ExecutionList { getChange( changed: AuthenticationExecutionInfoRepresentation, - order: string[] + order: string[], ) { const currentOrder = this.order(); const newLocIndex = order.findIndex((id) => id === changed.id); @@ -127,7 +127,7 @@ export class ExecutionList { return new LevelChange( parent?.executionList?.length || 0, newLocation.index!, - parent + parent, ); } return new LevelChange(this.expandableList.length, newLocation.index!); diff --git a/js/apps/admin-ui/src/authentication/form/CreateFlow.tsx b/js/apps/admin-ui/src/authentication/form/CreateFlow.tsx index 63743a9e19a5..972a13f975f0 100644 --- a/js/apps/admin-ui/src/authentication/form/CreateFlow.tsx +++ b/js/apps/admin-ui/src/authentication/form/CreateFlow.tsx @@ -32,7 +32,7 @@ export default function CreateFlow() { try { const { id } = await adminClient.authenticationManagement.createFlow( - flow + flow, ); addAlert(t("flowCreatedSuccess"), AlertVariant.success); navigate( @@ -40,14 +40,14 @@ export default function CreateFlow() { realm, id: id!, usedBy: "notInUse", - }) + }), ); } catch (error: any) { addAlert( t("flowCreateError", { error: error.response?.data?.errorMessage || error, }), - AlertVariant.danger + AlertVariant.danger, ); } }; diff --git a/js/apps/admin-ui/src/authentication/policies/CibaPolicy.tsx b/js/apps/admin-ui/src/authentication/policies/CibaPolicy.tsx index 076811ea316e..2e78bad11b2a 100644 --- a/js/apps/admin-ui/src/authentication/policies/CibaPolicy.tsx +++ b/js/apps/admin-ui/src/authentication/policies/CibaPolicy.tsx @@ -67,7 +67,7 @@ export const CibaPolicy = ({ realm, realmUpdated }: CibaPolicyProps) => { try { await adminClient.realms.update( { realm: realmName }, - convertFormValuesToObject(formValues) + convertFormValuesToObject(formValues), ); const updatedRealm = await adminClient.realms.findOne({ @@ -95,7 +95,7 @@ export const CibaPolicy = ({ realm, realmUpdated }: CibaPolicyProps) => { labelIcon={ diff --git a/js/apps/admin-ui/src/authentication/policies/OtpPolicy.tsx b/js/apps/admin-ui/src/authentication/policies/OtpPolicy.tsx index d30badf682ca..c0f8360c61e7 100644 --- a/js/apps/admin-ui/src/authentication/policies/OtpPolicy.tsx +++ b/js/apps/admin-ui/src/authentication/policies/OtpPolicy.tsx @@ -70,7 +70,7 @@ export const OtpPolicy = ({ realm, realmUpdated }: OtpPolicyProps) => { const supportedApplications = useMemo(() => { const labels = (realm.otpSupportedApplications ?? []).map((key) => - t(`otpSupportedApplications.${key}`) + t(`otpSupportedApplications.${key}`), ); return localeSort(labels, (label) => label); diff --git a/js/apps/admin-ui/src/authentication/policies/PasswordPolicy.tsx b/js/apps/admin-ui/src/authentication/policies/PasswordPolicy.tsx index d2124d89312a..59d18fb14697 100644 --- a/js/apps/admin-ui/src/authentication/policies/PasswordPolicy.tsx +++ b/js/apps/admin-ui/src/authentication/policies/PasswordPolicy.tsx @@ -44,9 +44,9 @@ const PolicySelect = ({ onSelect, selectedPolicies }: PolicySelectProps) => { const policies = useMemo( () => passwordPolicies?.filter( - (p) => selectedPolicies.find((o) => o.id === p.id) === undefined + (p) => selectedPolicies.find((o) => o.id === p.id) === undefined, ), - [selectedPolicies] + [selectedPolicies], ); return ( diff --git a/js/apps/admin-ui/src/authentication/policies/Policies.tsx b/js/apps/admin-ui/src/authentication/policies/Policies.tsx index 338d2299cee9..9b2258241c0a 100644 --- a/js/apps/admin-ui/src/authentication/policies/Policies.tsx +++ b/js/apps/admin-ui/src/authentication/policies/Policies.tsx @@ -29,7 +29,7 @@ export const Policies = () => { (realm) => { setRealm(realm); }, - [] + [], ); if (!realm) { diff --git a/js/apps/admin-ui/src/authentication/policies/WebauthnPolicy.tsx b/js/apps/admin-ui/src/authentication/policies/WebauthnPolicy.tsx index 7a29f918015b..0c03a5fb0add 100644 --- a/js/apps/admin-ui/src/authentication/policies/WebauthnPolicy.tsx +++ b/js/apps/admin-ui/src/authentication/policies/WebauthnPolicy.tsx @@ -108,7 +108,7 @@ const WebauthnSelect = ({ onSelect={(_, selectedValue) => { if (isMultiSelect) { const changedValue = field.value.find( - (item: string) => item === selectedValue + (item: string) => item === selectedValue, ) ? field.value.filter((item: string) => item !== selectedValue) : [...field.value, selectedValue]; @@ -313,7 +313,7 @@ export const WebauthnPolicy = ({ labelIcon={ @@ -341,7 +341,7 @@ export const WebauthnPolicy = ({ labelIcon={ diff --git a/js/apps/admin-ui/src/authentication/policies/util.test.ts b/js/apps/admin-ui/src/authentication/policies/util.test.ts index ffc879632f54..0884da9772b8 100644 --- a/js/apps/admin-ui/src/authentication/policies/util.test.ts +++ b/js/apps/admin-ui/src/authentication/policies/util.test.ts @@ -19,7 +19,7 @@ describe("serializePolicy", () => { }; expect(serializePolicy(policies, submittedValues)).toEqual( - "one(value1) and two(value2)" + "one(value1) and two(value2)", ); }); }); diff --git a/js/apps/admin-ui/src/authentication/policies/util.ts b/js/apps/admin-ui/src/authentication/policies/util.ts index acb6def9c1e0..c47d397d75ca 100644 --- a/js/apps/admin-ui/src/authentication/policies/util.ts +++ b/js/apps/admin-ui/src/authentication/policies/util.ts @@ -8,7 +8,7 @@ const POLICY_SEPARATOR = " and "; export const serializePolicy = ( policies: PasswordPolicyTypeRepresentation[], - submitted: SubmittedValues + submitted: SubmittedValues, ) => policies .map((policy) => `${policy.id}(${submitted[policy.id!]})`) @@ -20,7 +20,7 @@ type PolicyValue = PasswordPolicyTypeRepresentation & { export const parsePolicy = ( value: string, - policies: PasswordPolicyTypeRepresentation[] + policies: PasswordPolicyTypeRepresentation[], ) => value .split(POLICY_SEPARATOR) diff --git a/js/apps/admin-ui/src/authentication/routes/Authentication.tsx b/js/apps/admin-ui/src/authentication/routes/Authentication.tsx index a34467504f66..31b0801b92fc 100644 --- a/js/apps/admin-ui/src/authentication/routes/Authentication.tsx +++ b/js/apps/admin-ui/src/authentication/routes/Authentication.tsx @@ -24,7 +24,7 @@ export const AuthenticationRouteWithTab: AppRouteObject = { }; export const toAuthentication = ( - params: AuthenticationParams + params: AuthenticationParams, ): Partial => { const path = params.tab ? AuthenticationRouteWithTab.path diff --git a/js/apps/admin-ui/src/client-scopes/ChangeTypeDropdown.tsx b/js/apps/admin-ui/src/client-scopes/ChangeTypeDropdown.tsx index 437a2c849f95..5f4b93480477 100644 --- a/js/apps/admin-ui/src/client-scopes/ChangeTypeDropdown.tsx +++ b/js/apps/admin-ui/src/client-scopes/ChangeTypeDropdown.tsx @@ -46,10 +46,10 @@ export const ChangeTypeDropdown = ({ clientId, row, row.type, - value as ClientScope + value as ClientScope, ) : changeScope(row, value as ClientScope); - }) + }), ); setOpen(false); refresh(); @@ -61,7 +61,7 @@ export const ChangeTypeDropdown = ({ > {clientScopeTypesSelectOptions( t, - !clientId ? allClientScopeTypes : undefined + !clientId ? allClientScopeTypes : undefined, )} ); diff --git a/js/apps/admin-ui/src/client-scopes/ClientScopesSection.tsx b/js/apps/admin-ui/src/client-scopes/ClientScopesSection.tsx index 5831b15ba7ce..1cabd78ecd9a 100644 --- a/js/apps/admin-ui/src/client-scopes/ClientScopesSection.tsx +++ b/js/apps/admin-ui/src/client-scopes/ClientScopesSection.tsx @@ -99,7 +99,7 @@ export default function ClientScopesSection() { const [searchType, setSearchType] = useState("name"); const [searchTypeType, setSearchTypeType] = useState( - AllClientScopes.none + AllClientScopes.none, ); const [searchProtocol, setSearchProtocol] = useState("all"); const localeSort = useLocaleSort(); @@ -129,11 +129,11 @@ export default function ClientScopesSection() { const row: Row = { ...scope, type: defaultScopes.find( - (defaultScope) => defaultScope.name === scope.name + (defaultScope) => defaultScope.name === scope.name, ) ? ClientScope.default : optionalScopes.find( - (optionalScope) => optionalScope.name === scope.name + (optionalScope) => optionalScope.name === scope.name, ) ? ClientScope.optional : AllClientScopes.none, @@ -144,7 +144,7 @@ export default function ClientScopesSection() { return localeSort(transformed, mapByKey("name")).slice( first, - Number(first) + Number(max) + Number(first) + Number(max), ); }; @@ -164,7 +164,7 @@ export default function ClientScopesSection() { } catch (error: any) { console.warn( "could not remove scope", - error.response?.data?.errorMessage || error + error.response?.data?.errorMessage || error, ); } await adminClient.clientScopes.del({ id: scope.id! }); diff --git a/js/apps/admin-ui/src/client-scopes/CreateClientScope.tsx b/js/apps/admin-ui/src/client-scopes/CreateClientScope.tsx index f9103a3afa46..eddb4db232de 100644 --- a/js/apps/admin-ui/src/client-scopes/CreateClientScope.tsx +++ b/js/apps/admin-ui/src/client-scopes/CreateClientScope.tsx @@ -46,7 +46,7 @@ export default function CreateClientScope() { realm, id: scope.id!, tab: "settings", - }) + }), ); } catch (error) { addError("client-scopes:createError", error); diff --git a/js/apps/admin-ui/src/client-scopes/EditClientScope.tsx b/js/apps/admin-ui/src/client-scopes/EditClientScope.tsx index aef1a9fff739..7d948ab601a0 100644 --- a/js/apps/admin-ui/src/client-scopes/EditClientScope.tsx +++ b/js/apps/admin-ui/src/client-scopes/EditClientScope.tsx @@ -74,14 +74,14 @@ export default function EditClientScope() { (clientScope) => { setClientScope(clientScope); }, - [key, id] + [key, id], ); async function determineScopeType(clientScope: ClientScopeRepresentation) { const defaultScopes = await adminClient.clientScopes.listDefaultClientScopes(); const hasDefaultScope = defaultScopes.find( - (defaultScope) => defaultScope.name === clientScope.name + (defaultScope) => defaultScope.name === clientScope.name, ); if (hasDefaultScope) { @@ -91,7 +91,7 @@ export default function EditClientScope() { const optionalScopes = await adminClient.clientScopes.listDefaultOptionalClientScopes(); const hasOptionalScope = optionalScopes.find( - (optionalScope) => optionalScope.name === clientScope.name + (optionalScope) => optionalScope.name === clientScope.name, ); return hasOptionalScope ? ClientScope.optional : AllClientScopes.none; @@ -103,7 +103,7 @@ export default function EditClientScope() { realm, id, tab, - }) + }), ); const settingsTab = useTab("settings"); @@ -155,7 +155,7 @@ export default function EditClientScope() { { id, }, - realmRoles + realmRoles, ); await Promise.all( rows @@ -166,9 +166,9 @@ export default function EditClientScope() { id, client: row.client!.id!, }, - [row.role as RoleMappingPayload] - ) - ) + [row.role as RoleMappingPayload], + ), + ), ); addAlert(t("roleMappingUpdatedSuccess"), AlertVariant.success); } catch (error) { @@ -177,7 +177,7 @@ export default function EditClientScope() { }; const addMappers = async ( - mappers: ProtocolMapperTypeRepresentation | ProtocolMapperRepresentation[] + mappers: ProtocolMapperTypeRepresentation | ProtocolMapperRepresentation[], ): Promise => { if (!Array.isArray(mappers)) { const mapper = mappers as ProtocolMapperTypeRepresentation; @@ -186,13 +186,13 @@ export default function EditClientScope() { realm, id: clientScope!.id!, mapperId: mapper.id!, - }) + }), ); } else { try { await adminClient.clientScopes.addMultipleProtocolMappers( { id: clientScope!.id! }, - mappers as ProtocolMapperRepresentation[] + mappers as ProtocolMapperRepresentation[], ); refresh(); addAlert(t("common:mappingCreatedSuccess"), AlertVariant.success); diff --git a/js/apps/admin-ui/src/client-scopes/add/MapperDialog.tsx b/js/apps/admin-ui/src/client-scopes/add/MapperDialog.tsx index d53c97dbffd9..4810b2afa79d 100644 --- a/js/apps/admin-ui/src/client-scopes/add/MapperDialog.tsx +++ b/js/apps/admin-ui/src/client-scopes/add/MapperDialog.tsx @@ -33,7 +33,7 @@ export type AddMapperDialogModalProps = { protocol: string; filter?: ProtocolMapperRepresentation[]; onConfirm: ( - value: ProtocolMapperTypeRepresentation | ProtocolMapperRepresentation[] + value: ProtocolMapperTypeRepresentation | ProtocolMapperRepresentation[], ) => void; }; @@ -57,7 +57,7 @@ export const AddMapperDialog = (props: AddMapperDialogProps) => { () => localeSort(builtInMappers, mapByKey("name")).map((mapper) => { const mapperType = protocolMappers.filter( - (type) => type.id === mapper.protocolMapper + (type) => type.id === mapper.protocolMapper, )[0]; return { item: mapper, @@ -65,7 +65,7 @@ export const AddMapperDialog = (props: AddMapperDialogProps) => { description: mapperType.helpText, }; }), - [builtInMappers, protocolMappers] + [builtInMappers, protocolMappers], ); const [rows, setRows] = useState(allRows); @@ -77,7 +77,7 @@ export const AddMapperDialog = (props: AddMapperDialogProps) => { const sortedProtocolMappers = useMemo( () => localeSort(protocolMappers, mapByKey("name")), - [protocolMappers] + [protocolMappers], ); const isBuiltIn = !!props.filter; diff --git a/js/apps/admin-ui/src/client-scopes/details/MapperList.tsx b/js/apps/admin-ui/src/client-scopes/details/MapperList.tsx index 6635b6a32866..f7ac948717ea 100644 --- a/js/apps/admin-ui/src/client-scopes/details/MapperList.tsx +++ b/js/apps/admin-ui/src/client-scopes/details/MapperList.tsx @@ -21,7 +21,7 @@ import { type MapperListProps = { model: ClientScopeRepresentation | ClientRepresentation; onAdd: ( - mappers: ProtocolMapperTypeRepresentation | ProtocolMapperRepresentation[] + mappers: ProtocolMapperTypeRepresentation | ProtocolMapperRepresentation[], ) => void; onDelete: (mapper: ProtocolMapperRepresentation) => void; detailLink: (id: string) => Partial; @@ -74,7 +74,7 @@ export const MapperList = ({ const list = mapperList.reduce((rows, mapper) => { const mapperType = mapperTypes.find( - ({ id }) => id === mapper.protocolMapper + ({ id }) => id === mapper.protocolMapper, ); if (!mapperType) { diff --git a/js/apps/admin-ui/src/client-scopes/details/MappingDetails.tsx b/js/apps/admin-ui/src/client-scopes/details/MappingDetails.tsx index 24d9bddd718b..dfe3c01ba098 100644 --- a/js/apps/admin-ui/src/client-scopes/details/MappingDetails.tsx +++ b/js/apps/admin-ui/src/client-scopes/details/MappingDetails.tsx @@ -83,7 +83,7 @@ export default function MappingDetails() { const mapperTypes = serverInfo.protocolMapperTypes![data!.protocol!]; const mapping = mapperTypes.find( - (type) => type.id === data!.protocolMapper + (type) => type.id === data!.protocolMapper, ); return { @@ -104,7 +104,7 @@ export default function MappingDetails() { const protocolMappers = serverInfo.protocolMapperTypes![model.protocol!]; const mapping = protocolMappers.find( - (mapper) => mapper.id === mapperId + (mapper) => mapper.id === mapperId, ); if (!mapping) { throw new Error(t("common:notFound")); @@ -125,7 +125,7 @@ export default function MappingDetails() { convertToFormValues(data, setValue); } }, - [] + [], ); const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ @@ -162,11 +162,11 @@ export default function MappingDetails() { isOnClientScope ? await adminClient.clientScopes.updateProtocolMapper( { id, mapperId }, - { id: mapperId, ...mapping } + { id: mapperId, ...mapping }, ) : await adminClient.clients.updateProtocolMapper( { id, mapperId }, - { id: mapperId, ...mapping } + { id: mapperId, ...mapping }, ); } else { isOnClientScope diff --git a/js/apps/admin-ui/src/client-scopes/details/ScopeForm.tsx b/js/apps/admin-ui/src/client-scopes/details/ScopeForm.tsx index 47215661dde9..1e3757afc6f7 100644 --- a/js/apps/admin-ui/src/client-scopes/details/ScopeForm.tsx +++ b/js/apps/admin-ui/src/client-scopes/details/ScopeForm.tsx @@ -65,7 +65,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => { const dynamicScope = useWatch({ control, name: convertAttributeNameToForm( - "attributes.is.dynamic.scope" + "attributes.is.dynamic.scope", ), defaultValue: "false", }); @@ -73,9 +73,9 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => { const setDynamicRegex = (value: string, append: boolean) => setValue( convertAttributeNameToForm( - "attributes.dynamic.scope.regexp" + "attributes.dynamic.scope.regexp", ), - append ? `${value}:*` : value + append ? `${value}:*` : value, ); useEffect(() => { @@ -123,7 +123,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => { ( - "attributes.is.dynamic.scope" + "attributes.is.dynamic.scope", )} label={t("dynamicScope")} labelIcon={t("client-scopes-help:dynamicScope")} @@ -135,7 +135,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => { {dynamicScope === "true" && ( ( - "attributes.dynamic.scope.regexp" + "attributes.dynamic.scope.regexp", )} label={t("dynamicScopeFormat")} labelIcon={t("client-scopes-help:dynamicScopeFormat")} @@ -256,7 +256,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => { > ( - "attributes.display.on.consent.screen" + "attributes.display.on.consent.screen", )} control={control} defaultValue={displayOnConsentScreen} @@ -286,8 +286,8 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => { id="kc-consent-screen-text" {...register( convertAttributeNameToForm( - "attributes.consent.screen.text" - ) + "attributes.consent.screen.text", + ), )} /> @@ -305,7 +305,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => { > ( - "attributes.include.in.token.scope" + "attributes.include.in.token.scope", )} control={control} defaultValue="true" @@ -332,7 +332,7 @@ export const ScopeForm = ({ clientScope, save }: ScopeFormProps) => { > ( - "attributes.gui.order" + "attributes.gui.order", )} defaultValue="" control={control} diff --git a/js/apps/admin-ui/src/client-scopes/routes/NewClientScope.tsx b/js/apps/admin-ui/src/client-scopes/routes/NewClientScope.tsx index f7104a619a67..5132f0236dbc 100644 --- a/js/apps/admin-ui/src/client-scopes/routes/NewClientScope.tsx +++ b/js/apps/admin-ui/src/client-scopes/routes/NewClientScope.tsx @@ -17,7 +17,7 @@ export const NewClientScopeRoute: AppRouteObject = { }; export const toNewClientScope = ( - params: NewClientScopeParams + params: NewClientScopeParams, ): Partial => ({ pathname: generatePath(NewClientScopeRoute.path, params), }); diff --git a/js/apps/admin-ui/src/clients/AdvancedTab.tsx b/js/apps/admin-ui/src/clients/AdvancedTab.tsx index 4d442aaf0010..3fc04f51091c 100644 --- a/js/apps/admin-ui/src/clients/AdvancedTab.tsx +++ b/js/apps/admin-ui/src/clients/AdvancedTab.tsx @@ -22,7 +22,7 @@ export const parseResult = ( result: GlobalRequestResult, prefixKey: string, addAlert: AddAlertFunction, - t: TFunction + t: TFunction, ) => { const successCount = result.successRequests?.length || 0; const failedCount = result.failedRequests?.length || 0; @@ -32,16 +32,16 @@ export const parseResult = ( } else if (failedCount > 0) { addAlert( t(prefixKey + "Success", { successNodes: result.successRequests }), - AlertVariant.success + AlertVariant.success, ); addAlert( t(prefixKey + "Fail", { failedNodes: result.failedRequests }), - AlertVariant.danger + AlertVariant.danger, ); } else { addAlert( t(prefixKey + "Success", { successNodes: result.successRequests }), - AlertVariant.success + AlertVariant.success, ); } }; @@ -67,7 +67,7 @@ export const AdvancedTab = ({ save, client }: AdvancedProps) => { for (const name of names) { setValue( convertAttributeNameToForm(`attributes.${name}`), - attributes?.[name] || "" + attributes?.[name] || "", ); } }; @@ -179,7 +179,7 @@ export const AdvancedTab = ({ save, client }: AdvancedProps) => { {t( "clients-help:advancedSettings" + - toUpperCase(protocol || "") + toUpperCase(protocol || ""), )} { reset={() => { setValue( "authenticationFlowBindingOverrides.browser", - authenticationFlowBindingOverrides?.browser + authenticationFlowBindingOverrides?.browser, ); setValue( "authenticationFlowBindingOverrides.direct_grant", - authenticationFlowBindingOverrides?.direct_grant + authenticationFlowBindingOverrides?.direct_grant, ); }} /> diff --git a/js/apps/admin-ui/src/clients/ClientDetails.tsx b/js/apps/admin-ui/src/clients/ClientDetails.tsx index f4a72f814c04..a3d3bd4e1e84 100644 --- a/js/apps/admin-ui/src/clients/ClientDetails.tsx +++ b/js/apps/admin-ui/src/clients/ClientDetails.tsx @@ -106,7 +106,7 @@ const ClientDetailHeader = ({ const badges = useMemo(() => { const protocolName = getProtocolName( t, - client.protocol ?? "openid-connect" + client.protocol ?? "openid-connect", ); const text = client.bearerOnly ? ( @@ -229,7 +229,7 @@ export default function ClientDetails() { realm, clientId, tab, - }) + }), ); const settingsTab = useTab("settings"); @@ -249,7 +249,7 @@ export default function ClientDetails() { realm, clientId, tab, - }) + }), ); const clientScopesSetupTab = useClientScopesTab("setup"); @@ -261,7 +261,7 @@ export default function ClientDetails() { realm, clientId, tab, - }) + }), ); const authorizationSettingsTab = useAuthorizationTab("settings"); @@ -296,8 +296,8 @@ export default function ClientDetails() { convertAttributeNameToForm("attributes.acr.loa.map"), // @ts-ignore Object.entries(JSON.parse(client.attributes["acr.loa.map"])).flatMap( - ([key, value]) => ({ key, value }) - ) + ([key, value]) => ({ key, value }), + ), ); } }; @@ -311,14 +311,14 @@ export default function ClientDetails() { setClient(cloneDeep(fetchedClient)); setupForm(fetchedClient); }, - [clientId, key] + [clientId, key], ); const save = async ( { confirmed = false, messageKey = "clientSaveSuccess" }: SaveOptions = { confirmed: false, messageKey: "clientSaveSuccess", - } + }, ) => { if (!(await form.trigger())) { return; @@ -343,8 +343,8 @@ export default function ClientDetails() { Object.fromEntries( (submittedClient.attributes["acr.loa.map"] as KeyValueType[]) .filter(({ key }) => key !== "") - .map(({ key, value }) => [key, value]) - ) + .map(({ key, value }) => [key, value]), + ), ); } diff --git a/js/apps/admin-ui/src/clients/ClientSessions.tsx b/js/apps/admin-ui/src/clients/ClientSessions.tsx index df69d2894f5e..5e6b58e31abb 100644 --- a/js/apps/admin-ui/src/clients/ClientSessions.tsx +++ b/js/apps/admin-ui/src/clients/ClientSessions.tsx @@ -15,7 +15,7 @@ export const ClientSessions = ({ client }: ClientSessionsProps) => { const loader: LoaderFunction = async ( first, - max + max, ) => { const mapSessionsToType = (type: string) => (sessions: UserSessionRepresentation[]) => diff --git a/js/apps/admin-ui/src/clients/add/CapabilityConfig.tsx b/js/apps/admin-ui/src/clients/add/CapabilityConfig.tsx index 31daaee42b58..9068c87212e9 100644 --- a/js/apps/admin-ui/src/clients/add/CapabilityConfig.tsx +++ b/js/apps/admin-ui/src/clients/add/CapabilityConfig.tsx @@ -69,9 +69,9 @@ export const CapabilityConfig = ({ setValue("serviceAccountsEnabled", false); setValue( convertAttributeNameToForm( - "attributes.oidc.ciba.grant.enabled" + "attributes.oidc.ciba.grant.enabled", ), - false + false, ); } }} @@ -234,7 +234,7 @@ export const CapabilityConfig = ({ /> @@ -245,7 +245,7 @@ export const CapabilityConfig = ({ ( - "attributes.oidc.ciba.grant.enabled" + "attributes.oidc.ciba.grant.enabled", )} defaultValue={false} control={control} @@ -287,7 +287,7 @@ export const CapabilityConfig = ({ > ( - "attributes.saml.encrypt" + "attributes.saml.encrypt", )} control={control} defaultValue={false} @@ -317,7 +317,7 @@ export const CapabilityConfig = ({ > ( - "attributes.saml.client.signature" + "attributes.saml.client.signature", )} control={control} defaultValue={false} diff --git a/js/apps/admin-ui/src/clients/add/LoginSettings.tsx b/js/apps/admin-ui/src/clients/add/LoginSettings.tsx index 00de1b21baf0..c8a35f4c0409 100644 --- a/js/apps/admin-ui/src/clients/add/LoginSettings.tsx +++ b/js/apps/admin-ui/src/clients/add/LoginSettings.tsx @@ -24,7 +24,7 @@ export const LoginSettings = ({ const { realm } = useRealm(); const idpInitiatedSsoUrlName: string = watch( - "attributes.saml_idp_initiated_sso_url_name" + "attributes.saml_idp_initiated_sso_url_name", ); const standardFlowEnabled = watch("standardFlowEnabled"); @@ -97,7 +97,7 @@ export const LoginSettings = ({ { const consentRequired = watch("consentRequired"); const displayOnConsentScreen: string = watch( convertAttributeNameToForm( - "attributes.display.on.consent.screen" - ) + "attributes.display.on.consent.screen", + ), ); return ( @@ -114,7 +114,7 @@ export const LoginSettingsPanel = ({ access }: { access?: boolean }) => { > ( - "attributes.display.on.consent.screen" + "attributes.display.on.consent.screen", )} defaultValue={false} control={control} @@ -145,8 +145,8 @@ export const LoginSettingsPanel = ({ access }: { access?: boolean }) => { id="kc-consent-screen-text" {...register( convertAttributeNameToForm( - "attributes.consent.screen.text" - ) + "attributes.consent.screen.text", + ), )} isDisabled={!(consentRequired && displayOnConsentScreen === "true")} /> diff --git a/js/apps/admin-ui/src/clients/add/LogoutPanel.tsx b/js/apps/admin-ui/src/clients/add/LogoutPanel.tsx index cecfea338fe9..31452ed64537 100644 --- a/js/apps/admin-ui/src/clients/add/LogoutPanel.tsx +++ b/js/apps/admin-ui/src/clients/add/LogoutPanel.tsx @@ -94,12 +94,12 @@ export const LogoutPanel = ({ type="url" {...register( convertAttributeNameToForm( - "attributes.frontchannel.logout.url" + "attributes.frontchannel.logout.url", ), { validate: (uri) => validateUrl(uri, t("frontchannelUrlInvalid").toString()), - } + }, )} validated={ errors.attributes?.[beerify("frontchannel.logout.url")]?.message @@ -135,12 +135,12 @@ export const LogoutPanel = ({ type="url" {...register( convertAttributeNameToForm( - "attributes.backchannel.logout.url" + "attributes.backchannel.logout.url", ), { validate: (uri) => validateUrl(uri, t("backchannelUrlInvalid").toString()), - } + }, )} validated={ errors.attributes?.[beerify("backchannel.logout.url")]?.message @@ -162,7 +162,7 @@ export const LogoutPanel = ({ > ( - "attributes.backchannel.logout.session.required" + "attributes.backchannel.logout.session.required", )} defaultValue="true" control={control} @@ -183,7 +183,7 @@ export const LogoutPanel = ({ labelIcon={ @@ -193,7 +193,7 @@ export const LogoutPanel = ({ > ( - "attributes.backchannel.logout.revoke.offline.tokens" + "attributes.backchannel.logout.revoke.offline.tokens", )} defaultValue="false" control={control} diff --git a/js/apps/admin-ui/src/clients/add/SamlConfig.tsx b/js/apps/admin-ui/src/clients/add/SamlConfig.tsx index 9aec799c7d85..24a4e4d6d2b3 100644 --- a/js/apps/admin-ui/src/clients/add/SamlConfig.tsx +++ b/js/apps/admin-ui/src/clients/add/SamlConfig.tsx @@ -121,13 +121,13 @@ export const SamlConfig = () => { /> diff --git a/js/apps/admin-ui/src/clients/add/SamlSignature.tsx b/js/apps/admin-ui/src/clients/add/SamlSignature.tsx index 65e99a5ffc26..ec9558b59269 100644 --- a/js/apps/admin-ui/src/clients/add/SamlSignature.tsx +++ b/js/apps/admin-ui/src/clients/add/SamlSignature.tsx @@ -50,12 +50,12 @@ export const SamlSignature = () => { const { control, watch } = useFormContext(); const signDocs = watch( - convertAttributeNameToForm("attributes.saml.server.signature") + convertAttributeNameToForm("attributes.saml.server.signature"), ); const signAssertion = watch( convertAttributeNameToForm( - "attributes.saml.assertion.signature" - ) + "attributes.saml.assertion.signature", + ), ); return ( @@ -86,7 +86,7 @@ export const SamlSignature = () => { > ( - "attributes.saml.signature.algorithm" + "attributes.saml.signature.algorithm", )} defaultValue={SIGNATURE_ALGORITHMS[0]} control={control} @@ -126,7 +126,7 @@ export const SamlSignature = () => { > ( - "attributes.saml.server.signature.keyinfo.xmlSigKeyInfoKeyNameTransformer" + "attributes.saml.server.signature.keyinfo.xmlSigKeyInfoKeyNameTransformer", )} defaultValue={KEYNAME_TRANSFORMER[0]} control={control} diff --git a/js/apps/admin-ui/src/clients/advanced/AdvancedSettings.tsx b/js/apps/admin-ui/src/clients/advanced/AdvancedSettings.tsx index 34cf3e0a410d..c657a55709a1 100644 --- a/js/apps/admin-ui/src/clients/advanced/AdvancedSettings.tsx +++ b/js/apps/admin-ui/src/clients/advanced/AdvancedSettings.tsx @@ -47,7 +47,7 @@ export const AdvancedSettings = ({ useFetch( () => adminClient.realms.findOne({ realm: realmName }), setRealm, - [] + [], ); const { control } = useFormContext(); @@ -70,7 +70,7 @@ export const AdvancedSettings = ({ > ( - "attributes.saml.assertion.lifespan" + "attributes.saml.assertion.lifespan", )} defaultValue="" control={control} @@ -89,7 +89,7 @@ export const AdvancedSettings = ({ ( - "attributes.tls.client.certificate.bound.access.tokens" + "attributes.tls.client.certificate.bound.access.tokens", )} defaultValue={false} control={control} @@ -177,7 +177,7 @@ export const AdvancedSettings = ({ > ( - "attributes.pkce.code.challenge.method" + "attributes.pkce.code.challenge.method", )} defaultValue="" control={control} @@ -214,7 +214,7 @@ export const AdvancedSettings = ({ > ( - "attributes.require.pushed.authorization.requests" + "attributes.require.pushed.authorization.requests", )} defaultValue="false" control={control} diff --git a/js/apps/admin-ui/src/clients/advanced/AuthenticationOverrides.tsx b/js/apps/admin-ui/src/clients/advanced/AuthenticationOverrides.tsx index 361f8ba18116..d2ae445f6507 100644 --- a/js/apps/admin-ui/src/clients/advanced/AuthenticationOverrides.tsx +++ b/js/apps/admin-ui/src/clients/advanced/AuthenticationOverrides.tsx @@ -55,7 +55,7 @@ export const AuthenticationOverrides = ({ )), ]); }, - [] + [], ); return ( diff --git a/js/apps/admin-ui/src/clients/advanced/ClusteringPanel.tsx b/js/apps/admin-ui/src/clients/advanced/ClusteringPanel.tsx index 276509d843eb..033b15ab58d5 100644 --- a/js/apps/admin-ui/src/clients/advanced/ClusteringPanel.tsx +++ b/js/apps/admin-ui/src/clients/advanced/ClusteringPanel.tsx @@ -132,7 +132,7 @@ export const ClusteringPanel = ({ Promise.resolve( Object.entries(nodes || {}).map((entry) => { return { host: entry[0], registration: entry[1] }; - }) + }), ) } toolbarItem={ @@ -180,7 +180,7 @@ export const ClusteringPanel = ({ value ? formatDate( new Date(parseInt(value.toString()) * 1000), - FORMAT_DATE_AND_TIME + FORMAT_DATE_AND_TIME, ) : "", ], diff --git a/js/apps/admin-ui/src/clients/advanced/FineGrainOpenIdConnect.tsx b/js/apps/admin-ui/src/clients/advanced/FineGrainOpenIdConnect.tsx index d0d0c0cd17fb..355694974a3b 100644 --- a/js/apps/admin-ui/src/clients/advanced/FineGrainOpenIdConnect.tsx +++ b/js/apps/admin-ui/src/clients/advanced/FineGrainOpenIdConnect.tsx @@ -172,7 +172,7 @@ export const FineGrainOpenIdConnect = ({ > ( - "attributes.access.token.signed.response.alg" + "attributes.access.token.signed.response.alg", )} defaultValue="" control={control} @@ -205,7 +205,7 @@ export const FineGrainOpenIdConnect = ({ > ( - "attributes.id.token.signed.response.alg" + "attributes.id.token.signed.response.alg", )} defaultValue="" control={control} @@ -238,7 +238,7 @@ export const FineGrainOpenIdConnect = ({ > ( - "attributes.id.token.encrypted.response.alg" + "attributes.id.token.encrypted.response.alg", )} defaultValue="" control={control} @@ -265,7 +265,7 @@ export const FineGrainOpenIdConnect = ({ labelIcon={ @@ -273,7 +273,7 @@ export const FineGrainOpenIdConnect = ({ > ( - "attributes.id.token.encrypted.response.enc" + "attributes.id.token.encrypted.response.enc", )} defaultValue="" control={control} @@ -306,7 +306,7 @@ export const FineGrainOpenIdConnect = ({ > ( - "attributes.user.info.response.signature.alg" + "attributes.user.info.response.signature.alg", )} defaultValue="" control={control} @@ -333,7 +333,7 @@ export const FineGrainOpenIdConnect = ({ labelIcon={ @@ -341,7 +341,7 @@ export const FineGrainOpenIdConnect = ({ > ( - "attributes.user.info.encrypted.response.alg" + "attributes.user.info.encrypted.response.alg", )} defaultValue="" control={control} @@ -368,7 +368,7 @@ export const FineGrainOpenIdConnect = ({ labelIcon={ @@ -376,7 +376,7 @@ export const FineGrainOpenIdConnect = ({ > ( - "attributes.user.info.encrypted.response.enc" + "attributes.user.info.encrypted.response.enc", )} defaultValue="" control={control} @@ -409,7 +409,7 @@ export const FineGrainOpenIdConnect = ({ > ( - "attributes.request.object.signature.alg" + "attributes.request.object.signature.alg", )} defaultValue="" control={control} @@ -442,7 +442,7 @@ export const FineGrainOpenIdConnect = ({ > ( - "attributes.request.object.encryption.alg" + "attributes.request.object.encryption.alg", )} defaultValue="" control={control} @@ -475,7 +475,7 @@ export const FineGrainOpenIdConnect = ({ > ( - "attributes.request.object.encryption.enc" + "attributes.request.object.encryption.enc", )} defaultValue="" control={control} @@ -508,7 +508,7 @@ export const FineGrainOpenIdConnect = ({ > ( - "attributes.request.object.required" + "attributes.request.object.required", )} defaultValue="" control={control} @@ -558,7 +558,7 @@ export const FineGrainOpenIdConnect = ({ > ( - "attributes.authorization.signed.response.alg" + "attributes.authorization.signed.response.alg", )} defaultValue="" control={control} @@ -591,7 +591,7 @@ export const FineGrainOpenIdConnect = ({ > ( - "attributes.authorization.encrypted.response.alg" + "attributes.authorization.encrypted.response.alg", )} defaultValue="" control={control} @@ -624,7 +624,7 @@ export const FineGrainOpenIdConnect = ({ > ( - "attributes.authorization.encrypted.response.enc" + "attributes.authorization.encrypted.response.enc", )} defaultValue="" control={control} diff --git a/js/apps/admin-ui/src/clients/advanced/OpenIdConnectCompatibilityModes.tsx b/js/apps/admin-ui/src/clients/advanced/OpenIdConnectCompatibilityModes.tsx index 3f99e6c3c578..4ecc97d715d6 100644 --- a/js/apps/admin-ui/src/clients/advanced/OpenIdConnectCompatibilityModes.tsx +++ b/js/apps/admin-ui/src/clients/advanced/OpenIdConnectCompatibilityModes.tsx @@ -33,7 +33,7 @@ export const OpenIdConnectCompatibilityModes = ({ labelIcon={ @@ -41,7 +41,7 @@ export const OpenIdConnectCompatibilityModes = ({ > ( - "attributes.exclude.session.state.from.auth.response" + "attributes.exclude.session.state.from.auth.response", )} defaultValue="" control={control} @@ -70,7 +70,7 @@ export const OpenIdConnectCompatibilityModes = ({ > ( - "attributes.use.refresh.tokens" + "attributes.use.refresh.tokens", )} defaultValue="true" control={control} @@ -93,7 +93,7 @@ export const OpenIdConnectCompatibilityModes = ({ labelIcon={ @@ -101,7 +101,7 @@ export const OpenIdConnectCompatibilityModes = ({ > ( - "attributes.client_credentials.use_refresh_token" + "attributes.client_credentials.use_refresh_token", )} defaultValue="false" control={control} @@ -130,7 +130,7 @@ export const OpenIdConnectCompatibilityModes = ({ > ( - "attributes.token.response.type.bearer.lower-case" + "attributes.token.response.type.bearer.lower-case", )} defaultValue="false" control={control} diff --git a/js/apps/admin-ui/src/clients/authorization/AuthorizationEvaluate.tsx b/js/apps/admin-ui/src/clients/authorization/AuthorizationEvaluate.tsx index 648773aeeb2f..fabf23d55460 100644 --- a/js/apps/admin-ui/src/clients/authorization/AuthorizationEvaluate.tsx +++ b/js/apps/admin-ui/src/clients/authorization/AuthorizationEvaluate.tsx @@ -122,7 +122,7 @@ const AuthorizationEvaluateContent = ({ client }: Props) => { (roles) => { setClientRoles(roles); }, - [] + [], ); useFetch( @@ -139,7 +139,7 @@ const AuthorizationEvaluateContent = ({ client }: Props) => { setResources(resources); setScopes(scopes); }, - [] + [], ); const evaluate = async () => { @@ -159,7 +159,7 @@ const AuthorizationEvaluateContent = ({ client }: Props) => { scopes: r.scopes?.filter((s) => Object.values(keys) .flatMap((v) => v) - .includes(s.name!) + .includes(s.name!), ), })), entitlements: false, @@ -167,7 +167,7 @@ const AuthorizationEvaluateContent = ({ client }: Props) => { attributes: Object.fromEntries( formValues.context.attributes .filter((item) => item.key || item.value !== "") - .map(({ key, value }) => [key, value]) + .map(({ key, value }) => [key, value]), ), }, }; @@ -175,7 +175,7 @@ const AuthorizationEvaluateContent = ({ client }: Props) => { try { const evaluation = await adminClient.clients.evaluateResource( { id: client.id!, realm: realm.realm }, - resEval + resEval, ); setEvaluateResult(evaluation); @@ -254,8 +254,8 @@ const AuthorizationEvaluateContent = ({ client }: Props) => { if (field.value?.includes(option)) { field.onChange( field.value.filter( - (item: string) => item !== option - ) + (item: string) => item !== option, + ), ); } else { field.onChange([...(field.value || []), option]); @@ -375,8 +375,8 @@ const AuthorizationEvaluateContent = ({ client }: Props) => { if (field.value.includes(option)) { field.onChange( field.value.filter( - (item: string) => item !== option - ) + (item: string) => item !== option, + ), ); } else { field.onChange([...field.value, option]); diff --git a/js/apps/admin-ui/src/clients/authorization/AuthorizationEvaluateResource.tsx b/js/apps/admin-ui/src/clients/authorization/AuthorizationEvaluateResource.tsx index 603d90e70561..05536a94677c 100644 --- a/js/apps/admin-ui/src/clients/authorization/AuthorizationEvaluateResource.tsx +++ b/js/apps/admin-ui/src/clients/authorization/AuthorizationEvaluateResource.tsx @@ -80,7 +80,7 @@ export const AuthorizationEvaluateResource = ({ outerPolicy={outerPolicy as PolicyResultRepresentation} resource={resource} /> - ) + ), )} diff --git a/js/apps/admin-ui/src/clients/authorization/AuthorizationExport.tsx b/js/apps/admin-ui/src/clients/authorization/AuthorizationExport.tsx index a4a1db49d4a0..76b4d2194a66 100644 --- a/js/apps/admin-ui/src/clients/authorization/AuthorizationExport.tsx +++ b/js/apps/admin-ui/src/clients/authorization/AuthorizationExport.tsx @@ -40,7 +40,7 @@ export const AuthorizationExport = () => { setCode(JSON.stringify(authDetails, null, 2)); setAuthorizationDetails(authDetails); }, - [] + [], ); const exportAuthDetails = () => { @@ -49,7 +49,7 @@ export const AuthorizationExport = () => { new Blob([prettyPrintJSON(authorizationDetails)], { type: "application/json", }), - "test-authz-config.json" + "test-authz-config.json", ); addAlert(t("exportAuthDetailsSuccess"), AlertVariant.success); } catch (error) { diff --git a/js/apps/admin-ui/src/clients/authorization/DetailCell.tsx b/js/apps/admin-ui/src/clients/authorization/DetailCell.tsx index 2664a8c40068..db6c7b9b7b52 100644 --- a/js/apps/admin-ui/src/clients/authorization/DetailCell.tsx +++ b/js/apps/admin-ui/src/clients/authorization/DetailCell.tsx @@ -42,7 +42,7 @@ export const DetailCell = ({ id, clientId, uris }: DetailCellProps) => { setScope(scopes); setPermissions(permissions); }, - [] + [], ); if (!permissions || !scope) { diff --git a/js/apps/admin-ui/src/clients/authorization/ImportDialog.tsx b/js/apps/admin-ui/src/clients/authorization/ImportDialog.tsx index f586f57dc843..4c74448bec6a 100644 --- a/js/apps/admin-ui/src/clients/authorization/ImportDialog.tsx +++ b/js/apps/admin-ui/src/clients/authorization/ImportDialog.tsx @@ -78,7 +78,7 @@ export const ImportDialog = ({ onConfirm, closeDialog }: ImportDialogProps) => { id="policyEnforcementMode" name="policyEnforcementMode" label={t( - `policyEnforcementModes.${imported.policyEnforcementMode}` + `policyEnforcementModes.${imported.policyEnforcementMode}`, )} isChecked isDisabled diff --git a/js/apps/admin-ui/src/clients/authorization/KeyBasedAttributeInput.tsx b/js/apps/admin-ui/src/clients/authorization/KeyBasedAttributeInput.tsx index 48e881ffb4d4..1d3da3f1a821 100644 --- a/js/apps/admin-ui/src/clients/authorization/KeyBasedAttributeInput.tsx +++ b/js/apps/admin-ui/src/clients/authorization/KeyBasedAttributeInput.tsx @@ -69,7 +69,7 @@ const ValueInput = ({ if (selectableValues) { values = defaultContextAttributes.find( - (attr) => attr.key === getValues().context?.[rowIndex]?.key + (attr) => attr.key === getValues().context?.[rowIndex]?.key, )?.values; } @@ -78,7 +78,7 @@ const ValueInput = ({ const renderSelectOptionType = () => { const scopeValues = resources?.find( - (resource) => resource.name === getValues().resources?.[rowIndex]?.key + (resource) => resource.name === getValues().resources?.[rowIndex]?.key, )?.scopes; if (attributeValues?.length && !resources) { diff --git a/js/apps/admin-ui/src/clients/authorization/NewPolicyDialog.tsx b/js/apps/admin-ui/src/clients/authorization/NewPolicyDialog.tsx index f0b4dcc34ff9..7575b51457ff 100644 --- a/js/apps/admin-ui/src/clients/authorization/NewPolicyDialog.tsx +++ b/js/apps/admin-ui/src/clients/authorization/NewPolicyDialog.tsx @@ -37,7 +37,7 @@ export const NewPolicyDialog = ({ const sortedPolicies = useMemo( () => policyProviders ? localeSort(policyProviders, mapByKey("name")) : [], - [policyProviders] + [policyProviders], ); return ( diff --git a/js/apps/admin-ui/src/clients/authorization/PermissionDetails.tsx b/js/apps/admin-ui/src/clients/authorization/PermissionDetails.tsx index fd270a0fc1f0..41b0922fd6da 100644 --- a/js/apps/admin-ui/src/clients/authorization/PermissionDetails.tsx +++ b/js/apps/admin-ui/src/clients/authorization/PermissionDetails.tsx @@ -105,12 +105,12 @@ export default function PermissionDetails() { reset({ ...permission, resources, policies, scopes }); if (permission && "resourceType" in permission) { setApplyToResourceTypeFlag( - !!(permission as { resourceType: string }).resourceType + !!(permission as { resourceType: string }).resourceType, ); } setPermission({ ...permission, resources, policies }); }, - [] + [], ); const save = async (permission: PolicyRepresentation) => { @@ -118,12 +118,12 @@ export default function PermissionDetails() { if (permissionId) { await adminClient.clients.updatePermission( { id, type: permissionType, permissionId }, - permission + permission, ); } else { const result = await adminClient.clients.createPermission( { id, type: permissionType }, - permission + permission, ); navigate( toPermissionDetails({ @@ -131,12 +131,12 @@ export default function PermissionDetails() { id, permissionType, permissionId: result.id!, - }) + }), ); } addAlert( t((permissionId ? "update" : "create") + "PermissionSuccess"), - AlertVariant.success + AlertVariant.success, ); } catch (error) { addError("clients:permissionSaveError", error); @@ -159,7 +159,7 @@ export default function PermissionDetails() { }); addAlert(t("permissionDeletedSuccess"), AlertVariant.success); navigate( - toAuthorizationTab({ realm, clientId: id, tab: "permissions" }) + toAuthorizationTab({ realm, clientId: id, tab: "permissions" }), ); } catch (error) { addError("clients:permissionDeletedError", error); diff --git a/js/apps/admin-ui/src/clients/authorization/Permissions.tsx b/js/apps/admin-ui/src/clients/authorization/Permissions.tsx index 835f62f0e62a..0357e20492c7 100644 --- a/js/apps/admin-ui/src/clients/authorization/Permissions.tsx +++ b/js/apps/admin-ui/src/clients/authorization/Permissions.tsx @@ -113,11 +113,11 @@ export const AuthorizationPermissions = ({ clientId }: PermissionsProps) => { associatedPolicies, isExpanded: false, }; - }) + }), ); }, setPermissions, - [key, search, first, max] + [key, search, first, max], ); useFetch( @@ -135,7 +135,7 @@ export const AuthorizationPermissions = ({ clientId }: PermissionsProps) => { ]); return { policies: policies.filter( - (p) => p.type === "resource" || p.type === "scope" + (p) => p.type === "resource" || p.type === "scope", ), resources: resources.length !== 1, scopes: scopes.length !== 1, @@ -145,7 +145,7 @@ export const AuthorizationPermissions = ({ clientId }: PermissionsProps) => { setPolicyProviders(policies); setDisabledCreate({ resources, scopes }); }, - [] + [], ); const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ @@ -223,7 +223,7 @@ export const AuthorizationPermissions = ({ clientId }: PermissionsProps) => { realm, id: clientId, permissionType: "resource", - }) + }), ) } > @@ -241,7 +241,7 @@ export const AuthorizationPermissions = ({ clientId }: PermissionsProps) => { realm, id: clientId, permissionType: "scope", - }) + }), ) } > @@ -285,7 +285,7 @@ export const AuthorizationPermissions = ({ clientId }: PermissionsProps) => { const rows = permissions.map((p, index) => index === rowIndex ? { ...p, isExpanded: !p.isExpanded } - : p + : p, ); setPermissions(rows); }, diff --git a/js/apps/admin-ui/src/clients/authorization/Policies.tsx b/js/apps/admin-ui/src/clients/authorization/Policies.tsx index 71a649c48906..68d01c0f0f9d 100644 --- a/js/apps/admin-ui/src/clients/authorization/Policies.tsx +++ b/js/apps/admin-ui/src/clients/authorization/Policies.tsx @@ -110,11 +110,11 @@ export const AuthorizationPolicies = ({ clientId }: PoliciesProps) => { }, ([providers, ...policies]) => { setPolicyProviders( - providers.filter((p) => p.type !== "resource" && p.type !== "scope") + providers.filter((p) => p.type !== "resource" && p.type !== "scope"), ); setPolicies(policies); }, - [key, search, first, max] + [key, search, first, max], ); const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ @@ -174,7 +174,7 @@ export const AuthorizationPolicies = ({ clientId }: PoliciesProps) => { policyProviders={policyProviders} onSelect={(p) => navigate( - toCreatePolicy({ id: clientId, realm, policyType: p.type! }) + toCreatePolicy({ id: clientId, realm, policyType: p.type! }), ) } toggleDialog={toggleDialog} @@ -231,7 +231,7 @@ export const AuthorizationPolicies = ({ clientId }: PoliciesProps) => { const rows = policies.map((policy, index) => index === rowIndex ? { ...policy, isExpanded: !policy.isExpanded } - : policy + : policy, ); setPolicies(rows); }, @@ -317,11 +317,11 @@ export const AuthorizationPolicies = ({ clientId }: PoliciesProps) => { {newDialog && ( p.type !== "aggregate" + (p) => p.type !== "aggregate", )} onSelect={(p) => navigate( - toCreatePolicy({ id: clientId, realm, policyType: p.type! }) + toCreatePolicy({ id: clientId, realm, policyType: p.type! }), ) } toggleDialog={toggleDialog} diff --git a/js/apps/admin-ui/src/clients/authorization/ResourceDetails.tsx b/js/apps/admin-ui/src/clients/authorization/ResourceDetails.tsx index b7562ad01e9b..2d2a436f0f3c 100644 --- a/js/apps/admin-ui/src/clients/authorization/ResourceDetails.tsx +++ b/js/apps/admin-ui/src/clients/authorization/ResourceDetails.tsx @@ -92,7 +92,7 @@ export default function ResourceDetails() { setResource(resource); setupForm(resource); }, - [] + [], ); const submit = async (submitted: SubmittedResource) => { @@ -107,13 +107,13 @@ export default function ResourceDetails() { } else { const result = await adminClient.clients.createResource( { id }, - resource + resource, ); navigate(toResourceDetails({ realm, id, resourceId: result._id! })); } addAlert( t((resourceId ? "update" : "create") + "ResourceSuccess"), - AlertVariant.success + AlertVariant.success, ); } catch (error) { addError("clients:resourceSaveError", error); diff --git a/js/apps/admin-ui/src/clients/authorization/Resources.tsx b/js/apps/admin-ui/src/clients/authorization/Resources.tsx index 5036f53f16b8..8e66ef01b613 100644 --- a/js/apps/admin-ui/src/clients/authorization/Resources.tsx +++ b/js/apps/admin-ui/src/clients/authorization/Resources.tsx @@ -84,9 +84,9 @@ export const AuthorizationResources = ({ clientId }: ResourcesProps) => { }, (resources) => setResources( - resources.map((resource) => ({ ...resource, isExpanded: false })) + resources.map((resource) => ({ ...resource, isExpanded: false })), ), - [key, search, first, max] + [key, search, first, max], ); const fetchPermissions = async (id: string) => { @@ -209,7 +209,7 @@ export const AuthorizationResources = ({ clientId }: ResourcesProps) => { ...resource, isExpanded: !resource.isExpanded, } - : resource + : resource, ); setResources(rows); }, @@ -258,7 +258,7 @@ export const AuthorizationResources = ({ clientId }: ResourcesProps) => { onClick: async () => { setSelectedResource(resource); setPermission( - await fetchPermissions(resource._id!) + await fetchPermissions(resource._id!), ); toggleDeleteDialog(); }, diff --git a/js/apps/admin-ui/src/clients/authorization/ResourcesPolicySelect.tsx b/js/apps/admin-ui/src/clients/authorization/ResourcesPolicySelect.tsx index b586b36496ee..6bf16f3b2c2b 100644 --- a/js/apps/admin-ui/src/clients/authorization/ResourcesPolicySelect.tsx +++ b/js/apps/admin-ui/src/clients/authorization/ResourcesPolicySelect.tsx @@ -70,7 +70,7 @@ export const ResourcesPolicySelect = ({ const functions = typeMapping[name]; const convert = ( - p: PolicyRepresentation | ResourceRepresentation + p: PolicyRepresentation | ResourceRepresentation, ): Policies => ({ id: "_id" in p ? p._id : "id" in p ? p.id : undefined, name: p.name, @@ -80,7 +80,7 @@ export const ResourcesPolicySelect = ({ async () => { const params: PolicyQuery = Object.assign( { id: clientId, first: 0, max: 10, permission: "false" }, - search === "" ? null : { name: search } + search === "" ? null : { name: search }, ); return ( await Promise.all([ @@ -96,16 +96,16 @@ export const ResourcesPolicySelect = ({ .flat() .filter( (r): r is PolicyRepresentation | ResourceRepresentation => - typeof r !== "string" + typeof r !== "string", ) .map(convert) .filter( ({ id }, index, self) => - index === self.findIndex(({ id: otherId }) => id === otherId) + index === self.findIndex(({ id: otherId }) => id === otherId), ); }, setItems, - [search] + [search], ); const toSelectOptions = () => @@ -139,7 +139,7 @@ export const ResourcesPolicySelect = ({ const option = selectedValue.toString(); if (variant === SelectVariant.typeaheadMulti) { const changedValue = field.value?.find( - (p: string) => p === option + (p: string) => p === option, ) ? field.value.filter((p: string) => p !== option) : [...field.value!, option]; diff --git a/js/apps/admin-ui/src/clients/authorization/ScopeDetails.tsx b/js/apps/admin-ui/src/clients/authorization/ScopeDetails.tsx index c7b3a2073d78..b163c1db7aa9 100644 --- a/js/apps/admin-ui/src/clients/authorization/ScopeDetails.tsx +++ b/js/apps/admin-ui/src/clients/authorization/ScopeDetails.tsx @@ -64,7 +64,7 @@ export default function ScopeDetails() { setScope(scope); reset({ ...scope }); }, - [] + [], ); const onSubmit = async (scope: ScopeRepresentation) => { @@ -72,7 +72,7 @@ export default function ScopeDetails() { if (scopeId) { await adminClient.clients.updateAuthorizationScope( { id, scopeId }, - scope + scope, ); setScope(scope); } else { @@ -82,13 +82,13 @@ export default function ScopeDetails() { name: scope.name!, displayName: scope.displayName, iconUri: scope.iconUri, - } + }, ); navigate(toAuthorizationTab({ realm, clientId: id, tab: "scopes" })); } addAlert( t((scopeId ? "update" : "create") + "ScopeSuccess"), - AlertVariant.success + AlertVariant.success, ); } catch (error) { addError("clients:scopeSaveError", error); diff --git a/js/apps/admin-ui/src/clients/authorization/ScopePicker.tsx b/js/apps/admin-ui/src/clients/authorization/ScopePicker.tsx index 6dcc0f572ce1..8d6e6ef90fc7 100644 --- a/js/apps/admin-ui/src/clients/authorization/ScopePicker.tsx +++ b/js/apps/admin-ui/src/clients/authorization/ScopePicker.tsx @@ -38,7 +38,7 @@ export const ScopePicker = ({ clientId }: { clientId: string }) => { return adminClient.clients.listAllScopes(params); }, setScopes, - [search] + [search], ); const renderScopes = (scopes?: ScopeRepresentation[]) => @@ -85,7 +85,7 @@ export const ScopePicker = ({ clientId }: { clientId: string }) => { ? selectedValue : (selectedValue as Scope).name; const changedValue = field.value.find( - (o: Scope) => o.name === option + (o: Scope) => o.name === option, ) ? field.value.filter((item: Scope) => item.name !== option) : [...field.value, selectedValue]; diff --git a/js/apps/admin-ui/src/clients/authorization/ScopeSelect.tsx b/js/apps/admin-ui/src/clients/authorization/ScopeSelect.tsx index 2403504056c1..244ad0c5cafb 100644 --- a/js/apps/admin-ui/src/clients/authorization/ScopeSelect.tsx +++ b/js/apps/admin-ui/src/clients/authorization/ScopeSelect.tsx @@ -29,7 +29,7 @@ export const ScopeSelect = ({ const [scopes, setScopes] = useState([]); const [selectedScopes, setSelectedScopes] = useState( - [] + [], ); const [search, setSearch] = useState(""); const [open, setOpen] = useState(false); @@ -50,8 +50,8 @@ export const ScopeSelect = ({ return adminClient.clients.listAllScopes( Object.assign( { id: clientId, deep: false }, - search === "" ? null : { name: search } - ) + search === "" ? null : { name: search }, + ), ); } @@ -69,10 +69,10 @@ export const ScopeSelect = ({ setScopes(scopes); if (!search) setSelectedScopes( - scopes.filter((s: ScopeRepresentation) => values?.includes(s.id!)) + scopes.filter((s: ScopeRepresentation) => values?.includes(s.id!)), ); }, - [resourceId, search] + [resourceId, search], ); return ( diff --git a/js/apps/admin-ui/src/clients/authorization/Scopes.tsx b/js/apps/admin-ui/src/clients/authorization/Scopes.tsx index 45a3481783f6..eb34a3ab2136 100644 --- a/js/apps/admin-ui/src/clients/authorization/Scopes.tsx +++ b/js/apps/admin-ui/src/clients/authorization/Scopes.tsx @@ -83,7 +83,7 @@ export const AuthorizationScopes = ({ clientId }: ScopesProps) => { setScopes(scopes.map((s) => ({ ...s, isLoaded: false }))); setCollapsed(scopes.map((s) => ({ id: s.id!, isExpanded: false }))); }, - [key, search, first, max] + [key, search, first, max], ); const getScope = (id: string) => scopes?.find((scope) => scope.id === id)!; @@ -116,14 +116,14 @@ export const AuthorizationScopes = ({ clientId }: ScopesProps) => { permissions, isLoaded: true, }; - }) + }), ); }, (resourcesScopes) => { let result = [...(scopes || [])]; resourcesScopes.forEach((resourceScope) => { const index = scopes?.findIndex( - (scope) => resourceScope.id === scope.id + (scope) => resourceScope.id === scope.id, )!; result = [ ...result.slice(0, index), @@ -134,7 +134,7 @@ export const AuthorizationScopes = ({ clientId }: ScopesProps) => { setScopes(result); }, - [collapsed] + [collapsed], ); if (!scopes) { diff --git a/js/apps/admin-ui/src/clients/authorization/Settings.tsx b/js/apps/admin-ui/src/clients/authorization/Settings.tsx index 84b4442fc3ed..b26112c12edd 100644 --- a/js/apps/admin-ui/src/clients/authorization/Settings.tsx +++ b/js/apps/admin-ui/src/clients/authorization/Settings.tsx @@ -50,7 +50,7 @@ export const AuthorizationSettings = ({ clientId }: { clientId: string }) => { setResource(resource); reset(resource); }, - [] + [], ); const importResource = async (value: ResourceServerRepresentation) => { @@ -67,7 +67,7 @@ export const AuthorizationSettings = ({ clientId }: { clientId: string }) => { try { await adminClient.clients.updateResourceServer( { id: clientId }, - resource + resource, ); addAlert(t("updateResourceSuccess"), AlertVariant.success); } catch (error) { diff --git a/js/apps/admin-ui/src/clients/authorization/evaluate/Results.tsx b/js/apps/admin-ui/src/clients/authorization/evaluate/Results.tsx index dd5150aedcd3..9ce955d7f9ba 100644 --- a/js/apps/admin-ui/src/clients/authorization/evaluate/Results.tsx +++ b/js/apps/admin-ui/src/clients/authorization/evaluate/Results.tsx @@ -40,7 +40,7 @@ enum ResultsFilter { function filterResults( results: EvaluationResultRepresentation[], - filter: ResultsFilter + filter: ResultsFilter, ) { switch (filter) { case ResultsFilter.StatusPermitted: @@ -74,9 +74,9 @@ export const Results = ({ evaluateResult, refresh, back }: ResultProps) => { const filteredResources = useMemo( () => filterResults(evaluateResult.results!, filter).filter( - ({ resource }) => resource?.name?.includes(searchQuery) ?? false + ({ resource }) => resource?.name?.includes(searchQuery) ?? false, ), - [evaluateResult.results, filter, searchQuery] + [evaluateResult.results, filter, searchQuery], ); const noEvaluatedData = evaluateResult.results!.length === 0; diff --git a/js/apps/admin-ui/src/clients/authorization/policy/Client.tsx b/js/apps/admin-ui/src/clients/authorization/policy/Client.tsx index 781ebdd114f8..8165e08d4a11 100644 --- a/js/apps/admin-ui/src/clients/authorization/policy/Client.tsx +++ b/js/apps/admin-ui/src/clients/authorization/policy/Client.tsx @@ -41,14 +41,14 @@ export const Client = () => { return await Promise.all( values.map( (id: string) => - adminClient.clients.findOne({ id }) as ClientRepresentation - ) + adminClient.clients.findOne({ id }) as ClientRepresentation, + ), ); } return await adminClient.clients.find(params); }, setClients, - [search] + [search], ); const convert = (clients: ClientRepresentation[]) => @@ -98,7 +98,7 @@ export const Client = () => { const option = v.toString(); if (field.value.includes(option)) { field.onChange( - field.value.filter((item: string) => item !== option) + field.value.filter((item: string) => item !== option), ); } else { field.onChange([...field.value, option]); diff --git a/js/apps/admin-ui/src/clients/authorization/policy/ClientScope.tsx b/js/apps/admin-ui/src/clients/authorization/policy/ClientScope.tsx index 3beb164b441b..e68f12db8b5c 100644 --- a/js/apps/admin-ui/src/clients/authorization/policy/ClientScope.tsx +++ b/js/apps/admin-ui/src/clients/authorization/policy/ClientScope.tsx @@ -47,11 +47,13 @@ export const ClientScope = () => { () => adminClient.clientScopes.find(), (scopes) => { setSelectedScopes( - getValues("clientScopes").map((s) => scopes.find((c) => c.id === s.id)!) + getValues("clientScopes").map( + (s) => scopes.find((c) => c.id === s.id)!, + ), ); setScopes(localeSort(scopes, mapByKey("name"))); }, - [] + [], ); return ( @@ -84,7 +86,7 @@ export const ClientScope = () => { (scope) => !field.value .map((c: RequiredIdValue) => c.id) - .includes(scope.id!) + .includes(scope.id!), )} isClientScopesConditionType open={open} @@ -152,7 +154,7 @@ export const ClientScope = () => { onClick={() => { setValue("clientScopes", [ ...getValues("clientScopes").filter( - (s) => s.id !== scope.id + (s) => s.id !== scope.id, ), ]); setSelectedScopes([ diff --git a/js/apps/admin-ui/src/clients/authorization/policy/Group.tsx b/js/apps/admin-ui/src/clients/authorization/policy/Group.tsx index 8369b34ab01b..19cfe40b12af 100644 --- a/js/apps/admin-ui/src/clients/authorization/policy/Group.tsx +++ b/js/apps/admin-ui/src/clients/authorization/policy/Group.tsx @@ -42,14 +42,14 @@ export const Group = () => { const [open, setOpen] = useState(false); const [selectedGroups, setSelectedGroups] = useState( - [] + [], ); useFetch( () => { if (values && values.length > 0) return Promise.all( - values.map((g) => adminClient.groups.findOne({ id: g.id })) + values.map((g) => adminClient.groups.findOne({ id: g.id })), ); return Promise.resolve([]); }, @@ -57,7 +57,7 @@ export const Group = () => { const filteredGroup = groups.filter((g) => g) as GroupRepresentation[]; setSelectedGroups(filteredGroup); }, - [] + [], ); return ( diff --git a/js/apps/admin-ui/src/clients/authorization/policy/PolicyDetails.tsx b/js/apps/admin-ui/src/clients/authorization/policy/PolicyDetails.tsx index d83aa8973570..03f5f2ad45ba 100644 --- a/js/apps/admin-ui/src/clients/authorization/policy/PolicyDetails.tsx +++ b/js/apps/admin-ui/src/clients/authorization/policy/PolicyDetails.tsx @@ -103,7 +103,7 @@ export default function PolicyDetails() { reset({ ...policy, policies }); setPolicy(policy); }, - [id, policyType, policyId] + [id, policyType, policyId], ); const onSubmit = async (policy: Policy) => { @@ -118,12 +118,12 @@ export default function PolicyDetails() { if (policyId) { await adminClient.clients.updatePolicy( { id, type: policyType, policyId }, - policy + policy, ); } else { const result = await adminClient.clients.createPolicy( { id, type: policyType }, - policy + policy, ); navigate( toPolicyDetails({ @@ -131,12 +131,12 @@ export default function PolicyDetails() { id, policyType, policyId: result.id!, - }) + }), ); } addAlert( t((policyId ? "update" : "create") + "PolicySuccess"), - AlertVariant.success + AlertVariant.success, ); } catch (error) { addError("clients:policySaveError", error); diff --git a/js/apps/admin-ui/src/clients/authorization/policy/Role.tsx b/js/apps/admin-ui/src/clients/authorization/policy/Role.tsx index 61dcd0783cc9..2437f5bcca51 100644 --- a/js/apps/admin-ui/src/clients/authorization/policy/Role.tsx +++ b/js/apps/admin-ui/src/clients/authorization/policy/Role.tsx @@ -38,7 +38,7 @@ export const Role = () => { async () => { if (values && values.length > 0) { const roles = await Promise.all( - values.map((r) => adminClient.roles.findOneById({ id: r.id })) + values.map((r) => adminClient.roles.findOneById({ id: r.id })), ); return Promise.all( roles.map(async (role) => ({ @@ -48,13 +48,13 @@ export const Role = () => { id: role?.containerId!, }) : undefined, - })) + })), ); } return Promise.resolve([]); }, setSelectedRoles, - [] + [], ); return ( @@ -153,7 +153,7 @@ export const Role = () => { ]); setSelectedRoles([ ...selectedRoles.filter( - (s) => s.role.id !== row.role.id + (s) => s.role.id !== row.role.id, ), ]); }} diff --git a/js/apps/admin-ui/src/clients/authorization/policy/Time.tsx b/js/apps/admin-ui/src/clients/authorization/policy/Time.tsx index ff8fa69b4f83..91f638032b3a 100644 --- a/js/apps/admin-ui/src/clients/authorization/policy/Time.tsx +++ b/js/apps/admin-ui/src/clients/authorization/policy/Time.tsx @@ -42,7 +42,7 @@ const DateTime = ({ name }: { name: string }) => { const parseTime = ( value: string, hour?: number | null, - minute?: number | null + minute?: number | null, ): string => { const parts = value.match(DATE_TIME_FORMAT); if (minute !== undefined && minute !== null) { diff --git a/js/apps/admin-ui/src/clients/credentials/ClientSecret.tsx b/js/apps/admin-ui/src/clients/credentials/ClientSecret.tsx index ff08de36d374..8c48ea694700 100644 --- a/js/apps/admin-ui/src/clients/credentials/ClientSecret.tsx +++ b/js/apps/admin-ui/src/clients/credentials/ClientSecret.tsx @@ -89,7 +89,7 @@ export const ClientSecret = ({ client, secret, toggle }: ClientSecretProps) => { const { addAlert, addError } = useAlerts(); const [secretRotated, setSecretRotated] = useState( - client.attributes?.["client.secret.rotated"] + client.attributes?.["client.secret.rotated"], ); const secretExpirationTime: number = client.attributes?.["client.secret.expiration.time"]; diff --git a/js/apps/admin-ui/src/clients/credentials/Credentials.tsx b/js/apps/admin-ui/src/clients/credentials/Credentials.tsx index b377aedaef36..4affb148932f 100644 --- a/js/apps/admin-ui/src/clients/credentials/Credentials.tsx +++ b/js/apps/admin-ui/src/clients/credentials/Credentials.tsx @@ -79,12 +79,12 @@ export const Credentials = ({ client, save, refresh }: CredentialsProps) => { setProviders(providers); setSecret(secret.value!); }, - [] + [], ); async function regenerate( call: (clientId: string) => Promise, - message: string + message: string, ): Promise { try { const data = await call(clientId); @@ -99,7 +99,7 @@ export const Credentials = ({ client, save, refresh }: CredentialsProps) => { const secret = await regenerate( (clientId) => adminClient.clients.generateNewClientSecret({ id: clientId }), - "clientSecret" + "clientSecret", ); setSecret(secret?.value || ""); refresh(); @@ -117,7 +117,7 @@ export const Credentials = ({ client, save, refresh }: CredentialsProps) => { const accessToken = await regenerate( (clientId) => adminClient.clients.generateRegistrationAccessToken({ id: clientId }), - "accessToken" + "accessToken", ); setAccessToken(accessToken?.registrationAccessToken || ""); }; diff --git a/js/apps/admin-ui/src/clients/credentials/SignedJWT.tsx b/js/apps/admin-ui/src/clients/credentials/SignedJWT.tsx index 4ba04d359073..e72af8833a97 100644 --- a/js/apps/admin-ui/src/clients/credentials/SignedJWT.tsx +++ b/js/apps/admin-ui/src/clients/credentials/SignedJWT.tsx @@ -41,7 +41,7 @@ export const SignedJWT = ({ clientAuthenticatorType }: SignedJWTProps) => { > ( - "attributes.token.endpoint.auth.signing.alg" + "attributes.token.endpoint.auth.signing.alg", )} defaultValue="" control={control} diff --git a/js/apps/admin-ui/src/clients/credentials/X509.tsx b/js/apps/admin-ui/src/clients/credentials/X509.tsx index ebcc65ea4966..5842949ebfac 100644 --- a/js/apps/admin-ui/src/clients/credentials/X509.tsx +++ b/js/apps/admin-ui/src/clients/credentials/X509.tsx @@ -28,7 +28,7 @@ export const X509 = () => { > ( - "attributes.x509.allow.regex.pattern.comparison" + "attributes.x509.allow.regex.pattern.comparison", )} defaultValue="false" control={control} @@ -71,7 +71,7 @@ export const X509 = () => { } {...register( convertAttributeNameToForm("attributes.x509.subjectdn"), - { required: true } + { required: true }, )} /> diff --git a/js/apps/admin-ui/src/clients/import/ImportForm.tsx b/js/apps/admin-ui/src/clients/import/ImportForm.tsx index 22905958d7fc..4fbab73c0a97 100644 --- a/js/apps/admin-ui/src/clients/import/ImportForm.tsx +++ b/js/apps/admin-ui/src/clients/import/ImportForm.tsx @@ -55,7 +55,7 @@ export default function ImportForm() { }; async function parseFileContents( - contents: string + contents: string, ): Promise { if (!isXml(contents)) { return JSON.parse(contents); @@ -63,18 +63,18 @@ export default function ImportForm() { const response = await fetch( `${addTrailingSlash( - adminClient.baseUrl + adminClient.baseUrl, )}admin/realms/${realm}/client-description-converter`, { method: "POST", body: contents, headers: getAuthorizationHeaders(await adminClient.getAccessToken()), - } + }, ); if (!response.ok) { throw new Error( - `Server responded with invalid status: ${response.statusText}` + `Server responded with invalid status: ${response.statusText}`, ); } diff --git a/js/apps/admin-ui/src/clients/initial-access/CreateInitialAccessToken.tsx b/js/apps/admin-ui/src/clients/initial-access/CreateInitialAccessToken.tsx index 478c41e6ba43..f60e7a2eab0d 100644 --- a/js/apps/admin-ui/src/clients/initial-access/CreateInitialAccessToken.tsx +++ b/js/apps/admin-ui/src/clients/initial-access/CreateInitialAccessToken.tsx @@ -40,7 +40,7 @@ export default function CreateInitialAccessToken() { try { const access = await adminClient.realms.createClientsInitialAccess( { realm }, - clientToken + clientToken, ); setToken(access.token!); } catch (error) { @@ -123,7 +123,7 @@ export default function CreateInitialAccessToken() { onMinus={() => field.onChange(field.value - 1)} onChange={(event) => { const value = Number( - (event.target as HTMLInputElement).value + (event.target as HTMLInputElement).value, ); field.onChange(value < 1 ? 1 : value); }} diff --git a/js/apps/admin-ui/src/clients/initial-access/InitialAccessTokenList.tsx b/js/apps/admin-ui/src/clients/initial-access/InitialAccessTokenList.tsx index eaf4cd7c7e64..2062f87d3c12 100644 --- a/js/apps/admin-ui/src/clients/initial-access/InitialAccessTokenList.tsx +++ b/js/apps/admin-ui/src/clients/initial-access/InitialAccessTokenList.tsx @@ -98,7 +98,7 @@ export const InitialAccessTokenList = () => { cellRenderer: (row) => formatDate( new Date(row.timestamp! * 1000 + row.expiration! * 1000), - FORMAT_DATE_AND_TIME + FORMAT_DATE_AND_TIME, ), }, { diff --git a/js/apps/admin-ui/src/clients/keys/ExportSamlKeyDialog.tsx b/js/apps/admin-ui/src/clients/keys/ExportSamlKeyDialog.tsx index 2fe471cb589a..95456dd169de 100644 --- a/js/apps/admin-ui/src/clients/keys/ExportSamlKeyDialog.tsx +++ b/js/apps/admin-ui/src/clients/keys/ExportSamlKeyDialog.tsx @@ -34,11 +34,11 @@ export const ExportSamlKeyDialog = ({ id: clientId, attr: "saml.signing", }, - config + config, ); saveAs( new Blob([keyStore], { type: "application/octet-stream" }), - `keystore.${getFileExtension(config.format ?? "")}` + `keystore.${getFileExtension(config.format ?? "")}`, ); addAlert(t("samlKeysExportSuccess")); close(); diff --git a/js/apps/admin-ui/src/clients/keys/Keys.tsx b/js/apps/admin-ui/src/clients/keys/Keys.tsx index 3f4c77686b56..46ce0f4fdc64 100644 --- a/js/apps/admin-ui/src/clients/keys/Keys.tsx +++ b/js/apps/admin-ui/src/clients/keys/Keys.tsx @@ -66,7 +66,7 @@ export const Keys = ({ clientId, save, hasConfigureAccess }: KeysProps) => { useFetch( () => adminClient.clients.getKeyInfo({ id: clientId, attr }), (info) => setKeyInfo(info), - [key] + [key], ); const generate = async (config: KeyStoreConfig) => { @@ -76,11 +76,11 @@ export const Keys = ({ clientId, save, hasConfigureAccess }: KeysProps) => { id: clientId, attr, }, - config + config, ); saveAs( new Blob([keyStore], { type: "application/octet-stream" }), - `keystore.${getFileExtension(config.format ?? "")}` + `keystore.${getFileExtension(config.format ?? "")}`, ); addAlert(t("generateSuccess"), AlertVariant.success); refresh(); @@ -102,7 +102,7 @@ export const Keys = ({ clientId, save, hasConfigureAccess }: KeysProps) => { await adminClient.clients.uploadCertificate( { id: clientId, attr }, - formData + formData, ); addAlert(t("importSuccess"), AlertVariant.success); refresh(); @@ -186,7 +186,7 @@ export const Keys = ({ clientId, save, hasConfigureAccess }: KeysProps) => { id="jwksUrl" type="url" {...register( - convertAttributeNameToForm("attributes.jwks.url") + convertAttributeNameToForm("attributes.jwks.url"), )} /> diff --git a/js/apps/admin-ui/src/clients/keys/SamlKeys.tsx b/js/apps/admin-ui/src/clients/keys/SamlKeys.tsx index 5fa00c389a0d..e8c018769466 100644 --- a/js/apps/admin-ui/src/clients/keys/SamlKeys.tsx +++ b/js/apps/admin-ui/src/clients/keys/SamlKeys.tsx @@ -169,11 +169,11 @@ export const SamlKeys = ({ clientId, save }: SamlKeysProps) => { () => Promise.all( KEYS.map((attr) => - adminClient.clients.getKeyInfo({ id: clientId, attr }) - ) + adminClient.clients.getKeyInfo({ id: clientId, attr }), + ), ), (info) => setKeyInfo(info), - [refresh] + [refresh], ); const generate = async (attr: KeyTypes) => { @@ -190,7 +190,7 @@ export const SamlKeys = ({ clientId, save }: SamlKeysProps) => { new Blob([info[index].privateKey!], { type: "application/octet-stream", }), - "private.key" + "private.key", ); addAlert(t("generateSuccess"), AlertVariant.success); diff --git a/js/apps/admin-ui/src/clients/keys/SamlKeysDialog.tsx b/js/apps/admin-ui/src/clients/keys/SamlKeysDialog.tsx index 1e7f89f277e1..4141a706f669 100644 --- a/js/apps/admin-ui/src/clients/keys/SamlKeysDialog.tsx +++ b/js/apps/admin-ui/src/clients/keys/SamlKeysDialog.tsx @@ -44,7 +44,7 @@ export const submitForm = async ( form: SamlKeysDialogForm, id: string, attr: KeyTypes, - callback: (error?: unknown) => void + callback: (error?: unknown) => void, ) => { try { const formData = new FormData(); @@ -52,8 +52,8 @@ export const submitForm = async ( Object.entries(rest).map(([key, value]) => formData.append( key === "format" ? "keystoreFormat" : key, - value.toString() - ) + value.toString(), + ), ); formData.append("file", file); @@ -102,7 +102,7 @@ export const SamlKeysDialog = ({ new Blob([key.privateKey!], { type: "application/octet-stream", }), - "private.key" + "private.key", ); addAlert(t("generateSuccess"), AlertVariant.success); diff --git a/js/apps/admin-ui/src/clients/registration/AddProviderDialog.tsx b/js/apps/admin-ui/src/clients/registration/AddProviderDialog.tsx index e95791350c17..1a83b987c60a 100644 --- a/js/apps/admin-ui/src/clients/registration/AddProviderDialog.tsx +++ b/js/apps/admin-ui/src/clients/registration/AddProviderDialog.tsx @@ -24,7 +24,7 @@ export const AddProviderDialog = ({ const { t } = useTranslation("clients"); const serverInfo = useServerInfo(); const providers = Object.keys( - serverInfo.providers?.["client-registration-policy"].providers || [] + serverInfo.providers?.["client-registration-policy"].providers || [], ); const descriptions = @@ -37,9 +37,9 @@ export const AddProviderDialog = ({ () => localeSort( descriptions?.filter((d) => providers.includes(d.id)) || [], - mapByKey("id") + mapByKey("id"), ), - [providers, descriptions] + [providers, descriptions], ); return ( {name} - ) + ), )} /> diff --git a/js/apps/admin-ui/src/clients/registration/ClientRegistrationList.tsx b/js/apps/admin-ui/src/clients/registration/ClientRegistrationList.tsx index 1b61f1367bd9..1145ef3db556 100644 --- a/js/apps/admin-ui/src/clients/registration/ClientRegistrationList.tsx +++ b/js/apps/admin-ui/src/clients/registration/ClientRegistrationList.tsx @@ -61,7 +61,7 @@ export const ClientRegistrationList = ({ type: "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy", }), (policies) => setPolicies(policies.filter((p) => p.subType === subType)), - [selectedPolicy] + [selectedPolicy], ); const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ @@ -95,7 +95,7 @@ export const ClientRegistrationList = ({ realm, subTab: subTab || "anonymous", providerId, - }) + }), ) } toggleDialog={toggleAddDialog} diff --git a/js/apps/admin-ui/src/clients/registration/DetailProvider.tsx b/js/apps/admin-ui/src/clients/registration/DetailProvider.tsx index f1970fefd5a6..f95b5ced2dc4 100644 --- a/js/apps/admin-ui/src/clients/registration/DetailProvider.tsx +++ b/js/apps/admin-ui/src/clients/registration/DetailProvider.tsx @@ -64,7 +64,7 @@ export default function DetailProvider() { setParentId(realm?.id || ""); reset(data || { providerId }); }, - [] + [], ); const providerName = useWatch({ control, defaultValue: "", name: "name" }); @@ -73,7 +73,7 @@ export default function DetailProvider() { if (component.config) Object.entries(component.config).forEach( ([key, value]) => - (component.config![key] = Array.isArray(value) ? value : [value]) + (component.config![key] = Array.isArray(value) ? value : [value]), ); try { const updatedComponent = { diff --git a/js/apps/admin-ui/src/clients/roles/CreateClientRole.tsx b/js/apps/admin-ui/src/clients/roles/CreateClientRole.tsx index c10217018845..3f3fba5817d7 100644 --- a/js/apps/admin-ui/src/clients/roles/CreateClientRole.tsx +++ b/js/apps/admin-ui/src/clients/roles/CreateClientRole.tsx @@ -46,7 +46,7 @@ export default function CreateClientRole() { clientId: clientId!, id: createdRole.id!, tab: "details", - }) + }), ); } catch (error) { addError("roles:roleCreateError", error); diff --git a/js/apps/admin-ui/src/clients/routes/AddRegistrationProvider.tsx b/js/apps/admin-ui/src/clients/routes/AddRegistrationProvider.tsx index 7de3abb24689..feafb57941a8 100644 --- a/js/apps/admin-ui/src/clients/routes/AddRegistrationProvider.tsx +++ b/js/apps/admin-ui/src/clients/routes/AddRegistrationProvider.tsx @@ -28,7 +28,7 @@ export const EditRegistrationProviderRoute: AppRouteObject = { }; export const toRegistrationProvider = ( - params: RegistrationProviderParams + params: RegistrationProviderParams, ): Partial => { const path = params.id ? EditRegistrationProviderRoute.path diff --git a/js/apps/admin-ui/src/clients/routes/AuthenticationTab.tsx b/js/apps/admin-ui/src/clients/routes/AuthenticationTab.tsx index 87531cfeba05..abb4d5454d70 100644 --- a/js/apps/admin-ui/src/clients/routes/AuthenticationTab.tsx +++ b/js/apps/admin-ui/src/clients/routes/AuthenticationTab.tsx @@ -30,7 +30,7 @@ export const AuthorizationRoute: AppRouteObject = { }; export const toAuthorizationTab = ( - params: AuthorizationParams + params: AuthorizationParams, ): Partial => ({ pathname: generatePath(AuthorizationRoute.path, params), }); diff --git a/js/apps/admin-ui/src/clients/routes/ClientRegistration.tsx b/js/apps/admin-ui/src/clients/routes/ClientRegistration.tsx index c1cd0f708e70..aed37eea8c6b 100644 --- a/js/apps/admin-ui/src/clients/routes/ClientRegistration.tsx +++ b/js/apps/admin-ui/src/clients/routes/ClientRegistration.tsx @@ -22,7 +22,7 @@ export const ClientRegistrationRoute: AppRouteObject = { }; export const toClientRegistration = ( - params: ClientRegistrationParams + params: ClientRegistrationParams, ): Partial => ({ pathname: generatePath(ClientRegistrationRoute.path, params), }); diff --git a/js/apps/admin-ui/src/clients/routes/ClientScopeTab.tsx b/js/apps/admin-ui/src/clients/routes/ClientScopeTab.tsx index f6965248a474..50f100489ee6 100644 --- a/js/apps/admin-ui/src/clients/routes/ClientScopeTab.tsx +++ b/js/apps/admin-ui/src/clients/routes/ClientScopeTab.tsx @@ -23,7 +23,7 @@ export const ClientScopesRoute: AppRouteObject = { }; export const toClientScopesTab = ( - params: ClientScopesParams + params: ClientScopesParams, ): Partial => ({ pathname: generatePath(ClientScopesRoute.path, params), }); diff --git a/js/apps/admin-ui/src/clients/routes/CreateInitialAccessToken.tsx b/js/apps/admin-ui/src/clients/routes/CreateInitialAccessToken.tsx index bef5bd91061f..85e8ba23036c 100644 --- a/js/apps/admin-ui/src/clients/routes/CreateInitialAccessToken.tsx +++ b/js/apps/admin-ui/src/clients/routes/CreateInitialAccessToken.tsx @@ -6,7 +6,7 @@ import type { AppRouteObject } from "../../routes"; export type CreateInitialAccessTokenParams = { realm: string }; const CreateInitialAccessToken = lazy( - () => import("../initial-access/CreateInitialAccessToken") + () => import("../initial-access/CreateInitialAccessToken"), ); export const CreateInitialAccessTokenRoute: AppRouteObject = { @@ -19,7 +19,7 @@ export const CreateInitialAccessTokenRoute: AppRouteObject = { }; export const toCreateInitialAccessToken = ( - params: CreateInitialAccessTokenParams + params: CreateInitialAccessTokenParams, ): Partial => ({ pathname: generatePath(CreateInitialAccessTokenRoute.path, params), }); diff --git a/js/apps/admin-ui/src/clients/routes/DedicatedScopeDetails.tsx b/js/apps/admin-ui/src/clients/routes/DedicatedScopeDetails.tsx index 16a0c97d7646..4ebfed42b454 100644 --- a/js/apps/admin-ui/src/clients/routes/DedicatedScopeDetails.tsx +++ b/js/apps/admin-ui/src/clients/routes/DedicatedScopeDetails.tsx @@ -28,7 +28,7 @@ export const DedicatedScopeDetailsWithTabRoute: AppRouteObject = { }; export const toDedicatedScope = ( - params: DedicatedScopeDetailsParams + params: DedicatedScopeDetailsParams, ): Partial => { const path = params.tab ? DedicatedScopeDetailsWithTabRoute.path diff --git a/js/apps/admin-ui/src/clients/routes/Mapper.tsx b/js/apps/admin-ui/src/clients/routes/Mapper.tsx index 99e2bdca0b10..85554f669fd3 100644 --- a/js/apps/admin-ui/src/clients/routes/Mapper.tsx +++ b/js/apps/admin-ui/src/clients/routes/Mapper.tsx @@ -10,7 +10,7 @@ export type MapperParams = { }; const MappingDetails = lazy( - () => import("../../client-scopes/details/MappingDetails") + () => import("../../client-scopes/details/MappingDetails"), ); export const MapperRoute: AppRouteObject = { diff --git a/js/apps/admin-ui/src/clients/routes/NewPermission.tsx b/js/apps/admin-ui/src/clients/routes/NewPermission.tsx index 5bf22884159a..7f9d9c7b8d46 100644 --- a/js/apps/admin-ui/src/clients/routes/NewPermission.tsx +++ b/js/apps/admin-ui/src/clients/routes/NewPermission.tsx @@ -13,7 +13,7 @@ export type NewPermissionParams = { }; const PermissionDetails = lazy( - () => import("../authorization/PermissionDetails") + () => import("../authorization/PermissionDetails"), ); export const NewPermissionRoute: AppRouteObject = { diff --git a/js/apps/admin-ui/src/clients/routes/NewPolicy.tsx b/js/apps/admin-ui/src/clients/routes/NewPolicy.tsx index 7f883816c4fa..590e1ab95782 100644 --- a/js/apps/admin-ui/src/clients/routes/NewPolicy.tsx +++ b/js/apps/admin-ui/src/clients/routes/NewPolicy.tsx @@ -6,7 +6,7 @@ import type { AppRouteObject } from "../../routes"; export type NewPolicyParams = { realm: string; id: string; policyType: string }; const PolicyDetails = lazy( - () => import("../authorization/policy/PolicyDetails") + () => import("../authorization/policy/PolicyDetails"), ); export const NewPolicyRoute: AppRouteObject = { diff --git a/js/apps/admin-ui/src/clients/routes/PermissionDetails.tsx b/js/apps/admin-ui/src/clients/routes/PermissionDetails.tsx index 255b24e6687b..a95aefdae21f 100644 --- a/js/apps/admin-ui/src/clients/routes/PermissionDetails.tsx +++ b/js/apps/admin-ui/src/clients/routes/PermissionDetails.tsx @@ -12,7 +12,7 @@ export type PermissionDetailsParams = { }; const PermissionDetails = lazy( - () => import("../authorization/PermissionDetails") + () => import("../authorization/PermissionDetails"), ); export const PermissionDetailsRoute: AppRouteObject = { @@ -25,7 +25,7 @@ export const PermissionDetailsRoute: AppRouteObject = { }; export const toPermissionDetails = ( - params: PermissionDetailsParams + params: PermissionDetailsParams, ): Partial => ({ pathname: generatePath(PermissionDetailsRoute.path, params), }); diff --git a/js/apps/admin-ui/src/clients/routes/PolicyDetails.tsx b/js/apps/admin-ui/src/clients/routes/PolicyDetails.tsx index f36455d03221..ee8851c8bd8b 100644 --- a/js/apps/admin-ui/src/clients/routes/PolicyDetails.tsx +++ b/js/apps/admin-ui/src/clients/routes/PolicyDetails.tsx @@ -11,7 +11,7 @@ export type PolicyDetailsParams = { }; const PolicyDetails = lazy( - () => import("../authorization/policy/PolicyDetails") + () => import("../authorization/policy/PolicyDetails"), ); export const PolicyDetailsRoute: AppRouteObject = { @@ -24,7 +24,7 @@ export const PolicyDetailsRoute: AppRouteObject = { }; export const toPolicyDetails = ( - params: PolicyDetailsParams + params: PolicyDetailsParams, ): Partial => ({ pathname: generatePath(PolicyDetailsRoute.path, params), }); diff --git a/js/apps/admin-ui/src/clients/routes/Resource.tsx b/js/apps/admin-ui/src/clients/routes/Resource.tsx index c53472353c89..c12ec6b5c30f 100644 --- a/js/apps/admin-ui/src/clients/routes/Resource.tsx +++ b/js/apps/admin-ui/src/clients/routes/Resource.tsx @@ -26,7 +26,7 @@ export const ResourceDetailsWithResourceIdRoute: AppRouteObject = { }; export const toResourceDetails = ( - params: ResourceDetailsParams + params: ResourceDetailsParams, ): Partial => { const path = params.resourceId ? ResourceDetailsWithResourceIdRoute.path diff --git a/js/apps/admin-ui/src/clients/scopes/AddScopeDialog.tsx b/js/apps/admin-ui/src/clients/scopes/AddScopeDialog.tsx index b814e0b88ca1..5f686bfc4cef 100644 --- a/js/apps/admin-ui/src/clients/scopes/AddScopeDialog.tsx +++ b/js/apps/admin-ui/src/clients/scopes/AddScopeDialog.tsx @@ -37,7 +37,7 @@ export type AddScopeDialogProps = { open: boolean; toggleDialog: () => void; onAdd: ( - scopes: { scope: ClientScopeRepresentation; type?: ClientScopeType }[] + scopes: { scope: ClientScopeRepresentation; type?: ClientScopeType }[], ) => void; isClientScopesConditionType?: boolean; }; diff --git a/js/apps/admin-ui/src/clients/scopes/ClientScopes.tsx b/js/apps/admin-ui/src/clients/scopes/ClientScopes.tsx index 15dbbcf57cce..96231f289037 100644 --- a/js/apps/admin-ui/src/clients/scopes/ClientScopes.tsx +++ b/js/apps/admin-ui/src/clients/scopes/ClientScopes.tsx @@ -90,7 +90,7 @@ const TypeSelector = ({ clientId, scope, scope.type, - value as ClientScope + value as ClientScope, ); addAlert(t("clientScopeSuccess"), AlertVariant.success); refresh(); @@ -116,7 +116,7 @@ export const ClientScopes = ({ const [searchType, setSearchType] = useState("name"); const [searchTypeType, setSearchTypeType] = useState( - AllClientScopes.none + AllClientScopes.none, ); const [addDialogOpen, setAddDialogOpen] = useState(false); @@ -170,7 +170,7 @@ export const ClientScopes = ({ setRest( clientScopes .filter((scope) => !names.includes(scope.name)) - .filter((scope) => scope.protocol === protocol) + .filter((scope) => scope.protocol === protocol), ); const filter = @@ -203,7 +203,7 @@ export const ClientScopes = ({ await removeClientScope( clientId, selectedRows[0], - selectedRows[0].type as ClientScope + selectedRows[0].type as ClientScope, ); addAlert(t("clientScopeRemoveSuccess"), AlertVariant.success); refresh(); @@ -226,8 +226,8 @@ export const ClientScopes = ({ await Promise.all( scopes.map( async (scope) => - await addClientScope(clientId, scope.scope, scope.type!) - ) + await addClientScope(clientId, scope.scope, scope.type!), + ), ); addAlert(t("clientScopeSuccess"), AlertVariant.success); refresh(); @@ -299,9 +299,9 @@ export const ClientScopes = ({ removeClientScope( clientId, { ...row }, - row.type as ClientScope - ) - ) + row.type as ClientScope, + ), + ), ); setKebabOpen(false); diff --git a/js/apps/admin-ui/src/clients/scopes/DecicatedScope.tsx b/js/apps/admin-ui/src/clients/scopes/DecicatedScope.tsx index 3da4f8349159..cc3e30e1ce68 100644 --- a/js/apps/admin-ui/src/clients/scopes/DecicatedScope.tsx +++ b/js/apps/admin-ui/src/clients/scopes/DecicatedScope.tsx @@ -44,7 +44,7 @@ export const DedicatedScope = ({ { id: client.id!, }, - realmRoles + realmRoles, ), ...rows .filter((row) => row.client !== undefined) @@ -54,8 +54,8 @@ export const DedicatedScope = ({ id: client.id!, client: row.client!.id!, }, - [row.role as RoleMappingPayload] - ) + [row.role as RoleMappingPayload], + ), ), ]); diff --git a/js/apps/admin-ui/src/clients/scopes/DedicatedScopes.tsx b/js/apps/admin-ui/src/clients/scopes/DedicatedScopes.tsx index 94727749a22f..1b348a8f8505 100644 --- a/js/apps/admin-ui/src/clients/scopes/DedicatedScopes.tsx +++ b/js/apps/admin-ui/src/clients/scopes/DedicatedScopes.tsx @@ -51,7 +51,7 @@ export default function DedicatedScopes() { } const addMappers = async ( - mappers: ProtocolMapperTypeRepresentation | ProtocolMapperRepresentation[] + mappers: ProtocolMapperTypeRepresentation | ProtocolMapperRepresentation[], ): Promise => { if (!Array.isArray(mappers)) { const mapper = mappers as ProtocolMapperTypeRepresentation; @@ -60,13 +60,13 @@ export default function DedicatedScopes() { realm, id: client.id!, mapperId: mapper.id!, - }) + }), ); } else { try { await adminClient.clients.addMultipleProtocolMappers( { id: client.id! }, - mappers as ProtocolMapperRepresentation[] + mappers as ProtocolMapperRepresentation[], ); setClient(await adminClient.clients.findOne({ id: client.id! })); addAlert(t("common:mappingCreatedSuccess"), AlertVariant.success); @@ -85,7 +85,7 @@ export default function DedicatedScopes() { setClient({ ...client, protocolMappers: client.protocolMappers?.filter( - (m) => m.id !== mapper.id + (m) => m.id !== mapper.id, ), }); addAlert(t("common:mappingDeletedSuccess"), AlertVariant.success); diff --git a/js/apps/admin-ui/src/clients/scopes/EvaluateScopes.tsx b/js/apps/admin-ui/src/clients/scopes/EvaluateScopes.tsx index 2d56852f6988..d10b1d9a504c 100644 --- a/js/apps/admin-ui/src/clients/scopes/EvaluateScopes.tsx +++ b/js/apps/admin-ui/src/clients/scopes/EvaluateScopes.tsx @@ -128,7 +128,7 @@ export const EvaluateScopes = ({ clientId, protocol }: EvaluateScopesProps) => { const [key, setKey] = useState(""); const refresh = () => setKey(`${new Date().getTime()}`); const [effectiveRoles, setEffectiveRoles] = useState( - [] + [], ); const [protocolMappers, setProtocolMappers] = useState< ProtocolMapperRepresentation[] @@ -151,7 +151,7 @@ export const EvaluateScopes = ({ clientId, protocol }: EvaluateScopesProps) => { useFetch( () => adminClient.clients.listOptionalClientScopes({ id: clientId }), (optionalClientScopes) => setSelectableScopes(optionalClientScopes), - [] + [], ); useFetch( @@ -180,14 +180,14 @@ export const EvaluateScopes = ({ clientId, protocol }: EvaluateScopesProps) => { setEffectiveRoles(effectiveRoles); mapperList.map((mapper) => { mapper.type = mapperTypes.filter( - (type) => type.id === mapper.protocolMapper + (type) => type.id === mapper.protocolMapper, )[0]; }); setProtocolMappers(mapperList); refresh(); }, - [selected] + [selected], ); useFetch( @@ -219,7 +219,7 @@ export const EvaluateScopes = ({ clientId, protocol }: EvaluateScopesProps) => { setUserInfo(prettyPrintJSON(userInfo)); setIdToken(prettyPrintJSON(idToken)); }, - [form.getValues("user"), selected] + [form.getValues("user"), selected], ); return ( diff --git a/js/apps/admin-ui/src/clients/service-account/ServiceAccount.tsx b/js/apps/admin-ui/src/clients/service-account/ServiceAccount.tsx index a3480bc07fb7..d81723f6640b 100644 --- a/js/apps/admin-ui/src/clients/service-account/ServiceAccount.tsx +++ b/js/apps/admin-ui/src/clients/service-account/ServiceAccount.tsx @@ -38,7 +38,7 @@ export const ServiceAccount = ({ client }: ServiceAccountProps) => { id: client.id!, }), (serviceAccount) => setServiceAccount(serviceAccount), - [] + [], ); const assignRoles = async (rows: Row[]) => { @@ -59,8 +59,8 @@ export const ServiceAccount = ({ client }: ServiceAccountProps) => { id: serviceAccount?.id!, clientUniqueId: row.client!.id!, roles: [row.role as RoleMappingPayload], - }) - ) + }), + ), ); addAlert(t("roleMappingUpdatedSuccess"), AlertVariant.success); } catch (error) { diff --git a/js/apps/admin-ui/src/components/SwitchControl.tsx b/js/apps/admin-ui/src/components/SwitchControl.tsx index c9b0cb98da7e..fa5738729f46 100644 --- a/js/apps/admin-ui/src/components/SwitchControl.tsx +++ b/js/apps/admin-ui/src/components/SwitchControl.tsx @@ -5,7 +5,7 @@ import { SwitchControl } from "ui-shared"; type DefaultSwitchControlProps< T extends FieldValues, - P extends FieldPath = FieldPath + P extends FieldPath = FieldPath, > = SwitchProps & UseControllerProps & { name: string; @@ -16,9 +16,9 @@ type DefaultSwitchControlProps< export const DefaultSwitchControl = < T extends FieldValues, - P extends FieldPath = FieldPath + P extends FieldPath = FieldPath, >( - props: DefaultSwitchControlProps + props: DefaultSwitchControlProps, ) => { const { t } = useTranslation("common"); diff --git a/js/apps/admin-ui/src/components/alert/Alerts.tsx b/js/apps/admin-ui/src/components/alert/Alerts.tsx index 57647bcccf05..c44101d2fe9c 100644 --- a/js/apps/admin-ui/src/components/alert/Alerts.tsx +++ b/js/apps/admin-ui/src/components/alert/Alerts.tsx @@ -13,7 +13,7 @@ const ALERT_TIMEOUT = 8000; export type AddAlertFunction = ( message: string, variant?: AlertVariant, - description?: string + description?: string, ) => void; export type AddErrorFunction = (message: string, error: unknown) => void; @@ -25,7 +25,7 @@ export type AlertProps = { export const AlertContext = createNamedContext( "AlertContext", - undefined + undefined, ); export const useAlerts = () => useRequiredContext(AlertContext); @@ -57,7 +57,7 @@ export const AlertProvider = ({ children }: PropsWithChildren) => { setAlerts((alerts) => [alert, ...alerts]); setTimeout(() => removeAlert(alert.id), ALERT_TIMEOUT); }, - [] + [], ); const addError = useCallback((message, error) => { @@ -65,7 +65,7 @@ export const AlertProvider = ({ children }: PropsWithChildren) => { t(message, { error: getErrorMessage(error), }), - AlertVariant.danger + AlertVariant.danger, ); }, []); diff --git a/js/apps/admin-ui/src/components/bread-crumb/GroupBreadCrumbs.tsx b/js/apps/admin-ui/src/components/bread-crumb/GroupBreadCrumbs.tsx index de145dc2d824..f46ebc6aa40e 100644 --- a/js/apps/admin-ui/src/components/bread-crumb/GroupBreadCrumbs.tsx +++ b/js/apps/admin-ui/src/components/bread-crumb/GroupBreadCrumbs.tsx @@ -33,7 +33,7 @@ export const GroupBreadCrumbs = () => { remove(group)} > diff --git a/js/apps/admin-ui/src/components/bread-crumb/PageBreadCrumbs.tsx b/js/apps/admin-ui/src/components/bread-crumb/PageBreadCrumbs.tsx index 0b6330bdaffa..8317cca0d933 100644 --- a/js/apps/admin-ui/src/components/bread-crumb/PageBreadCrumbs.tsx +++ b/js/apps/admin-ui/src/components/bread-crumb/PageBreadCrumbs.tsx @@ -27,7 +27,7 @@ export const PageBreadCrumbs = () => { disableDefaults: true, excludePaths: ["/", `/${realm}`], }), - elementText + elementText, ); return crumbs.length > 1 ? ( diff --git a/js/apps/admin-ui/src/components/client-scope/ClientScopeTypes.tsx b/js/apps/admin-ui/src/components/client-scope/ClientScopeTypes.tsx index ed9cb8635e40..552fd43ed075 100644 --- a/js/apps/admin-ui/src/components/client-scope/ClientScopeTypes.tsx +++ b/js/apps/admin-ui/src/components/client-scope/ClientScopeTypes.tsx @@ -32,7 +32,7 @@ export const allClientScopeTypes = Object.keys({ export const clientScopeTypesSelectOptions = ( t: TFunction, - scopeTypes: string[] | undefined = clientScopeTypes + scopeTypes: string[] | undefined = clientScopeTypes, ) => scopeTypes.map((type) => ( @@ -42,7 +42,7 @@ export const clientScopeTypesSelectOptions = ( export const clientScopeTypesDropdown = ( t: TFunction, - onClick: (scope: ClientScopeType) => void + onClick: (scope: ClientScopeType) => void, ) => clientScopeTypes.map((type) => ( onClick(type as ClientScopeType)}> @@ -76,7 +76,7 @@ export const CellDropdown = ({ selections={[type]} onSelect={(_, value) => { onSelect( - all ? (value as ClientScopeType) : (value as AllClientScopeType) + all ? (value as ClientScopeType) : (value as AllClientScopeType), ); setOpen(false); }} @@ -84,7 +84,7 @@ export const CellDropdown = ({ > {clientScopeTypesSelectOptions( t, - all ? allClientScopeTypes : clientScopeTypes + all ? allClientScopeTypes : clientScopeTypes, )} ); @@ -96,7 +96,7 @@ export type ClientScopeDefaultOptionalType = ClientScopeRepresentation & { export const changeScope = async ( clientScope: ClientScopeDefaultOptionalType, - changeTo: AllClientScopeType + changeTo: AllClientScopeType, ) => { await removeScope(clientScope); await addScope(clientScope, changeTo); @@ -108,7 +108,7 @@ const castAdminClient = () => }; export const removeScope = async ( - clientScope: ClientScopeDefaultOptionalType + clientScope: ClientScopeDefaultOptionalType, ) => { if (clientScope.type !== AllClientScopes.none) await castAdminClient()[ @@ -122,7 +122,7 @@ export const removeScope = async ( const addScope = async ( clientScope: ClientScopeDefaultOptionalType, - type: AllClientScopeType + type: AllClientScopeType, ) => { if (type !== AllClientScopes.none) await castAdminClient()[ @@ -136,7 +136,7 @@ export const changeClientScope = async ( clientId: string, clientScope: ClientScopeRepresentation, type: AllClientScopeType, - changeTo: ClientScopeType + changeTo: ClientScopeType, ) => { if (type !== "none") { await removeClientScope(clientId, clientScope, type); @@ -147,7 +147,7 @@ export const changeClientScope = async ( export const removeClientScope = async ( clientId: string, clientScope: ClientScopeRepresentation, - type: ClientScope + type: ClientScope, ) => { const methodName = `del${toUpperCase(type)}ClientScope` as const; @@ -160,7 +160,7 @@ export const removeClientScope = async ( export const addClientScope = async ( clientId: string, clientScope: ClientScopeRepresentation, - type: ClientScopeType + type: ClientScopeType, ) => { const methodName = `add${toUpperCase(type)}ClientScope` as const; diff --git a/js/apps/admin-ui/src/components/client/ClientSelect.tsx b/js/apps/admin-ui/src/components/client/ClientSelect.tsx index fa00abb713f8..c27ea505abca 100644 --- a/js/apps/admin-ui/src/components/client/ClientSelect.tsx +++ b/js/apps/admin-ui/src/components/client/ClientSelect.tsx @@ -51,7 +51,7 @@ export const ClientSelect = ({ return adminClient.clients.find(params); }, (clients) => setClients(clients), - [search] + [search], ); const convert = (clients: ClientRepresentation[]) => [ diff --git a/js/apps/admin-ui/src/components/confirm-dialog/ConfirmDialog.tsx b/js/apps/admin-ui/src/components/confirm-dialog/ConfirmDialog.tsx index 0bce2f47b7c5..e574267ab1be 100644 --- a/js/apps/admin-ui/src/components/confirm-dialog/ConfirmDialog.tsx +++ b/js/apps/admin-ui/src/components/confirm-dialog/ConfirmDialog.tsx @@ -8,7 +8,7 @@ import { import { useTranslation } from "react-i18next"; export const useConfirmDialog = ( - props: ConfirmDialogProps + props: ConfirmDialogProps, ): [() => void, () => ReactElement] => { const [show, setShow] = useState(false); diff --git a/js/apps/admin-ui/src/components/download-dialog/DownloadDialog.tsx b/js/apps/admin-ui/src/components/download-dialog/DownloadDialog.tsx index 3e3a62d96048..e2b5b32ed3ac 100644 --- a/js/apps/admin-ui/src/components/download-dialog/DownloadDialog.tsx +++ b/js/apps/admin-ui/src/components/download-dialog/DownloadDialog.tsx @@ -42,20 +42,20 @@ export const DownloadDialog = ({ const configFormats = serverInfo.clientInstallations![protocol]; const [selected, setSelected] = useState( - configFormats[configFormats.length - 1].id + configFormats[configFormats.length - 1].id, ); const [snippet, setSnippet] = useState(); const [openType, setOpenType] = useState(false); const selectedConfig = useMemo( () => configFormats.find((config) => config.id === selected) ?? null, - [selected] + [selected], ); const sanitizeSnippet = (snippet: string) => snippet.replace( /.*<\/PrivateKeyPem>/gs, - `${t("clients:privateKeyMask")}` + `${t("clients:privateKeyMask")}`, ); useFetch( @@ -63,14 +63,14 @@ export const DownloadDialog = ({ if (selectedConfig?.mediaType === "application/zip") { const response = await fetch( `${addTrailingSlash( - adminClient.baseUrl + adminClient.baseUrl, )}admin/realms/${realm}/clients/${id}/installation/providers/${selected}`, { method: "GET", headers: getAuthorizationHeaders( - await adminClient.getAccessToken() + await adminClient.getAccessToken(), ), - } + }, ); return response.arrayBuffer(); @@ -87,7 +87,7 @@ export const DownloadDialog = ({ } }, (snippet) => setSnippet(snippet), - [id, selected] + [id, selected], ); // Clear snippet when selected config changes, this prevents old snippets from being displayed during fetch. @@ -100,7 +100,7 @@ export const DownloadDialog = ({ onConfirm={() => { saveAs( new Blob([snippet!], { type: selectedConfig?.mediaType }), - selectedConfig?.filename + selectedConfig?.filename, ); }} open={open} diff --git a/js/apps/admin-ui/src/components/dynamic/MultivaluedListComponent.tsx b/js/apps/admin-ui/src/components/dynamic/MultivaluedListComponent.tsx index f692fc20e31f..8574d746ad8d 100644 --- a/js/apps/admin-ui/src/components/dynamic/MultivaluedListComponent.tsx +++ b/js/apps/admin-ui/src/components/dynamic/MultivaluedListComponent.tsx @@ -54,7 +54,7 @@ export const MultiValuedListComponent = ({ const option = v.toString(); if (field.value.includes(option)) { field.onChange( - field.value.filter((item: string) => item !== option) + field.value.filter((item: string) => item !== option), ); } else { field.onChange([...field.value, option]); diff --git a/js/apps/admin-ui/src/components/form/FormAccess.tsx b/js/apps/admin-ui/src/components/form/FormAccess.tsx index 03b28075b5a3..2cf5b2c61fee 100644 --- a/js/apps/admin-ui/src/components/form/FormAccess.tsx +++ b/js/apps/admin-ui/src/components/form/FormAccess.tsx @@ -64,7 +64,7 @@ export const FormAccess = ({ const recursiveCloneChildren = ( children: ReactNode, - newProps: any + newProps: any, ): ReactNode => { return Children.map(children, (child) => { if (!isValidElement(child)) { @@ -87,7 +87,7 @@ export const FormAccess = ({ } const children = recursiveCloneChildren( element.props.children, - newProps + newProps, ); if (child.type === TextArea) { return cloneElement(child, { @@ -106,7 +106,7 @@ export const FormAccess = ({ child.type === Stack || child.type === StackItem ? { children } - : { ...newProps, children } + : { ...newProps, children }, ); } return child; diff --git a/js/apps/admin-ui/src/components/group/GroupPickerDialog.tsx b/js/apps/admin-ui/src/components/group/GroupPickerDialog.tsx index edfe42210bc4..5b9e9f96934b 100644 --- a/js/apps/admin-ui/src/components/group/GroupPickerDialog.tsx +++ b/js/apps/admin-ui/src/components/group/GroupPickerDialog.tsx @@ -80,8 +80,8 @@ export const GroupPickerDialog = ({ first: `${first}`, max: `${max + 1}`, }, - isSearching ? null : { search: filter } - ) + isSearching ? null : { search: filter }, + ), ); } else if (!navigation.map(({ id }) => id).includes(groupId)) { group = await adminClient.groups.findOne({ id: groupId }); @@ -118,7 +118,7 @@ export const GroupPickerDialog = ({ } setCount(count); }, - [groupId, filter, first, max] + [groupId, filter, first, max], ); const isRowDisabled = (row?: GroupRepresentation) => { @@ -149,7 +149,7 @@ export const GroupPickerDialog = ({ ? selectedRows : navigation.length ? [currentGroup()] - : undefined + : undefined, ); }} isDisabled={type === "selectMany" && selectedRows.length === 0} diff --git a/js/apps/admin-ui/src/components/json-file-upload/patternfly/FileUpload.tsx b/js/apps/admin-ui/src/components/json-file-upload/patternfly/FileUpload.tsx index 75d6d9cd58ec..c61ef49fe4f6 100644 --- a/js/apps/admin-ui/src/components/json-file-upload/patternfly/FileUpload.tsx +++ b/js/apps/admin-ui/src/components/json-file-upload/patternfly/FileUpload.tsx @@ -37,7 +37,7 @@ export interface FileUploadProps event: | React.MouseEvent // Clear button was clicked | React.ChangeEvent // User typed in the TextArea - | DropEvent + | DropEvent, ) => void; /** Change event emitted from the hidden \ field associated with the component */ onFileInputChange?: (event: DropEvent, file: File) => void; @@ -154,7 +154,7 @@ export const FileUpload = ({ }; const onClearButtonClick = ( - event: React.MouseEvent + event: React.MouseEvent, ) => { onChange?.("", "", event); onClearClick?.(event); diff --git a/js/apps/admin-ui/src/components/json-file-upload/patternfly/FileUploadField.tsx b/js/apps/admin-ui/src/components/json-file-upload/patternfly/FileUploadField.tsx index 164da7dd1ccb..04a596015f4f 100644 --- a/js/apps/admin-ui/src/components/json-file-upload/patternfly/FileUploadField.tsx +++ b/js/apps/admin-ui/src/components/json-file-upload/patternfly/FileUploadField.tsx @@ -32,7 +32,7 @@ export interface FileUploadFieldProps filename: string, event: | React.ChangeEvent // User typed in the TextArea - | React.MouseEvent // User clicked Clear button + | React.MouseEvent, // User clicked Clear button ) => void; /** Additional classes added to the FileUploadField container element. */ className?: string; @@ -75,15 +75,15 @@ export interface FileUploadFieldProps /** A callback for when the Browse button is clicked. */ onBrowseButtonClick?: ( - event: React.MouseEvent + event: React.MouseEvent, ) => void; /** A callback for when the Clear button is clicked. */ onClearButtonClick?: ( - event: React.MouseEvent + event: React.MouseEvent, ) => void; /** A callback from when the text area is clicked. Can also be set via the onClick property of FileUpload. */ onTextAreaClick?: ( - event: React.MouseEvent + event: React.MouseEvent, ) => void; /** Flag to show if a file is being dragged over the field */ isDragActive?: boolean; @@ -126,7 +126,7 @@ export const FileUploadField = ({ }: PropsWithChildren) => { const onTextAreaChange = ( newValue: string, - event: React.ChangeEvent + event: React.ChangeEvent, ) => { onChange?.(newValue, filename, event); onTextChange?.(newValue); @@ -137,7 +137,7 @@ export const FileUploadField = ({ styles.fileUpload, isDragActive && styles.modifiers.dragHover, isLoading && styles.modifiers.loading, - className + className, )} ref={containerRef} {...props} diff --git a/js/apps/admin-ui/src/components/key-value-form/KeySelect.tsx b/js/apps/admin-ui/src/components/key-value-form/KeySelect.tsx index 6edf8ee12a7d..6d2eb6676797 100644 --- a/js/apps/admin-ui/src/components/key-value-form/KeySelect.tsx +++ b/js/apps/admin-ui/src/components/key-value-form/KeySelect.tsx @@ -15,7 +15,7 @@ export const KeySelect = ({ selectItems, ...rest }: KeySelectProp) => { const [open, toggle] = useToggle(); const { field } = useController(rest); const [custom, setCustom] = useState( - !selectItems.map(({ key }) => key).includes(field.value) + !selectItems.map(({ key }) => key).includes(field.value), ); return ( diff --git a/js/apps/admin-ui/src/components/key-value-form/ValueSelect.tsx b/js/apps/admin-ui/src/components/key-value-form/ValueSelect.tsx index 5efd270a0097..8c753f59330a 100644 --- a/js/apps/admin-ui/src/components/key-value-form/ValueSelect.tsx +++ b/js/apps/admin-ui/src/components/key-value-form/ValueSelect.tsx @@ -22,7 +22,7 @@ export const ValueSelect = ({ const defaultItem = useMemo( () => selectItems.find((v) => v.key === keyValue), - [selectItems, keyValue] + [selectItems, keyValue], ); return defaultItem?.values ? ( diff --git a/js/apps/admin-ui/src/components/key-value-form/key-value-convert.ts b/js/apps/admin-ui/src/components/key-value-form/key-value-convert.ts index ef0181fd7956..45f06537565f 100644 --- a/js/apps/admin-ui/src/components/key-value-form/key-value-convert.ts +++ b/js/apps/admin-ui/src/components/key-value-form/key-value-convert.ts @@ -19,7 +19,7 @@ export function keyValueToArray(attributeArray: KeyValueType[] = []) { export function arrayToKeyValue(attributes: Record = {}) { const result = Object.entries(attributes).flatMap(([key, value]) => - value.map((value) => ({ key, value })) + value.map((value) => ({ key, value })), ); return result as PathValue>; diff --git a/js/apps/admin-ui/src/components/password-input/PasswordInput.tsx b/js/apps/admin-ui/src/components/password-input/PasswordInput.tsx index 57010e5f7122..c75055677228 100644 --- a/js/apps/admin-ui/src/components/password-input/PasswordInput.tsx +++ b/js/apps/admin-ui/src/components/password-input/PasswordInput.tsx @@ -42,6 +42,6 @@ const PasswordInputBase = ({ export const PasswordInput = forwardRef( (props: PasswordInputProps, ref: Ref) => ( } /> - ) + ), ); PasswordInput.displayName = "PasswordInput"; diff --git a/js/apps/admin-ui/src/components/permission-tab/PermissionTab.tsx b/js/apps/admin-ui/src/components/permission-tab/PermissionTab.tsx index 56162050fea3..35d3c0075276 100644 --- a/js/apps/admin-ui/src/components/permission-tab/PermissionTab.tsx +++ b/js/apps/admin-ui/src/components/permission-tab/PermissionTab.tsx @@ -57,7 +57,7 @@ export const PermissionsTab = ({ id, type }: PermissionsTabProps) => { case "clients": return adminClient.clients.updateFineGrainPermission( { id: id! }, - { enabled } + { enabled }, ); case "users": return adminClient.realms.updateUsersManagementPermissions({ @@ -71,7 +71,7 @@ export const PermissionsTab = ({ id, type }: PermissionsTabProps) => { case "identityProviders": return adminClient.identityProviders.updatePermission( { alias: id! }, - { enabled } + { enabled }, ); } }; @@ -106,7 +106,7 @@ export const PermissionsTab = ({ id, type }: PermissionsTabProps) => { setRealmId(clients[0]?.id!); setPermission(permission); }, - [id] + [id], ); const [toggleDisableDialog, DisableConfirm] = useConfirmDialog({ @@ -197,7 +197,7 @@ export const PermissionsTab = ({ id, type }: PermissionsTabProps) => { {localeSort( Object.entries(permission.scopePermissions || {}), - ([name]) => name + ([name]) => name, ).map(([name, id]) => ( @@ -227,7 +227,7 @@ export const PermissionsTab = ({ id, type }: PermissionsTabProps) => { id: realmId, permissionType: "scope", permissionId: id, - }) + }), ); }, }, diff --git a/js/apps/admin-ui/src/components/realm-selector/RealmSelector.tsx b/js/apps/admin-ui/src/components/realm-selector/RealmSelector.tsx index 8de0c0383025..e56907e31ed6 100644 --- a/js/apps/admin-ui/src/components/realm-selector/RealmSelector.tsx +++ b/js/apps/admin-ui/src/components/realm-selector/RealmSelector.tsx @@ -97,13 +97,13 @@ export const RealmSelector = () => { .concat( realms .filter( - (r) => !recentRealms.includes(r.realm!) || r.realm === realm + (r) => !recentRealms.includes(r.realm!) || r.realm === realm, ) .map((r) => { return { name: r.realm!, used: false }; - }) + }), ), - [recentRealms, realm, realms] + [recentRealms, realm, realms], ); const filteredItems = useMemo( @@ -111,9 +111,9 @@ export const RealmSelector = () => { search.trim() === "" ? all : all.filter((r) => - r.name.toLowerCase().includes(search.toLowerCase()) + r.name.toLowerCase().includes(search.toLowerCase()), ), - [search, all] + [search, all], ); return realms.length > 5 ? ( diff --git a/js/apps/admin-ui/src/components/role-mapping/AddRoleMappingModal.tsx b/js/apps/admin-ui/src/components/role-mapping/AddRoleMappingModal.tsx index ed9f830b48f0..b39cda7f4b80 100644 --- a/js/apps/admin-ui/src/components/role-mapping/AddRoleMappingModal.tsx +++ b/js/apps/admin-ui/src/components/role-mapping/AddRoleMappingModal.tsx @@ -47,7 +47,7 @@ export const AddRoleMappingModal = ({ const [searchToggle, setSearchToggle] = useState(false); const [filterType, setFilterType] = useState( - canViewRealmRoles ? "roles" : "clients" + canViewRealmRoles ? "roles" : "clients", ); const [selectedRows, setSelectedRows] = useState([]); const [key, setKey] = useState(0); @@ -59,7 +59,7 @@ export const AddRoleMappingModal = ({ const loader = async ( first?: number, max?: number, - search?: string + search?: string, ): Promise => { const params: Record = { first: first!, @@ -83,7 +83,7 @@ export const AddRoleMappingModal = ({ const clientRolesLoader = async ( first?: number, max?: number, - search?: string + search?: string, ): Promise => { const roles = await getAvailableClientRoles({ id, @@ -99,7 +99,7 @@ export const AddRoleMappingModal = ({ role: { id: e.id, name: e.role, description: e.description }, id: e.id, })), - ({ client: { clientId }, role: { name } }) => `${clientId}${name}` + ({ client: { clientId }, role: { name } }) => `${clientId}${name}`, ); }; diff --git a/js/apps/admin-ui/src/components/role-mapping/RoleMapping.tsx b/js/apps/admin-ui/src/components/role-mapping/RoleMapping.tsx index bc40116c31e2..a626d79077f1 100644 --- a/js/apps/admin-ui/src/components/role-mapping/RoleMapping.tsx +++ b/js/apps/admin-ui/src/components/role-mapping/RoleMapping.tsx @@ -38,7 +38,7 @@ export type Row = { export const mapRoles = ( assignedRoles: Row[], effectiveRoles: Row[], - hide: boolean + hide: boolean, ) => [ ...(hide ? assignedRoles.map((row) => ({ @@ -126,7 +126,7 @@ export const RoleMapping = ({ client.mappings.map((role: RoleRepresentation) => ({ client: { clientId: client.client, ...client }, role, - })) + })), ) .flat(); @@ -134,7 +134,7 @@ export const RoleMapping = ({ ...mapRoles( [...realmRolesMapping, ...clientMapping], [...effectiveClientRoles, ...effectiveRoles], - hide + hide, ), ]; }; diff --git a/js/apps/admin-ui/src/components/role-mapping/queries.ts b/js/apps/admin-ui/src/components/role-mapping/queries.ts index 51670a9a6751..7c3472344397 100644 --- a/js/apps/admin-ui/src/components/role-mapping/queries.ts +++ b/js/apps/admin-ui/src/components/role-mapping/queries.ts @@ -122,13 +122,13 @@ export const deleteMapping = (type: ResourcesKey, id: string, rows: Row[]) => client: row.client?.id, roles: [role], }, - [role] + [role], ); }); export const getMapping = async ( type: ResourcesKey, - id: string + id: string, ): Promise => { const query = mapping[type]!.listEffective[0]; const result = applyQuery(type, query, { id }); @@ -146,7 +146,7 @@ export const getMapping = async ( role.containerId = client?.clientId; return { ...client, mappings: [role] }; - }) + }), ); return { @@ -157,7 +157,7 @@ export const getMapping = async ( export const getEffectiveRoles = async ( type: ResourcesKey, - id: string + id: string, ): Promise => { const query = mapping[type]!.listEffective[1]; if (type !== "roles") { @@ -169,14 +169,14 @@ export const getEffectiveRoles = async ( const parentRoles = await Promise.all( roles .filter((r) => r.composite) - .map((r) => applyQuery(type, query, { id: r.id })) + .map((r) => applyQuery(type, query, { id: r.id })), ); return [...roles, ...parentRoles.flat()].map((role) => ({ role })); }; export const getAvailableRoles = async ( type: ResourcesKey, - params: Record + params: Record, ): Promise => { const query = mapping[type]!.listAvailable[1]; return (await applyQuery(type, query, params)).map((role) => ({ diff --git a/js/apps/admin-ui/src/components/role-mapping/resource.ts b/js/apps/admin-ui/src/components/role-mapping/resource.ts index 43bd0b74a903..e0dc11d6f3f5 100644 --- a/js/apps/admin-ui/src/components/role-mapping/resource.ts +++ b/js/apps/admin-ui/src/components/role-mapping/resource.ts @@ -42,12 +42,12 @@ const fetchEndpoint = async ({ }); export const getAvailableClientRoles = ( - query: PaginatingQuery + query: PaginatingQuery, ): Promise => fetchEndpoint({ ...query, endpoint: "available-roles" }); export const getEffectiveClientRoles = ( - query: EffectiveClientRolesQuery + query: EffectiveClientRolesQuery, ): Promise => fetchEndpoint({ ...query, endpoint: "effective-roles" }); diff --git a/js/apps/admin-ui/src/components/roles-list/RolesList.tsx b/js/apps/admin-ui/src/components/roles-list/RolesList.tsx index 7c676e0ec435..3bf8f6c54b7d 100644 --- a/js/apps/admin-ui/src/components/roles-list/RolesList.tsx +++ b/js/apps/admin-ui/src/components/roles-list/RolesList.tsx @@ -58,7 +58,7 @@ type RolesListProps = { loader?: ( first?: number, max?: number, - search?: string + search?: string, ) => Promise; }; @@ -84,7 +84,7 @@ export const RolesList = ({ (realm) => { setRealm(realm); }, - [] + [], ); const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ @@ -150,7 +150,7 @@ export const RolesList = ({ ) { addAlert( t("defaultRoleDeleteError"), - AlertVariant.danger + AlertVariant.danger, ); } else toggleDeleteDialog(); }, diff --git a/js/apps/admin-ui/src/components/routable-tabs/RoutableTabs.tsx b/js/apps/admin-ui/src/components/routable-tabs/RoutableTabs.tsx index 708b9b5a5b45..e84dcd3712e0 100644 --- a/js/apps/admin-ui/src/components/routable-tabs/RoutableTabs.tsx +++ b/js/apps/admin-ui/src/components/routable-tabs/RoutableTabs.tsx @@ -40,7 +40,7 @@ export const RoutableTabs = ({ // Determine if there is an exact match. const exactMatch = eventKeys.find( - (eventKey) => eventKey === decodeURI(pathname) + (eventKey) => eventKey === decodeURI(pathname), ); // Determine which event keys at least partially match the current path, then sort them so the nearest match ends up on top. diff --git a/js/apps/admin-ui/src/components/scroll-form/ScrollForm.tsx b/js/apps/admin-ui/src/components/scroll-form/ScrollForm.tsx index 7a73bf4c48db..40ad0e99a0f4 100644 --- a/js/apps/admin-ui/src/components/scroll-form/ScrollForm.tsx +++ b/js/apps/admin-ui/src/components/scroll-form/ScrollForm.tsx @@ -38,7 +38,7 @@ export const ScrollForm = ({ const { t } = useTranslation("common"); const shownSections = useMemo( () => sections.filter(({ isHidden }) => !isHidden), - [sections] + [sections], ); return ( diff --git a/js/apps/admin-ui/src/components/table-toolbar/KeycloakDataTable.tsx b/js/apps/admin-ui/src/components/table-toolbar/KeycloakDataTable.tsx index eecf3c934516..e4bf02bd1446 100644 --- a/js/apps/admin-ui/src/components/table-toolbar/KeycloakDataTable.tsx +++ b/js/apps/admin-ui/src/components/table-toolbar/KeycloakDataTable.tsx @@ -128,7 +128,7 @@ export type Action = IAction & { export type LoaderFunction = ( first?: number, max?: number, - search?: string + search?: string, ) => Promise; export type DataListProps = Omit< @@ -210,7 +210,7 @@ export function KeycloakDataTable({ const [defaultPageSize, setDefaultPageSize] = useStoredState( localStorage, "pageSize", - 10 + 10, ); const [max, setMax] = useState(defaultPageSize); @@ -272,7 +272,7 @@ export function KeycloakDataTable({ return getNodeText( isValidElement((node as TitleCell).title) ? (node as TitleCell).title.props - : Object.values(node) + : Object.values(node), ); } return ""; @@ -287,11 +287,13 @@ export function KeycloakDataTable({ row.cells.some( (cell) => cell && - getNodeText(cell).toLowerCase().includes(search.toLowerCase()) - ) + getNodeText(cell) + .toLowerCase() + .includes(search.toLowerCase()), + ), ) .slice(first, first + max + 1), - [search, first, max] + [search, first, max], ); useEffect(() => { @@ -301,7 +303,7 @@ export function KeycloakDataTable({ .item(0); if (checkboxes) { const checkAllCheckbox = checkboxes.children!.item( - 0 + 0, )! as HTMLInputElement; checkAllCheckbox.indeterminate = selected.length > 0 && @@ -338,7 +340,13 @@ export function KeycloakDataTable({ setRows(result); setLoading(false); }, - [key, first, max, search, typeof loader !== "function" ? loader : undefined] + [ + key, + first, + max, + search, + typeof loader !== "function" ? loader : undefined, + ], ); const convertAction = () => @@ -347,7 +355,7 @@ export function KeycloakDataTable({ delete action.onRowClick; action.onClick = async (_, rowIndex) => { const result = await actions[index].onRowClick!( - (filteredData || rows)![rowIndex].data + (filteredData || rows)![rowIndex].data, ); if (result) { if (!isPaginated) { @@ -366,7 +374,7 @@ export function KeycloakDataTable({ data!.map((row) => { (row as Row).selected = isSelected; return row; - }) + }), ); } else { (data![rowIndex] as Row).selected = isSelected; @@ -378,7 +386,7 @@ export function KeycloakDataTable({ const difference = differenceBy( selected, data!.map((row) => row.data), - "id" + "id", ); // Selected rows are any rows previously selected from a different page, plus current page selections diff --git a/js/apps/admin-ui/src/components/time-selector/TimeSelector.tsx b/js/apps/admin-ui/src/components/time-selector/TimeSelector.tsx index 4457311f5768..1344f09bbf44 100644 --- a/js/apps/admin-ui/src/components/time-selector/TimeSelector.tsx +++ b/js/apps/admin-ui/src/components/time-selector/TimeSelector.tsx @@ -36,7 +36,7 @@ export const getTimeUnit = (value: number | undefined = 0) => value % time.multiplier === 0 && v.multiplier < time.multiplier ? time : v, - allTimes[0] + allTimes[0], ); export const toHumanFormat = (value: number, locale: string) => { @@ -62,7 +62,7 @@ export const TimeSelector = ({ const defaultMultiplier = useMemo( () => allTimes.find((time) => time.unit === units[0])?.multiplier, - [units] + [units], ); const [timeValue, setTimeValue] = useState<"" | number>(""); @@ -71,7 +71,7 @@ export const TimeSelector = ({ const times = useMemo(() => { const filteredUnits = units.map( - (unit) => allTimes.find((time) => time.unit === unit)! + (unit) => allTimes.find((time) => time.unit === unit)!, ); if ( !filteredUnits.every((u) => u.multiplier === multiplier) && @@ -96,7 +96,7 @@ export const TimeSelector = ({ const updateTimeout = ( timeout: "" | number, - times: number | undefined = multiplier + times: number | undefined = multiplier, ) => { if (timeout !== "") { onChange?.(timeout * (times || 1)); diff --git a/js/apps/admin-ui/src/components/users/UserDataTable.tsx b/js/apps/admin-ui/src/components/users/UserDataTable.tsx index e52c3cb174bd..b073b38ef7e6 100644 --- a/js/apps/admin-ui/src/components/users/UserDataTable.tsx +++ b/js/apps/admin-ui/src/components/users/UserDataTable.tsx @@ -82,18 +82,18 @@ export function UserDataTable() { return [[], {}, {}] as [ ComponentRepresentation[], RealmRepresentation | undefined, - UserProfileConfig + UserProfileConfig, ]; } }, ([storageProviders, realm, profile]) => { setUserStorage( - storageProviders.filter((p) => p.config?.enabled[0] === "true") + storageProviders.filter((p) => p.config?.enabled[0] === "true"), ); setRealm(realm); setProfile(profile); }, - [] + [], ); const UserDetailLink = (user: UserRepresentation) => ( @@ -215,7 +215,7 @@ export function UserDataTable() { const clearAllFilters = () => { const filtered = [...activeFilters].filter( - (chip) => chip.name !== chip.name + (chip) => chip.name !== chip.name, ); setActiveFilters(filtered); setSearchUser(""); @@ -251,7 +251,7 @@ export function UserDataTable() { event.stopPropagation(); const filtered = [...activeFilters].filter( - (chip) => chip.name !== entry.name + (chip) => chip.name !== entry.name, ); const attributes = createQueryString(filtered); diff --git a/js/apps/admin-ui/src/components/users/UserDataTableAttributeSearchForm.tsx b/js/apps/admin-ui/src/components/users/UserDataTableAttributeSearchForm.tsx index 7c079331104c..8a2a9cf82e37 100644 --- a/js/apps/admin-ui/src/components/users/UserDataTableAttributeSearchForm.tsx +++ b/js/apps/admin-ui/src/components/users/UserDataTableAttributeSearchForm.tsx @@ -120,7 +120,7 @@ export function UserDataTableAttributeSearchForm({ const clearActiveFilters = () => { const filtered = [...activeFilters].filter( - (chip) => chip.name !== chip.name + (chip) => chip.name !== chip.name, ); setActiveFilters(filtered); }; diff --git a/js/apps/admin-ui/src/components/users/UserSelect.tsx b/js/apps/admin-ui/src/components/users/UserSelect.tsx index f1e9a5f5289c..c8f9c67d968f 100644 --- a/js/apps/admin-ui/src/components/users/UserSelect.tsx +++ b/js/apps/admin-ui/src/components/users/UserSelect.tsx @@ -55,13 +55,13 @@ export const UserSelect = ({ if (values?.length && !search) { return Promise.all( - values.map((id: string) => adminClient.users.findOne({ id })) + values.map((id: string) => adminClient.users.findOne({ id })), ); } return adminClient.users.find(params); }, setUsers, - [search] + [search], ); const convert = (clients: (UserRepresentation | undefined)[]) => @@ -117,7 +117,7 @@ export const UserSelect = ({ : field.onChange([option]); } else { const changedValue = field.value.find( - (v: string) => v === option + (v: string) => v === option, ) ? field.value.filter((v: string) => v !== option) : [...field.value, option]; diff --git a/js/apps/admin-ui/src/context/RealmsContext.tsx b/js/apps/admin-ui/src/context/RealmsContext.tsx index d3055b0bd62a..d83b30e1a388 100644 --- a/js/apps/admin-ui/src/context/RealmsContext.tsx +++ b/js/apps/admin-ui/src/context/RealmsContext.tsx @@ -17,7 +17,7 @@ type RealmsContextProps = { export const RealmsContext = createNamedContext( "RealmsContext", - undefined + undefined, ); export const RealmsProvider = ({ children }: PropsWithChildren) => { @@ -46,7 +46,7 @@ export const RealmsProvider = ({ children }: PropsWithChildren) => { } }, (realms) => updateRealms(realms), - [refreshCount] + [refreshCount], ); const refresh = useCallback(async () => { @@ -58,7 +58,7 @@ export const RealmsProvider = ({ children }: PropsWithChildren) => { const value = useMemo( () => ({ realms, refresh }), - [realms, refresh] + [realms, refresh], ); return ( diff --git a/js/apps/admin-ui/src/context/RecentRealms.tsx b/js/apps/admin-ui/src/context/RecentRealms.tsx index e25f9d9eec76..d6262704e883 100644 --- a/js/apps/admin-ui/src/context/RecentRealms.tsx +++ b/js/apps/admin-ui/src/context/RecentRealms.tsx @@ -13,7 +13,7 @@ const MAX_REALMS = 4; export const RecentRealmsContext = createNamedContext( "RecentRealmsContext", - undefined + undefined, ); export const RecentRealmsProvider = ({ children }: PropsWithChildren) => { @@ -22,12 +22,12 @@ export const RecentRealmsProvider = ({ children }: PropsWithChildren) => { const [storedRealms, setStoredRealms] = useStoredState( localStorage, "recentRealms", - [realm] + [realm], ); const recentRealms = useMemo( () => filterRealmNames(realms, storedRealms), - [realms, storedRealms] + [realms, storedRealms], ); useEffect(() => { diff --git a/js/apps/admin-ui/src/context/access/Access.tsx b/js/apps/admin-ui/src/context/access/Access.tsx index 655efc9f6c3f..81a3d0f39b1c 100644 --- a/js/apps/admin-ui/src/context/access/Access.tsx +++ b/js/apps/admin-ui/src/context/access/Access.tsx @@ -11,7 +11,7 @@ type AccessContextProps = { export const AccessContext = createNamedContext( "AccessContext", - undefined + undefined, ); export const useAccess = () => useRequiredContext(AccessContext); diff --git a/js/apps/admin-ui/src/context/auth/admin-ui-endpoint.ts b/js/apps/admin-ui/src/context/auth/admin-ui-endpoint.ts index 8a5907577542..e3e3e91c639d 100644 --- a/js/apps/admin-ui/src/context/auth/admin-ui-endpoint.ts +++ b/js/apps/admin-ui/src/context/auth/admin-ui-endpoint.ts @@ -4,7 +4,7 @@ import { joinPath } from "../../utils/joinPath"; export async function fetchAdminUI( endpoint: string, - query?: Record + query?: Record, ): Promise { const accessToken = await adminClient.getAccessToken(); const baseUrl = adminClient.baseUrl; @@ -15,7 +15,7 @@ export async function fetchAdminUI( { method: "GET", headers: getAuthorizationHeaders(accessToken), - } + }, ); return await response.json(); diff --git a/js/apps/admin-ui/src/context/realm-context/RealmContext.tsx b/js/apps/admin-ui/src/context/realm-context/RealmContext.tsx index a5400acb5b11..91378910354a 100644 --- a/js/apps/admin-ui/src/context/realm-context/RealmContext.tsx +++ b/js/apps/admin-ui/src/context/realm-context/RealmContext.tsx @@ -12,7 +12,7 @@ type RealmContextType = { export const RealmContext = createNamedContext( "RealmContext", - undefined + undefined, ); export const RealmContextProvider = ({ children }: PropsWithChildren) => { @@ -24,7 +24,7 @@ export const RealmContextProvider = ({ children }: PropsWithChildren) => { const realmParam = routeMatch?.params.realm; const realm = useMemo( () => realmParam ?? environment.loginRealm, - [realmParam] + [realmParam], ); // Configure admin client to use selected realm when it changes. diff --git a/js/apps/admin-ui/src/context/whoami/WhoAmI.tsx b/js/apps/admin-ui/src/context/whoami/WhoAmI.tsx index 6eedb790eeaa..fe91c62320aa 100644 --- a/js/apps/admin-ui/src/context/whoami/WhoAmI.tsx +++ b/js/apps/admin-ui/src/context/whoami/WhoAmI.tsx @@ -59,7 +59,7 @@ type WhoAmIProps = { export const WhoAmIContext = createNamedContext( "WhoAmIContext", - undefined + undefined, ); export const useWhoAmI = () => useRequiredContext(WhoAmIContext); @@ -74,7 +74,7 @@ export const WhoAmIContextProvider = ({ children }: PropsWithChildren) => { const whoAmI = new WhoAmI(me); setWhoAmI(whoAmI); }, - [key] + [key], ); return ( diff --git a/js/apps/admin-ui/src/dashboard/Dashboard.tsx b/js/apps/admin-ui/src/dashboard/Dashboard.tsx index 8f8e9e992a71..829c9e2fdf88 100644 --- a/js/apps/admin-ui/src/dashboard/Dashboard.tsx +++ b/js/apps/admin-ui/src/dashboard/Dashboard.tsx @@ -89,9 +89,9 @@ const Dashboard = () => { () => localeSort( serverInfo.profileInfo?.disabledFeatures ?? [], - (item) => item + (item) => item, ), - [serverInfo.profileInfo] + [serverInfo.profileInfo], ); const enabledFeatures = useMemo( @@ -100,15 +100,15 @@ const Dashboard = () => { filter( union( serverInfo.profileInfo?.experimentalFeatures, - serverInfo.profileInfo?.previewFeatures + serverInfo.profileInfo?.previewFeatures, ), (feature) => { return !isDeprecatedFeature(feature); - } + }, ), - (item) => item + (item) => item, ), - [serverInfo.profileInfo] + [serverInfo.profileInfo], ); const useTab = (tab: DashboardTab) => @@ -116,7 +116,7 @@ const Dashboard = () => { toDashboard({ realm, tab, - }) + }), ); const infoTab = useTab("info"); diff --git a/js/apps/admin-ui/src/dashboard/ProviderInfo.tsx b/js/apps/admin-ui/src/dashboard/ProviderInfo.tsx index 3ae1436747a0..10db08ace9e7 100644 --- a/js/apps/admin-ui/src/dashboard/ProviderInfo.tsx +++ b/js/apps/admin-ui/src/dashboard/ProviderInfo.tsx @@ -22,9 +22,9 @@ export const ProviderInfo = () => { const providerInfo = useMemo( () => Object.entries(serverInfo.providers || []).filter(([key]) => - key.includes(filter) + key.includes(filter), ), - [filter] + [filter], ); const toggleOpen = (option: string) => { @@ -78,14 +78,14 @@ export const ProviderInfo = () => { {key} {value} - ) + ), )} ) : null} - ) + ), )} diff --git a/js/apps/admin-ui/src/events/AdminEvents.tsx b/js/apps/admin-ui/src/events/AdminEvents.tsx index 037160b54603..1174af99a3b1 100644 --- a/js/apps/admin-ui/src/events/AdminEvents.tsx +++ b/js/apps/admin-ui/src/events/AdminEvents.tsx @@ -166,7 +166,7 @@ export const AdminEvents = () => { function removeFilterValue( key: keyof AdminEventSearchForm, - valueToRemove: string + valueToRemove: string, ) { const formValues = getValues(); const fieldValue = formValues[key]; @@ -181,7 +181,7 @@ export const AdminEvents = () => { function commitFilters() { const newFilters: Partial = pickBy( getValues(), - (value) => value !== "" || (Array.isArray(value) && value.length > 0) + (value) => value !== "" || (Array.isArray(value) && value.length > 0), ); setActiveFilters(newFilters); @@ -263,7 +263,7 @@ export const AdminEvents = () => { onClick={(resource) => { resource.stopPropagation(); field.onChange( - field.value.filter((val) => val !== chip) + field.value.filter((val) => val !== chip), ); }} > @@ -324,7 +324,7 @@ export const AdminEvents = () => { onClick={(operation) => { operation.stopPropagation(); field.onChange( - field.value.filter((val) => val !== chip) + field.value.filter((val) => val !== chip), ); }} > @@ -465,7 +465,7 @@ export const AdminEvents = () => { {Object.entries(activeFilters).map((filter) => { const [key, value] = filter as [ keyof AdminEventSearchForm, - string | string[] + string | string[], ]; return ( @@ -510,7 +510,7 @@ export const AdminEvents = () => { representationEvent?.representation ? prettyPrintJSON(JSON.parse(representationEvent.representation)) : "", - [representationEvent?.representation] + [representationEvent?.representation], ); return ( diff --git a/js/apps/admin-ui/src/events/EventsSection.tsx b/js/apps/admin-ui/src/events/EventsSection.tsx index 424235eb5820..5e912d6bcf58 100644 --- a/js/apps/admin-ui/src/events/EventsSection.tsx +++ b/js/apps/admin-ui/src/events/EventsSection.tsx @@ -161,7 +161,7 @@ export default function EventsSection() { useFetch( () => adminClient.realms.getConfigEvents({ realm }), (events) => setEvents(events), - [] + [], ); function loader(first?: number, max?: number) { @@ -199,7 +199,7 @@ export default function EventsSection() { function removeFilterValue( key: keyof UserEventSearchForm, - valueToRemove: EventType + valueToRemove: EventType, ) { const formValues = getValues(); const fieldValue = formValues[key]; @@ -214,7 +214,7 @@ export default function EventsSection() { function commitFilters() { const newFilters: Partial = pickBy( getValues(), - (value) => value !== "" || (Array.isArray(value) && value.length > 0) + (value) => value !== "" || (Array.isArray(value) && value.length > 0), ); setActiveFilters(newFilters); @@ -308,7 +308,7 @@ export default function EventsSection() { onClick={(event) => { event.stopPropagation(); field.onChange( - field.value.filter((val) => val !== chip) + field.value.filter((val) => val !== chip), ); }} > @@ -418,7 +418,7 @@ export default function EventsSection() { {Object.entries(activeFilters).map((filter) => { const [key, value] = filter as [ keyof UserEventSearchForm, - string | EventType[] + string | EventType[], ]; return ( diff --git a/js/apps/admin-ui/src/events/ResourceLinks.tsx b/js/apps/admin-ui/src/events/ResourceLinks.tsx index 5c903c0c3bb3..75147751f589 100644 --- a/js/apps/admin-ui/src/events/ResourceLinks.tsx +++ b/js/apps/admin-ui/src/events/ResourceLinks.tsx @@ -50,7 +50,7 @@ const isLinkable = (event: AdminEventRepresentation) => { }; const idRegex = new RegExp( - /([0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12})/ + /([0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12})/, ); const createLink = (realm: string, event: AdminEventRepresentation) => { @@ -114,5 +114,5 @@ export const ResourceLink = ({ event }: ResourceLinkProps) => { }; export const CellResourceLinkRenderer = ( - adminEvent: AdminEventRepresentation + adminEvent: AdminEventRepresentation, ) => ; diff --git a/js/apps/admin-ui/src/groups/GroupRoleMapping.tsx b/js/apps/admin-ui/src/groups/GroupRoleMapping.tsx index 551b9ac147a4..034ffbc700e5 100644 --- a/js/apps/admin-ui/src/groups/GroupRoleMapping.tsx +++ b/js/apps/admin-ui/src/groups/GroupRoleMapping.tsx @@ -33,8 +33,8 @@ export const GroupRoleMapping = ({ id, name }: GroupRoleMappingProps) => { id, clientUniqueId: row.client!.id!, roles: [row.role as RoleMappingPayload], - }) - ) + }), + ), ); addAlert(t("roleMappingUpdatedSuccess"), AlertVariant.success); } catch (error) { diff --git a/js/apps/admin-ui/src/groups/GroupTable.tsx b/js/apps/admin-ui/src/groups/GroupTable.tsx index c6e3cc3346ad..e600f34e1e69 100644 --- a/js/apps/admin-ui/src/groups/GroupTable.tsx +++ b/js/apps/admin-ui/src/groups/GroupTable.tsx @@ -221,7 +221,7 @@ export const GroupTable = ({ hasIcon={true} message={t(`noGroupsInThis${id ? "SubGroup" : "Realm"}`)} instructions={t( - `noGroupsInThis${id ? "SubGroup" : "Realm"}Instructions` + `noGroupsInThis${id ? "SubGroup" : "Realm"}Instructions`, )} primaryActionText={t("createGroup")} onPrimaryAction={toggleCreateOpen} diff --git a/js/apps/admin-ui/src/groups/GroupsModal.tsx b/js/apps/admin-ui/src/groups/GroupsModal.tsx index 55bde6f55403..d4bf11e5894d 100644 --- a/js/apps/admin-ui/src/groups/GroupsModal.tsx +++ b/js/apps/admin-ui/src/groups/GroupsModal.tsx @@ -48,7 +48,7 @@ export const GroupsModal = ({ } else if (rename) { await adminClient.groups.update( { id }, - { ...rename, name: group.name } + { ...rename, name: group.name }, ); } else { await (group.id @@ -60,7 +60,7 @@ export const GroupsModal = ({ handleModalToggle(); addAlert( t(rename ? "groupUpdated" : "groupCreated"), - AlertVariant.success + AlertVariant.success, ); } catch (error) { addError("groups:couldNotCreateGroup", error); diff --git a/js/apps/admin-ui/src/groups/GroupsSection.tsx b/js/apps/admin-ui/src/groups/GroupsSection.tsx index 2767304ec203..19b35ec3292b 100644 --- a/js/apps/admin-ui/src/groups/GroupsSection.tsx +++ b/js/apps/admin-ui/src/groups/GroupsSection.tsx @@ -97,7 +97,7 @@ export default function GroupsSection() { (groups: GroupRepresentation[]) => { if (groups.length) setSubGroups(groups); }, - [id] + [id], ); return ( diff --git a/js/apps/admin-ui/src/groups/Members.tsx b/js/apps/admin-ui/src/groups/Members.tsx index 947484bebd61..1d8d1bd8d65d 100644 --- a/js/apps/admin-ui/src/groups/Members.tsx +++ b/js/apps/admin-ui/src/groups/Members.tsx @@ -99,14 +99,14 @@ export const Members = () => { const subGroups = getSubGroups(currentGroup()?.subGroups!); for (const group of subGroups) { members = members.concat( - await adminClient.groups.listMembers({ id: group.id! }) + await adminClient.groups.listMembers({ id: group.id! }), ); } members = uniqBy(members, (member) => member.username); } const memberOfPromises = await Promise.all( - members.map((member) => getMembership(member.id!)) + members.map((member) => getMembership(member.id!)), ); return members.map((member: UserRepresentation, i) => { return { ...member, membership: memberOfPromises[i] }; @@ -174,13 +174,13 @@ export const Members = () => { adminClient.users.delFromGroup({ id: user.id!, groupId: id!, - }) - ) + }), + ), ); setIsKebabOpen(false); addAlert( t("usersLeft", { count: selectedRows.length }), - AlertVariant.success + AlertVariant.success, ); } catch (error) { addError("groups:usersLeftError", error); @@ -210,7 +210,7 @@ export const Members = () => { }); addAlert( t("usersLeft", { count: 1 }), - AlertVariant.success + AlertVariant.success, ); } catch (error) { addError("groups:usersLeftError", error); diff --git a/js/apps/admin-ui/src/groups/MembersModal.tsx b/js/apps/admin-ui/src/groups/MembersModal.tsx index 767f43463ceb..517255fb1ff0 100644 --- a/js/apps/admin-ui/src/groups/MembersModal.tsx +++ b/js/apps/admin-ui/src/groups/MembersModal.tsx @@ -57,13 +57,13 @@ export const MemberModal = ({ groupId, onClose }: MemberModalProps) => { try { await Promise.all( selectedRows.map((user) => - adminClient.users.addToGroup({ id: user.id!, groupId }) - ) + adminClient.users.addToGroup({ id: user.id!, groupId }), + ), ); onClose(); addAlert( t("usersAdded", { count: selectedRows.length }), - AlertVariant.success + AlertVariant.success, ); } catch (error) { addError("groups:usersAddedError", error); diff --git a/js/apps/admin-ui/src/groups/SubGroupsContext.tsx b/js/apps/admin-ui/src/groups/SubGroupsContext.tsx index fe023155ccbd..b1b35fd243bf 100644 --- a/js/apps/admin-ui/src/groups/SubGroupsContext.tsx +++ b/js/apps/admin-ui/src/groups/SubGroupsContext.tsx @@ -12,7 +12,7 @@ type SubGroupsProps = { const SubGroupsContext = createNamedContext( "SubGroupsContext", - undefined + undefined, ); export const SubGroups = ({ children }: PropsWithChildren) => { @@ -21,7 +21,7 @@ export const SubGroups = ({ children }: PropsWithChildren) => { const clear = () => setSubGroups([]); const remove = (group: GroupRepresentation) => setSubGroups( - subGroups.slice(0, subGroups.findIndex((g) => g.id === group.id) + 1) + subGroups.slice(0, subGroups.findIndex((g) => g.id === group.id) + 1), ); const currentGroup = () => subGroups[subGroups.length - 1]; return ( diff --git a/js/apps/admin-ui/src/groups/components/CheckableTreeView.tsx b/js/apps/admin-ui/src/groups/components/CheckableTreeView.tsx index 6d68bf5d47f2..3e0b401d02bc 100644 --- a/js/apps/admin-ui/src/groups/components/CheckableTreeView.tsx +++ b/js/apps/admin-ui/src/groups/components/CheckableTreeView.tsx @@ -45,11 +45,11 @@ export const CheckableTreeView = ({ checkedItems: checked ? prevState.checkedItems.concat( flatCheckedItems.filter( - (item) => !prevState.checkedItems.some((i) => i.id === item.id) - ) + (item) => !prevState.checkedItems.some((i) => i.id === item.id), + ), ) : prevState.checkedItems.filter( - (item) => !flatCheckedItems.some((i) => i.id === item.id) + (item) => !flatCheckedItems.some((i) => i.id === item.id), ), }; }); diff --git a/js/apps/admin-ui/src/groups/components/GroupTree.tsx b/js/apps/admin-ui/src/groups/components/GroupTree.tsx index 37d6557d7662..1e1fe28b60a7 100644 --- a/js/apps/admin-ui/src/groups/components/GroupTree.tsx +++ b/js/apps/admin-ui/src/groups/components/GroupTree.tsx @@ -139,7 +139,7 @@ export const GroupTree = ({ const mapGroup = ( group: GroupRepresentation, parents: GroupRepresentation[], - refresh: () => void + refresh: () => void, ): TreeViewDataItem => { const groups = [...parents, group]; return { @@ -170,8 +170,8 @@ export const GroupTree = ({ max: `${max + 1}`, exact: `${exact}`, }, - search === "" ? null : { search } - ) + search === "" ? null : { search }, + ), ); const count = (await adminClient.groups.count({ search, top: true })) .count; @@ -182,14 +182,14 @@ export const GroupTree = ({ setData(groups.map((g) => mapGroup(g, [], refresh))); setCount(count); }, - [key, first, max, search, exact] + [key, first, max, search, exact], ); const findGroup = ( groups: GroupRepresentation[], id: string, path: GroupRepresentation[], - found: GroupRepresentation[] + found: GroupRepresentation[], ) => { return groups.map((group) => { if (found.length > 0) return; diff --git a/js/apps/admin-ui/src/groups/components/MoveDialog.tsx b/js/apps/admin-ui/src/groups/components/MoveDialog.tsx index ef927c068611..4aaa9fb6c273 100644 --- a/js/apps/admin-ui/src/groups/components/MoveDialog.tsx +++ b/js/apps/admin-ui/src/groups/components/MoveDialog.tsx @@ -18,7 +18,7 @@ const moveToRoot = (source: GroupRepresentation) => const moveToGroup = async ( source: GroupRepresentation, - dest: GroupRepresentation + dest: GroupRepresentation, ) => adminClient.groups.updateChildGroup({ id: dest.id! }, source); export const MoveDialog = ({ source, onClose, refresh }: MoveDialogProps) => { diff --git a/js/apps/admin-ui/src/i18n/OverridesBackend.ts b/js/apps/admin-ui/src/i18n/OverridesBackend.ts index 7a28873a8250..4fac8e068861 100644 --- a/js/apps/admin-ui/src/i18n/OverridesBackend.ts +++ b/js/apps/admin-ui/src/i18n/OverridesBackend.ts @@ -14,7 +14,7 @@ export class OverridesBackend extends HttpBackend { url: string, callback: ReadCallback, languages?: string | string[], - namespaces?: string | string[] + namespaces?: string | string[], ) { try { const [data, overrides] = await Promise.all([ @@ -38,7 +38,7 @@ export class OverridesBackend extends HttpBackend { #applyOverrides( namespace: string, data: ResourceKey, - overrides: ParsedOverrides + overrides: ParsedOverrides, ) { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (typeof data === "string" || !overrides[namespace]) { @@ -135,7 +135,7 @@ export class OverridesBackend extends HttpBackend { #loadUrlPromisified( url: string, languages?: string | string[], - namespaces?: string | string[] + namespaces?: string | string[], ) { return new Promise((resolve, reject) => { const callback: ReadCallback = (error, data) => { @@ -147,8 +147,8 @@ export class OverridesBackend extends HttpBackend { return reject( new Error( "Unable to load URL, data returned is of an unsupported type.", - { cause: error } - ) + { cause: error }, + ), ); } diff --git a/js/apps/admin-ui/src/identity-providers/IdentityProvidersSection.tsx b/js/apps/admin-ui/src/identity-providers/IdentityProvidersSection.tsx index c879175c0d73..245d5b674c5c 100644 --- a/js/apps/admin-ui/src/identity-providers/IdentityProvidersSection.tsx +++ b/js/apps/admin-ui/src/identity-providers/IdentityProvidersSection.tsx @@ -75,7 +75,7 @@ export default function IdentityProvidersSection() { const { t } = useTranslation("identity-providers"); const identityProviders = groupBy( useServerInfo().identityProviders, - "groupName" + "groupName", ); const { realm } = useRealm(); const navigate = useNavigate(); @@ -101,7 +101,7 @@ export default function IdentityProvidersSection() { (providers) => { setProviders(sortBy(providers, ["config.guiOrder", "alias"])); }, - [key] + [key], ); const navigateToCreate = (providerId: string) => @@ -109,7 +109,7 @@ export default function IdentityProvidersSection() { toIdentityProviderCreate({ realm, providerId, - }) + }), ); const identityProviderOptions = () => diff --git a/js/apps/admin-ui/src/identity-providers/ManageOrderDialog.tsx b/js/apps/admin-ui/src/identity-providers/ManageOrderDialog.tsx index 263140221125..173549bc7639 100644 --- a/js/apps/admin-ui/src/identity-providers/ManageOrderDialog.tsx +++ b/js/apps/admin-ui/src/identity-providers/ManageOrderDialog.tsx @@ -36,7 +36,7 @@ export const ManageOrderDialog = ({ const [alias, setAlias] = useState(""); const [liveText, setLiveText] = useState(""); const [order, setOrder] = useState( - providers.map((provider) => provider.alias!) + providers.map((provider) => provider.alias!), ); const onDragStart = (id: string) => { diff --git a/js/apps/admin-ui/src/identity-providers/add/AddIdentityProvider.tsx b/js/apps/admin-ui/src/identity-providers/add/AddIdentityProvider.tsx index 94c0d0df41cd..632113a7d0b3 100644 --- a/js/apps/admin-ui/src/identity-providers/add/AddIdentityProvider.tsx +++ b/js/apps/admin-ui/src/identity-providers/add/AddIdentityProvider.tsx @@ -37,7 +37,7 @@ export default function AddIdentityProvider() { for (const namespace of namespaces) { const social = serverInfo.componentTypes?.[namespace]?.find( - ({ id }) => id === providerId + ({ id }) => id === providerId, ); if (social) { @@ -69,7 +69,7 @@ export default function AddIdentityProvider() { providerId, alias: providerId, tab: "settings", - }) + }), ); } catch (error) { addError("identity-providers:createError", error); diff --git a/js/apps/admin-ui/src/identity-providers/add/AddMapper.tsx b/js/apps/admin-ui/src/identity-providers/add/AddMapper.tsx index eefddb5e8fc6..05d06ae7e37c 100644 --- a/js/apps/admin-ui/src/identity-providers/add/AddMapper.tsx +++ b/js/apps/admin-ui/src/identity-providers/add/AddMapper.tsx @@ -88,7 +88,7 @@ export default function AddMapper() { id: id!, alias: alias!, }, - { ...identityProviderMapper } + { ...identityProviderMapper }, ); addAlert(t("mapperSaveSuccess"), AlertVariant.success); } catch (error) { @@ -108,7 +108,7 @@ export default function AddMapper() { alias, providerId: providerId, id: createdMapper.id, - }) + }), ); } catch (error) { addError(t("mapperCreateError"), error); @@ -131,7 +131,7 @@ export default function AddMapper() { }); addAlert(t("deleteMapperSuccess"), AlertVariant.success); navigate( - toIdentityProvider({ providerId, alias, tab: "mappers", realm }) + toIdentityProvider({ providerId, alias, tab: "mappers", realm }), ); } catch (error) { addError("identity-providers:deleteErrorError", error); @@ -149,7 +149,7 @@ export default function AddMapper() { const mappers = localeSort(Object.values(mapperTypes), mapByKey("name")); if (mapper) { setCurrentMapper( - mappers.find(({ id }) => id === mapper.identityProviderMapper) + mappers.find(({ id }) => id === mapper.identityProviderMapper), ); setupForm(mapper); } else { @@ -158,7 +158,7 @@ export default function AddMapper() { setMapperTypes(mappers); }, - [] + [], ); const setupForm = (mapper: IdentityProviderMapperRepresentation) => { diff --git a/js/apps/admin-ui/src/identity-providers/add/AddMapperForm.tsx b/js/apps/admin-ui/src/identity-providers/add/AddMapperForm.tsx index f41a86bb2b14..387a14a39b5f 100644 --- a/js/apps/admin-ui/src/identity-providers/add/AddMapperForm.tsx +++ b/js/apps/admin-ui/src/identity-providers/add/AddMapperForm.tsx @@ -20,7 +20,7 @@ type AddMapperFormProps = { mapperType: IdentityProviderMapperTypeRepresentation; id: string; updateMapperType: ( - mapperType: IdentityProviderMapperTypeRepresentation + mapperType: IdentityProviderMapperTypeRepresentation, ) => void; form: UseFormReturn; }; diff --git a/js/apps/admin-ui/src/identity-providers/add/AddOpenIdConnect.tsx b/js/apps/admin-ui/src/identity-providers/add/AddOpenIdConnect.tsx index 38eab9f671f6..faaefdd4132f 100644 --- a/js/apps/admin-ui/src/identity-providers/add/AddOpenIdConnect.tsx +++ b/js/apps/admin-ui/src/identity-providers/add/AddOpenIdConnect.tsx @@ -56,7 +56,7 @@ export default function AddOpenIdConnect() { providerId: id, alias: provider.alias!, tab: "settings", - }) + }), ); } catch (error) { addError("identity-providers:createError", error); @@ -67,7 +67,7 @@ export default function AddOpenIdConnect() { <> diff --git a/js/apps/admin-ui/src/identity-providers/add/AddSamlConnect.tsx b/js/apps/admin-ui/src/identity-providers/add/AddSamlConnect.tsx index 64fd95168dcd..999d835ccd44 100644 --- a/js/apps/admin-ui/src/identity-providers/add/AddSamlConnect.tsx +++ b/js/apps/admin-ui/src/identity-providers/add/AddSamlConnect.tsx @@ -53,7 +53,7 @@ export default function AddSamlConnect() { providerId: id, alias: provider.alias!, tab: "settings", - }) + }), ); } catch (error: any) { addError("identity-providers:createError", error); diff --git a/js/apps/admin-ui/src/identity-providers/add/AdvancedSettings.tsx b/js/apps/admin-ui/src/identity-providers/add/AdvancedSettings.tsx index a0d010eac454..9dce00dc4845 100644 --- a/js/apps/admin-ui/src/identity-providers/add/AdvancedSettings.tsx +++ b/js/apps/admin-ui/src/identity-providers/add/AdvancedSettings.tsx @@ -36,7 +36,7 @@ const LoginFlow = ({ () => adminClient.authenticationManagement.getFlows(), (flows) => setFlows(flows.filter((flow) => flow.providerId === "basic-flow")), - [] + [], ); return ( diff --git a/js/apps/admin-ui/src/identity-providers/add/DescriptorSettings.tsx b/js/apps/admin-ui/src/identity-providers/add/DescriptorSettings.tsx index 1845ce6b0495..5be7a94ef638 100644 --- a/js/apps/admin-ui/src/identity-providers/add/DescriptorSettings.tsx +++ b/js/apps/admin-ui/src/identity-providers/add/DescriptorSettings.tsx @@ -525,7 +525,7 @@ const Fields = ({ readOnly }: DescriptorSettingsProps) => { onMinus={() => field.onChange(v - 1)} onChange={(event) => { const value = Number( - (event.target as HTMLInputElement).value + (event.target as HTMLInputElement).value, ); field.onChange(value < 0 ? 0 : value); }} @@ -564,7 +564,7 @@ const Fields = ({ readOnly }: DescriptorSettingsProps) => { onMinus={() => field.onChange(v - 1)} onChange={(event) => { const value = Number( - (event.target as HTMLInputElement).value + (event.target as HTMLInputElement).value, ); field.onChange(value < 0 ? 0 : value); }} diff --git a/js/apps/admin-ui/src/identity-providers/add/DetailSettings.tsx b/js/apps/admin-ui/src/identity-providers/add/DetailSettings.tsx index 144e7bd77827..ef84fe5feb86 100644 --- a/js/apps/admin-ui/src/identity-providers/add/DetailSettings.tsx +++ b/js/apps/admin-ui/src/identity-providers/add/DetailSettings.tsx @@ -88,7 +88,7 @@ const Header = ({ onChange, value, save, toggleDeleteDialog }: HeaderProps) => { } setProvider(fetchedProvider); }, - [] + [], ); const [toggleDisableDialog, DisableConfirm] = useConfirmDialog({ @@ -110,7 +110,7 @@ const Header = ({ onChange, value, save, toggleDeleteDialog }: HeaderProps) => { ? provider.displayName ? provider.displayName : provider.providerId! - : "" + : "", )} divider={false} dropdownItems={[ @@ -169,7 +169,7 @@ export default function DetailSettings() { serverInfo.componentTypes?.[ "org.keycloak.broker.social.SocialIdentityProvider" ]?.find((p) => p.id === providerId), - [serverInfo, providerId] + [serverInfo, providerId], ); const { addAlert, addError } = useAlerts(); @@ -191,18 +191,18 @@ export default function DetailSettings() { if (fetchedProvider.config!.authnContextClassRefs) { form.setValue( "config.authnContextClassRefs", - JSON.parse(fetchedProvider.config?.authnContextClassRefs) + JSON.parse(fetchedProvider.config?.authnContextClassRefs), ); } if (fetchedProvider.config!.authnContextDeclRefs) { form.setValue( "config.authnContextDeclRefs", - JSON.parse(fetchedProvider.config?.authnContextDeclRefs) + JSON.parse(fetchedProvider.config?.authnContextDeclRefs), ); } }, - [] + [], ); const toTab = (tab: IdentityProviderTab) => @@ -223,11 +223,11 @@ export default function DetailSettings() { const p = savedProvider || getValues(); if (p.config?.authnContextClassRefs) p.config.authnContextClassRefs = JSON.stringify( - p.config.authnContextClassRefs + p.config.authnContextClassRefs, ); if (p.config?.authnContextDeclRefs) p.config.authnContextDeclRefs = JSON.stringify( - p.config.authnContextDeclRefs + p.config.authnContextDeclRefs, ); try { @@ -238,7 +238,7 @@ export default function DetailSettings() { config: { ...provider?.config, ...p.config }, alias, providerId, - } + }, ); addAlert(t("updateSuccess"), AlertVariant.success); } catch (error) { @@ -278,7 +278,7 @@ export default function DetailSettings() { addAlert(t("deleteMapperSuccess"), AlertVariant.success); refresh(); navigate( - toIdentityProvider({ providerId, alias, tab: "mappers", realm }) + toIdentityProvider({ providerId, alias, tab: "mappers", realm }), ); } catch (error) { addError("identity-providers:deleteErrorError", error); @@ -302,7 +302,7 @@ export default function DetailSettings() { const components = loaderMappers.map((loaderMapper) => { const mapperType = Object.values(loaderMapperTypes).find( (loaderMapperType) => - loaderMapper.identityProviderMapper! === loaderMapperType.id! + loaderMapper.identityProviderMapper! === loaderMapperType.id!, ); const result: IdPWithMapperAttributes = { @@ -434,7 +434,7 @@ export default function DetailSettings() { alias: alias!, providerId: provider.providerId!, tab: "mappers", - }) + }), ) } /> diff --git a/js/apps/admin-ui/src/identity-providers/add/ExtendedNonDiscoverySettings.tsx b/js/apps/admin-ui/src/identity-providers/add/ExtendedNonDiscoverySettings.tsx index a076c3f3d316..e5d778fe0348 100644 --- a/js/apps/admin-ui/src/identity-providers/add/ExtendedNonDiscoverySettings.tsx +++ b/js/apps/admin-ui/src/identity-providers/add/ExtendedNonDiscoverySettings.tsx @@ -111,7 +111,7 @@ export const ExtendedNonDiscoverySettings = () => { onMinus={() => field.onChange(v - 1)} onChange={(event) => { const value = Number( - (event.target as HTMLInputElement).value + (event.target as HTMLInputElement).value, ); field.onChange(value < 0 ? 0 : value); }} diff --git a/js/apps/admin-ui/src/identity-providers/add/OpenIdConnectSettings.tsx b/js/apps/admin-ui/src/identity-providers/add/OpenIdConnectSettings.tsx index 8b480e97b327..c470d8a635bb 100644 --- a/js/apps/admin-ui/src/identity-providers/add/OpenIdConnectSettings.tsx +++ b/js/apps/admin-ui/src/identity-providers/add/OpenIdConnectSettings.tsx @@ -35,7 +35,7 @@ export const OpenIdConnectSettings = () => { try { const result = await adminClient.identityProviders.importFromUrl( - formData + formData, ); setupForm(result); } catch (error) { diff --git a/js/apps/admin-ui/src/identity-providers/add/SamlConnectSettings.tsx b/js/apps/admin-ui/src/identity-providers/add/SamlConnectSettings.tsx index 72950f02e045..6c4b4c8bcd04 100644 --- a/js/apps/admin-ui/src/identity-providers/add/SamlConnectSettings.tsx +++ b/js/apps/admin-ui/src/identity-providers/add/SamlConnectSettings.tsx @@ -33,7 +33,7 @@ export const SamlConnectSettings = () => { const setupForm = (result: IdentityProviderRepresentation) => { Object.entries(result).map(([key, value]) => - setValue(`config.${key}`, value) + setValue(`config.${key}`, value), ); }; @@ -49,13 +49,13 @@ export const SamlConnectSettings = () => { try { const response = await fetch( `${addTrailingSlash( - adminClient.baseUrl + adminClient.baseUrl, )}admin/realms/${realm}/identity-provider/import-config`, { method: "POST", body: formData, headers: getAuthorizationHeaders(await adminClient.getAccessToken()), - } + }, ); if (response.ok) { const result = await response.json(); diff --git a/js/apps/admin-ui/src/identity-providers/component/DiscoveryEndpointField.tsx b/js/apps/admin-ui/src/identity-providers/component/DiscoveryEndpointField.tsx index d60f28cd821a..3e4e582c2def 100644 --- a/js/apps/admin-ui/src/identity-providers/component/DiscoveryEndpointField.tsx +++ b/js/apps/admin-ui/src/identity-providers/component/DiscoveryEndpointField.tsx @@ -69,7 +69,7 @@ export const DiscoveryEndpointField = ({ <> {discovery && ( { const { realm } = useRealm(); const callbackUrl = `${addTrailingSlash( - adminClient.baseUrl + adminClient.baseUrl, )}realms/${realm}/broker`; return ( diff --git a/js/apps/admin-ui/src/identity-providers/routes/AddMapper.tsx b/js/apps/admin-ui/src/identity-providers/routes/AddMapper.tsx index 97b0bbf723aa..0148b3cbfcec 100644 --- a/js/apps/admin-ui/src/identity-providers/routes/AddMapper.tsx +++ b/js/apps/admin-ui/src/identity-providers/routes/AddMapper.tsx @@ -22,7 +22,7 @@ export const IdentityProviderAddMapperRoute: AppRouteObject = { }; export const toIdentityProviderAddMapper = ( - params: IdentityProviderAddMapperParams + params: IdentityProviderAddMapperParams, ): Partial => ({ pathname: generatePath(IdentityProviderAddMapperRoute.path, params), }); diff --git a/js/apps/admin-ui/src/identity-providers/routes/EditMapper.tsx b/js/apps/admin-ui/src/identity-providers/routes/EditMapper.tsx index e45eca956b1c..8c84d83f3214 100644 --- a/js/apps/admin-ui/src/identity-providers/routes/EditMapper.tsx +++ b/js/apps/admin-ui/src/identity-providers/routes/EditMapper.tsx @@ -22,7 +22,7 @@ export const IdentityProviderEditMapperRoute: AppRouteObject = { }; export const toIdentityProviderEditMapper = ( - params: IdentityProviderEditMapperParams + params: IdentityProviderEditMapperParams, ): Partial => ({ pathname: generatePath(IdentityProviderEditMapperRoute.path, params), }); diff --git a/js/apps/admin-ui/src/identity-providers/routes/IdentityProvider.tsx b/js/apps/admin-ui/src/identity-providers/routes/IdentityProvider.tsx index c62fee593d44..ab7b5b466e66 100644 --- a/js/apps/admin-ui/src/identity-providers/routes/IdentityProvider.tsx +++ b/js/apps/admin-ui/src/identity-providers/routes/IdentityProvider.tsx @@ -24,7 +24,7 @@ export const IdentityProviderRoute: AppRouteObject = { }; export const toIdentityProvider = ( - params: IdentityProviderParams + params: IdentityProviderParams, ): Partial => ({ pathname: generatePath(IdentityProviderRoute.path, params), }); diff --git a/js/apps/admin-ui/src/identity-providers/routes/IdentityProviderCreate.tsx b/js/apps/admin-ui/src/identity-providers/routes/IdentityProviderCreate.tsx index 3a1b1a20f051..13ae9e06a335 100644 --- a/js/apps/admin-ui/src/identity-providers/routes/IdentityProviderCreate.tsx +++ b/js/apps/admin-ui/src/identity-providers/routes/IdentityProviderCreate.tsx @@ -20,7 +20,7 @@ export const IdentityProviderCreateRoute: AppRouteObject = { }; export const toIdentityProviderCreate = ( - params: IdentityProviderCreateParams + params: IdentityProviderCreateParams, ): Partial => ({ pathname: generatePath(IdentityProviderCreateRoute.path, params), }); diff --git a/js/apps/admin-ui/src/identity-providers/routes/IdentityProviderKeycloakOidc.tsx b/js/apps/admin-ui/src/identity-providers/routes/IdentityProviderKeycloakOidc.tsx index 7217deea4c53..e0255511982c 100644 --- a/js/apps/admin-ui/src/identity-providers/routes/IdentityProviderKeycloakOidc.tsx +++ b/js/apps/admin-ui/src/identity-providers/routes/IdentityProviderKeycloakOidc.tsx @@ -17,7 +17,7 @@ export const IdentityProviderKeycloakOidcRoute: AppRouteObject = { }; export const toIdentityProviderKeycloakOidc = ( - params: IdentityProviderKeycloakOidcParams + params: IdentityProviderKeycloakOidcParams, ): Partial => ({ pathname: generatePath(IdentityProviderKeycloakOidcRoute.path, params), }); diff --git a/js/apps/admin-ui/src/identity-providers/routes/IdentityProviderOidc.tsx b/js/apps/admin-ui/src/identity-providers/routes/IdentityProviderOidc.tsx index a8b2a60b0824..df0609928302 100644 --- a/js/apps/admin-ui/src/identity-providers/routes/IdentityProviderOidc.tsx +++ b/js/apps/admin-ui/src/identity-providers/routes/IdentityProviderOidc.tsx @@ -17,7 +17,7 @@ export const IdentityProviderOidcRoute: AppRouteObject = { }; export const toIdentityProviderOidc = ( - params: IdentityProviderOidcParams + params: IdentityProviderOidcParams, ): Partial => ({ pathname: generatePath(IdentityProviderOidcRoute.path, params), }); diff --git a/js/apps/admin-ui/src/identity-providers/routes/IdentityProviderSaml.tsx b/js/apps/admin-ui/src/identity-providers/routes/IdentityProviderSaml.tsx index c2f11a6a1ead..4d685cae5430 100644 --- a/js/apps/admin-ui/src/identity-providers/routes/IdentityProviderSaml.tsx +++ b/js/apps/admin-ui/src/identity-providers/routes/IdentityProviderSaml.tsx @@ -17,7 +17,7 @@ export const IdentityProviderSamlRoute: AppRouteObject = { }; export const toIdentityProviderSaml = ( - params: IdentityProviderSamlParams + params: IdentityProviderSamlParams, ): Partial => ({ pathname: generatePath(IdentityProviderSamlRoute.path, params), }); diff --git a/js/apps/admin-ui/src/identity-providers/routes/IdentityProviders.tsx b/js/apps/admin-ui/src/identity-providers/routes/IdentityProviders.tsx index a74f373c5a8e..3645968b8525 100644 --- a/js/apps/admin-ui/src/identity-providers/routes/IdentityProviders.tsx +++ b/js/apps/admin-ui/src/identity-providers/routes/IdentityProviders.tsx @@ -6,7 +6,7 @@ import type { AppRouteObject } from "../../routes"; export type IdentityProvidersParams = { realm: string }; const IdentityProvidersSection = lazy( - () => import("../IdentityProvidersSection") + () => import("../IdentityProvidersSection"), ); export const IdentityProvidersRoute: AppRouteObject = { @@ -19,7 +19,7 @@ export const IdentityProvidersRoute: AppRouteObject = { }; export const toIdentityProviders = ( - params: IdentityProvidersParams + params: IdentityProvidersParams, ): Partial => ({ pathname: generatePath(IdentityProvidersRoute.path, params), }); diff --git a/js/apps/admin-ui/src/main.tsx b/js/apps/admin-ui/src/main.tsx index 385c9238e19b..e9fee24f5544 100644 --- a/js/apps/admin-ui/src/main.tsx +++ b/js/apps/admin-ui/src/main.tsx @@ -22,5 +22,5 @@ render( , - container + container, ); diff --git a/js/apps/admin-ui/src/realm-roles/RealmRoleTabs.tsx b/js/apps/admin-ui/src/realm-roles/RealmRoleTabs.tsx index 48740d9503dc..829bcf2104a2 100644 --- a/js/apps/admin-ui/src/realm-roles/RealmRoleTabs.tsx +++ b/js/apps/admin-ui/src/realm-roles/RealmRoleTabs.tsx @@ -114,7 +114,7 @@ export default function RealmRoleTabs() { setAttributes(convertedRole.attributes); setRealm(realm); }, - [key] + [key], ); const onSubmit: SubmitHandler = async (formValues) => { @@ -130,7 +130,7 @@ export default function RealmRoleTabs() { } else { await adminClient.clients.updateRole( { id: clientId, roleName: formValues.name! }, - roleRepresentation + roleRepresentation, ); } @@ -271,7 +271,7 @@ export default function RealmRoleTabs() { addAlert( t("compositeRoleOff"), AlertVariant.success, - t("compositesRemovedAlertDescription") + t("compositesRemovedAlertDescription"), ); navigate(toTab("details")); refresh(); @@ -289,7 +289,7 @@ export default function RealmRoleTabs() { try { await adminClient.roles.createComposite( { roleId: id, realm: realm!.realm }, - composites + composites, ); refresh(); navigate(toTab("associated-roles")); diff --git a/js/apps/admin-ui/src/realm-settings/AddClientProfileModal.tsx b/js/apps/admin-ui/src/realm-settings/AddClientProfileModal.tsx index d04a556836a3..dc45dca5a92c 100644 --- a/js/apps/admin-ui/src/realm-settings/AddClientProfileModal.tsx +++ b/js/apps/admin-ui/src/realm-settings/AddClientProfileModal.tsx @@ -48,7 +48,7 @@ export const AddClientProfileModal = (props: AddClientProfileModalProps) => { (globalProfiles) => ({ ...globalProfiles, global: true, - }) + }), ); const profiles = allProfiles.profiles?.map((profiles) => ({ @@ -58,7 +58,7 @@ export const AddClientProfileModal = (props: AddClientProfileModalProps) => { setTableProfiles([...(globalProfiles ?? []), ...(profiles ?? [])]); }, - [] + [], ); const loader = async () => diff --git a/js/apps/admin-ui/src/realm-settings/ClientProfileForm.tsx b/js/apps/admin-ui/src/realm-settings/ClientProfileForm.tsx index fd675028e1bc..4a15d52f36db 100644 --- a/js/apps/admin-ui/src/realm-settings/ClientProfileForm.tsx +++ b/js/apps/admin-ui/src/realm-settings/ClientProfileForm.tsx @@ -84,7 +84,7 @@ export default function ClientProfileForm() { serverInfo.componentTypes?.[ "org.keycloak.services.clientpolicy.executor.ClientPolicyExecutorProvider" ], - [] + [], ); const [executorToDelete, setExecutorToDelete] = useState<{ idx: number; @@ -103,21 +103,21 @@ export default function ClientProfileForm() { profiles: profiles.profiles?.filter((p) => p.name !== profileName), }); const globalProfile = profiles.globalProfiles?.find( - (p) => p.name === profileName + (p) => p.name === profileName, ); const profile = profiles.profiles?.find((p) => p.name === profileName); setIsGlobalProfile(globalProfile !== undefined); setValue("name", globalProfile?.name ?? profile?.name ?? ""); setValue( "description", - globalProfile?.description ?? profile?.description ?? "" + globalProfile?.description ?? profile?.description ?? "", ); setValue( "executors", - globalProfile?.executors ?? profile?.executors ?? [] + globalProfile?.executors ?? profile?.executors ?? [], ); }, - [key] + [key], ); const save = async (form: ClientProfileForm) => { @@ -133,7 +133,7 @@ export default function ClientProfileForm() { editMode ? t("realm-settings:updateClientProfileSuccess") : t("realm-settings:createClientProfileSuccess"), - AlertVariant.success + AlertVariant.success, ); navigate(toClientProfile({ realm, profileName: form.name })); @@ -142,7 +142,7 @@ export default function ClientProfileForm() { editMode ? "realm-settings:updateClientProfileError" : "realm-settings:createClientProfileError", - error + error, ); } }; @@ -361,7 +361,7 @@ export default function ClientProfileForm() { )} {executorTypes ?.filter( - (type) => type.id === executor.executor + (type) => type.id === executor.executor, ) .map((type) => ( diff --git a/js/apps/admin-ui/src/realm-settings/DefaultGroupsTab.tsx b/js/apps/admin-ui/src/realm-settings/DefaultGroupsTab.tsx index b8ca5442b5f1..e92ac0929f9e 100644 --- a/js/apps/admin-ui/src/realm-settings/DefaultGroupsTab.tsx +++ b/js/apps/admin-ui/src/realm-settings/DefaultGroupsTab.tsx @@ -54,7 +54,7 @@ export const DefaultsGroupsTab = () => { setDefaultGroups(groups); setKey(key + 1); }, - [load] + [load], ); const loader = () => Promise.resolve(defaultGroups!); @@ -66,12 +66,12 @@ export const DefaultsGroupsTab = () => { adminClient.realms.removeDefaultGroup({ realm, id: group.id!, - }) - ) + }), + ), ); addAlert( t("groupRemove", { count: selectedRows.length }), - AlertVariant.success + AlertVariant.success, ); setSelectedRows([]); } catch (error) { @@ -87,12 +87,12 @@ export const DefaultsGroupsTab = () => { adminClient.realms.addDefaultGroup({ realm, id: group.id!, - }) - ) + }), + ), ); addAlert( t("defaultGroupAdded", { count: groups.length }), - AlertVariant.success + AlertVariant.success, ); } catch (error) { addError("realm-settings:defaultGroupAddedError", error); diff --git a/js/apps/admin-ui/src/realm-settings/EmailTab.tsx b/js/apps/admin-ui/src/realm-settings/EmailTab.tsx index c0eba2e95799..9021eff79a9c 100644 --- a/js/apps/admin-ui/src/realm-settings/EmailTab.tsx +++ b/js/apps/admin-ui/src/realm-settings/EmailTab.tsx @@ -87,7 +87,7 @@ export const RealmSettingsEmailTab = ({ toggleTest(); await adminClient.realms.testSMTPConnection( { realm: realm.realm! }, - serverSettings + serverSettings, ); addAlert(t("testConnectionSuccess"), AlertVariant.success); } catch (error) { diff --git a/js/apps/admin-ui/src/realm-settings/ExecutorForm.tsx b/js/apps/admin-ui/src/realm-settings/ExecutorForm.tsx index 6ba6c6bc7a07..e88fc784f217 100644 --- a/js/apps/admin-ui/src/realm-settings/ExecutorForm.tsx +++ b/js/apps/admin-ui/src/realm-settings/ExecutorForm.tsx @@ -65,7 +65,7 @@ export default function ExecutorForm() { const setupForm = (profiles: ClientProfileRepresentation[]) => { const profile = profiles.find((profile) => profile.name === profileName); const executor = profile?.executors?.find( - (executor) => executor.executor === executorName + (executor) => executor.executor === executorName, ); if (executor) reset({ config: executor.configuration }); }; @@ -80,7 +80,7 @@ export default function ExecutorForm() { setupForm(profiles.profiles!); setupForm(profiles.globalProfiles!); }, - [] + [], ); const save = async () => { @@ -97,7 +97,7 @@ export default function ExecutorForm() { if (editMode) { const profileExecutor = profile.executors!.find( - (executor) => executor.executor === executorName + (executor) => executor.executor === executorName, ); profileExecutor!.configuration = { ...profileExecutor!.configuration, @@ -122,7 +122,7 @@ export default function ExecutorForm() { editMode ? t("realm-settings:updateExecutorSuccess") : t("realm-settings:addExecutorSuccess"), - AlertVariant.success + AlertVariant.success, ); navigate(toClientProfile({ realm, profileName })); @@ -131,17 +131,17 @@ export default function ExecutorForm() { editMode ? "realm-settings:updateExecutorError" : "realm-settings:addExecutorError", - error + error, ); } }; const globalProfile = globalProfiles.find( - (globalProfile) => globalProfile.name === profileName + (globalProfile) => globalProfile.name === profileName, ); const profileExecutorType = executorTypes?.find( - (executor) => executor.id === executorName + (executor) => executor.id === executorName, ); const editedProfileExecutors = @@ -152,7 +152,7 @@ export default function ExecutorForm() { ...property, defaultValue: globalDefaultValues, }; - } + }, ); return ( @@ -197,11 +197,11 @@ export default function ExecutorForm() { onSelect={(_, value) => { reset({ ...defaultValues, executor: value.toString() }); const selectedExecutor = executorTypes?.filter( - (type) => type.id === value + (type) => type.id === value, ); setExecutors(selectedExecutor ?? []); setExecutorProperties( - selectedExecutor?.[0].properties ?? [] + selectedExecutor?.[0].properties ?? [], ); setSelectExecutorTypeOpen(false); }} diff --git a/js/apps/admin-ui/src/realm-settings/GeneralTab.tsx b/js/apps/admin-ui/src/realm-settings/GeneralTab.tsx index 7a7e7e237baa..d18698e7ec3d 100644 --- a/js/apps/admin-ui/src/realm-settings/GeneralTab.tsx +++ b/js/apps/admin-ui/src/realm-settings/GeneralTab.tsx @@ -60,12 +60,12 @@ export const RealmSettingsGeneralTab = ({ convertToFormValues(realm, setValue); if (realm.attributes?.["acr.loa.map"]) { const result = Object.entries( - JSON.parse(realm.attributes["acr.loa.map"]) + JSON.parse(realm.attributes["acr.loa.map"]), ).flatMap(([key, value]) => ({ key, value })); result.concat({ key: "", value: "" }); setValue( convertAttributeNameToForm("attributes.acr.loa.map") as any, - result + result, ); } }; @@ -232,7 +232,7 @@ export const RealmSettingsGeneralTab = ({ @@ -273,7 +273,7 @@ export const RealmSettingsGeneralTab = ({ diff --git a/js/apps/admin-ui/src/realm-settings/LocalizationTab.tsx b/js/apps/admin-ui/src/realm-settings/LocalizationTab.tsx index e72535b82cb4..2825cbe846e0 100644 --- a/js/apps/admin-ui/src/realm-settings/LocalizationTab.tsx +++ b/js/apps/admin-ui/src/realm-settings/LocalizationTab.tsx @@ -96,7 +96,7 @@ export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => { const themeTypes = useServerInfo().themes!; const allLocales = useMemo(() => { const locales = Object.values(themeTypes).flatMap((theme) => - theme.flatMap(({ locales }) => (locales ? locales : [])) + theme.flatMap(({ locales }) => (locales ? locales : [])), ); return Array.from(new Set(locales)); }, [themeTypes]); @@ -158,7 +158,7 @@ export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => { if (filter) { const filtered = uniqWith( searchInBundles(0).concat(searchInBundles(1)), - isEqual + isEqual, ); result = Object.fromEntries(filtered); @@ -221,14 +221,14 @@ export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => { return bundles; }, - [tableKey, filter, first, max] + [tableKey, filter, first, max], ); const handleTextInputChange = ( newValue: string, evt: any, rowIndex: number, - cellIndex: number + cellIndex: number, ) => { setTableRows((prev) => { const newRows = cloneDeep(prev); @@ -243,7 +243,7 @@ export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => { const updateEditableRows = async ( type: RowEditType, rowIndex?: number, - validationErrors?: RowErrors + validationErrors?: RowErrors, ) => { if (rowIndex === undefined) { return; @@ -277,7 +277,7 @@ export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => { selectMenuLocale || getValues("defaultLocale") || DEFAULT_LOCALE, key, }, - value + value, ); addAlert(t("updateMessageBundleSuccess"), AlertVariant.success); } catch (error) { @@ -316,7 +316,7 @@ export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => { selectMenuLocale || getValues("defaultLocale") || DEFAULT_LOCALE, key: pair.key, }, - pair.value + pair.value, ); adminClient.setConfig({ @@ -414,8 +414,8 @@ export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => { if (field.value.includes(option)) { field.onChange( field.value.filter( - (item: string) => item !== option - ) + (item: string) => item !== option, + ), ); } else { field.onChange([...field.value, option]); @@ -464,7 +464,7 @@ export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => { ? localeToDisplayName(field.value) : realm.defaultLocale !== "" ? localeToDisplayName( - realm.defaultLocale || DEFAULT_LOCALE + realm.defaultLocale || DEFAULT_LOCALE, ) : t("placeholderText") } @@ -594,7 +594,7 @@ export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => { title: t("common:delete"), onClick: (_, row) => deleteKey( - (tableRows[row].cells?.[0] as IRowCell).props.value + (tableRows[row].cells?.[0] as IRowCell).props.value, ), }, ]} diff --git a/js/apps/admin-ui/src/realm-settings/LoginTab.tsx b/js/apps/admin-ui/src/realm-settings/LoginTab.tsx index 173ad65b44c7..6a0b8b375a75 100644 --- a/js/apps/admin-ui/src/realm-settings/LoginTab.tsx +++ b/js/apps/admin-ui/src/realm-settings/LoginTab.tsx @@ -37,7 +37,7 @@ export const RealmSettingsLoginTab = ({ }, Array.isArray(switches) ? switches.reduce((realm, s) => Object.assign(realm, s), realm) - : Object.assign(realm, switches) + : Object.assign(realm, switches), ); addAlert(t("enableSwitchSuccess", { switch: t(name) })); refresh(); diff --git a/js/apps/admin-ui/src/realm-settings/NewAttributeSettings.tsx b/js/apps/admin-ui/src/realm-settings/NewAttributeSettings.tsx index 47c8e47449e4..0bbda4aa493d 100644 --- a/js/apps/admin-ui/src/realm-settings/NewAttributeSettings.tsx +++ b/js/apps/admin-ui/src/realm-settings/NewAttributeSettings.tsx @@ -65,14 +65,14 @@ type PermissionView = [ { adminView: boolean; userView: boolean; - } + }, ]; type PermissionEdit = [ { adminEdit: boolean; userEdit: boolean; - } + }, ]; export const USERNAME_EMAIL = ["username", "email"]; @@ -141,29 +141,29 @@ export default function NewAttributeSettings() { ...values } = config.attributes!.find( - (attribute) => attribute.name === attributeName + (attribute) => attribute.name === attributeName, ) || {}; convertToFormValues(values, form.setValue); Object.entries( - flatten({ permissions, selector, required }, { safe: true }) + flatten({ permissions, selector, required }, { safe: true }), ).map(([key, value]) => form.setValue(key as any, value)); form.setValue( "annotations", Object.entries(annotations || {}).map(([key, value]) => ({ key, value, - })) + })), ); form.setValue( "validations", Object.entries(validations || {}).map(([key, value]) => ({ key, value, - })) + })), ); form.setValue("isRequired", required !== undefined); }, - [] + [], ); const save = async (profileConfig: UserProfileAttributeType) => { @@ -175,12 +175,12 @@ export default function NewAttributeSettings() { : currentValidations.value; return prevValidations; }, - {} as Record + {} as Record, ); const annotations = profileConfig.annotations.reduce( (obj, item) => Object.assign(obj, { [item.key]: item.value }), - {} + {}, ); const patchAttributes = () => @@ -203,7 +203,9 @@ export default function NewAttributeSettings() { profileConfig.isRequired ? { required: profileConfig.required } : undefined, - profileConfig.group ? { group: profileConfig.group } : { group: null } + profileConfig.group + ? { group: profileConfig.group } + : { group: null }, ); }); @@ -224,7 +226,7 @@ export default function NewAttributeSettings() { profileConfig.isRequired ? { required: profileConfig.required } : undefined, - profileConfig.group ? { group: profileConfig.group } : undefined + profileConfig.group ? { group: profileConfig.group } : undefined, ), ] as UserProfileAttribute); @@ -241,7 +243,7 @@ export default function NewAttributeSettings() { addAlert( t("realm-settings:createAttributeSuccess"), - AlertVariant.success + AlertVariant.success, ); } catch (error) { addError("realm-settings:createAttributeError", error); diff --git a/js/apps/admin-ui/src/realm-settings/NewClientPolicyCondition.tsx b/js/apps/admin-ui/src/realm-settings/NewClientPolicyCondition.tsx index 5db17673afb5..a1f14fbfe7b1 100644 --- a/js/apps/admin-ui/src/realm-settings/NewClientPolicyCondition.tsx +++ b/js/apps/admin-ui/src/realm-settings/NewClientPolicyCondition.tsx @@ -79,15 +79,15 @@ export default function NewClientPolicyCondition() { if (conditionName) { const currentPolicy = policies.policies?.find( - (item) => item.name === policyName + (item) => item.name === policyName, ); const typeAndConfigData = currentPolicy?.conditions?.find( - (item) => item.condition === conditionName + (item) => item.condition === conditionName, ); const currentCondition = conditionTypes?.find( - (condition) => condition.id === conditionName + (condition) => condition.id === conditionName, ); setConditionData(typeAndConfigData!); @@ -95,7 +95,7 @@ export default function NewClientPolicyCondition() { setupForm(typeAndConfigData!); } }, - [] + [], ); const save = async (configPolicy: ConfigProperty) => { @@ -122,7 +122,7 @@ export default function NewClientPolicyCondition() { }; const index = conditions.findIndex( - (condition) => conditionName === condition.condition + (condition) => conditionName === condition.condition, ); if (index === -1) { @@ -162,7 +162,7 @@ export default function NewClientPolicyCondition() { conditionName ? t("realm-settings:updateClientConditionSuccess") : t("realm-settings:createClientConditionSuccess"), - AlertVariant.success + AlertVariant.success, ); } catch (error) { addError("realm-settings:createClientConditionError", error); @@ -189,7 +189,7 @@ export default function NewClientPolicyCondition() { helpText={ conditionType ? `realm-settings-help:${camelCase( - conditionType.replace(/-/g, " ") + conditionType.replace(/-/g, " "), )}` : "realm-settings-help:conditions" } @@ -212,7 +212,7 @@ export default function NewClientPolicyCondition() { onSelect={(_, value) => { field.onChange(value); setConditionProperties( - (value as ComponentTypeRepresentation).properties + (value as ComponentTypeRepresentation).properties, ); setConditionType((value as ComponentTypeRepresentation).id); setCondition([ @@ -232,8 +232,8 @@ export default function NewClientPolicyCondition() { selected={condition.id === field.value} description={t( `realm-settings-help:${camelCase( - condition.id.replace(/-/g, " ") - )}` + condition.id.replace(/-/g, " "), + )}`, )} key={condition.id} value={condition} diff --git a/js/apps/admin-ui/src/realm-settings/NewClientPolicyForm.tsx b/js/apps/admin-ui/src/realm-settings/NewClientPolicyForm.tsx index 61d52016a3c6..f9f5a18378f4 100644 --- a/js/apps/admin-ui/src/realm-settings/NewClientPolicyForm.tsx +++ b/js/apps/admin-ui/src/realm-settings/NewClientPolicyForm.tsx @@ -183,7 +183,7 @@ export default function NewClientPolicyForm() { }, ({ policies, profiles }) => { const currentPolicy = policies.policies?.find( - (item) => item.name === policyName + (item) => item.name === policyName, ); const allClientProfiles = [ @@ -199,7 +199,7 @@ export default function NewClientPolicyForm() { setShowAddConditionsAndProfilesForm(true); } }, - [] + [], ); const setupForm = (policy: ClientPolicyRepresentation) => { @@ -207,7 +207,7 @@ export default function NewClientPolicyForm() { }; const policy = (policies || []).filter( - (policy) => policy.name === policyName + (policy) => policy.name === policyName, ); const policyConditions = policy[0]?.conditions || []; const policyProfiles = policy[0]?.profiles || []; @@ -229,12 +229,12 @@ export default function NewClientPolicyForm() { const getAllPolicies = () => { const policyNameExists = policies?.some( - (policy) => policy.name === createdPolicy.name + (policy) => policy.name === createdPolicy.name, ); if (policyNameExists) { return policies?.map((policy) => - policy.name === createdPolicy.name ? createdPolicy : policy + policy.name === createdPolicy.name ? createdPolicy : policy, ); } else if (createdForm.name !== policyName) { return policies @@ -252,7 +252,7 @@ export default function NewClientPolicyForm() { policyName ? t("realm-settings:updateClientPolicySuccess") : t("realm-settings:createClientPolicySuccess"), - AlertVariant.success + AlertVariant.success, ); navigate(toEditClientPolicy({ realm, policyName: createdForm.name! })); setShowAddConditionsAndProfilesForm(true); @@ -270,7 +270,7 @@ export default function NewClientPolicyForm() { continueButtonVariant: ButtonVariant.danger, onConfirm: async () => { const updatedPolicies = policies?.filter( - (policy) => policy.name !== policyName + (policy) => policy.name !== policyName, ); try { @@ -282,7 +282,7 @@ export default function NewClientPolicyForm() { toClientPolicies({ realm, tab: "policies", - }) + }), ); } catch (error) { addError(t("deleteClientPolicyError"), error); @@ -307,14 +307,14 @@ export default function NewClientPolicyForm() { }); addAlert(t("deleteConditionSuccess"), AlertVariant.success); navigate( - toEditClientPolicy({ realm, policyName: formValues.name! }) + toEditClientPolicy({ realm, policyName: formValues.name! }), ); } catch (error) { addError(t("deleteConditionError"), error); } } else { const updatedPolicies = policies?.filter( - (policy) => policy.name !== policyName + (policy) => policy.name !== policyName, ); try { @@ -326,7 +326,7 @@ export default function NewClientPolicyForm() { toClientPolicies({ realm, tab: "policies", - }) + }), ); } catch (error) { addError(t("deleteClientError"), error); @@ -357,7 +357,7 @@ export default function NewClientPolicyForm() { } } else { const updatedPolicies = policies?.filter( - (policy) => policy.name !== policyName + (policy) => policy.name !== policyName, ); try { @@ -369,7 +369,7 @@ export default function NewClientPolicyForm() { toClientPolicies({ realm, tab: "policies", - }) + }), ); } catch (error) { addError(t("deleteClientError"), error); @@ -400,7 +400,7 @@ export default function NewClientPolicyForm() { }; const index = policies?.findIndex( - (policy) => createdPolicy.name === policy.name + (policy) => createdPolicy.name === policy.name, ); if (index === undefined || index === -1) { @@ -421,7 +421,7 @@ export default function NewClientPolicyForm() { navigate(toEditClientPolicy({ realm, policyName: formValues.name! })); addAlert( t("realm-settings:addClientProfileSuccess"), - AlertVariant.success + AlertVariant.success, ); } catch (error) { addError("realm-settings:addClientProfileError", error); @@ -515,7 +515,7 @@ export default function NewClientPolicyForm() { toClientPolicies({ realm, tab: "policies", - }) + }), ) } data-testid="cancelCreatePolicy" @@ -618,7 +618,7 @@ export default function NewClientPolicyForm() { } > - ) + ), )} , ]} @@ -701,7 +701,7 @@ export default function NewClientPolicyForm() { type === profile.name + (profile) => type === profile.name, )?.description } fieldLabelId={profile} diff --git a/js/apps/admin-ui/src/realm-settings/PartialExport.tsx b/js/apps/admin-ui/src/realm-settings/PartialExport.tsx index 735140139a92..e54132bb3cb9 100644 --- a/js/apps/admin-ui/src/realm-settings/PartialExport.tsx +++ b/js/apps/admin-ui/src/realm-settings/PartialExport.tsx @@ -55,7 +55,7 @@ export const PartialExportDialog = ({ new Blob([prettyPrintJSON(realmExport)], { type: "application/json", }), - "realm-export.json" + "realm-export.json", ); addAlert(t("exportSuccess"), AlertVariant.success); diff --git a/js/apps/admin-ui/src/realm-settings/PartialImport.tsx b/js/apps/admin-ui/src/realm-settings/PartialImport.tsx index 3ac1b6774258..e9b0d413ff19 100644 --- a/js/apps/admin-ui/src/realm-settings/PartialImport.tsx +++ b/js/apps/admin-ui/src/realm-settings/PartialImport.tsx @@ -85,7 +85,7 @@ export const PartialImportDialog = (props: PartialImportProps) => { const [resourcesToImport, setResourcesToImport] = useState(INITIAL_RESOURCES); const isAnyResourceChecked = Object.values(resourcesToImport).some( - (checked) => checked + (checked) => checked, ); const resetResourcesToImport = () => { @@ -125,7 +125,7 @@ export const PartialImportDialog = (props: PartialImportProps) => { const handleResourceCheckBox = ( checked: boolean, - event: FormEvent + event: FormEvent, ) => { const resource = event.currentTarget.name as Resource; @@ -148,7 +148,7 @@ export const PartialImportDialog = (props: PartialImportProps) => { const handleCollisionSelect = ( event: ChangeEvent | ReactMouseEvent, - option: string | SelectOptionObject + option: string | SelectOptionObject, ) => { setCollisionOption(option as CollisionOption); setIsCollisionSelectOpen(false); @@ -211,13 +211,13 @@ export const PartialImportDialog = (props: PartialImportProps) => { }; const clientRolesCount = ( - clientRoles: Record + clientRoles: Record, ) => Object.values(clientRoles).reduce((total, role) => total + role.length, 0); const resourceDataListItem = ( resource: Resource, - resourceDisplayName: string + resourceDisplayName: string, ) => { return ( @@ -358,14 +358,14 @@ export const PartialImportDialog = (props: PartialImportProps) => { {targetHasResource("identityProviders") && resourceDataListItem( "identityProviders", - t("common:identityProviders") + t("common:identityProviders"), )} {targetHasRealmRoles() && resourceDataListItem("realmRoles", t("common:realmRoles"))} {targetHasClientRoles() && resourceDataListItem( "clientRoles", - t("common:clientRoles") + t("common:clientRoles"), )} diff --git a/js/apps/admin-ui/src/realm-settings/PoliciesTab.tsx b/js/apps/admin-ui/src/realm-settings/PoliciesTab.tsx index 595c957e44b6..74b3034fcc8c 100644 --- a/js/apps/admin-ui/src/realm-settings/PoliciesTab.tsx +++ b/js/apps/admin-ui/src/realm-settings/PoliciesTab.tsx @@ -60,7 +60,7 @@ export const PoliciesTab = () => { setTablePolicies(policies.policies || []), setCode(prettyPrintJSON(policies.policies)); }, - [key] + [key], ); const loader = async () => policies ?? []; @@ -76,7 +76,7 @@ export const PoliciesTab = () => { ...policy, enabled, }; - } + }, ); try { @@ -86,7 +86,7 @@ export const PoliciesTab = () => { navigate(toClientPolicies({ realm, tab: "policies" })); addAlert( t("realm-settings:updateClientPolicySuccess"), - AlertVariant.success + AlertVariant.success, ); } catch (error) { addError("realm-settings:updateClientPolicyError", error); @@ -155,7 +155,7 @@ export const PoliciesTab = () => { }); addAlert( t("realm-settings:updateClientPoliciesSuccess"), - AlertVariant.success + AlertVariant.success, ); refresh(); } catch (error) { @@ -176,7 +176,7 @@ export const PoliciesTab = () => { continueButtonVariant: ButtonVariant.danger, onConfirm: async () => { const updatedPolicies = policies?.filter( - (policy) => policy.name !== selectedPolicy?.name + (policy) => policy.name !== selectedPolicy?.name, ); try { diff --git a/js/apps/admin-ui/src/realm-settings/ProfilesTab.tsx b/js/apps/admin-ui/src/realm-settings/ProfilesTab.tsx index c092bc84a9e1..9ae4eab45580 100644 --- a/js/apps/admin-ui/src/realm-settings/ProfilesTab.tsx +++ b/js/apps/admin-ui/src/realm-settings/ProfilesTab.tsx @@ -65,7 +65,7 @@ export default function ProfilesTab() { (globalProfiles) => ({ ...globalProfiles, global: true, - }) + }), ); const profiles = allProfiles.profiles?.map((profiles) => ({ @@ -77,13 +77,13 @@ export default function ProfilesTab() { setTableProfiles(allClientProfiles || []); setCode(JSON.stringify(allClientProfiles, null, 2)); }, - [key] + [key], ); const loader = async () => tableProfiles ?? []; const normalizeProfile = ( - profile: ClientProfile + profile: ClientProfile, ): ClientProfileRepresentation => omit(profile, "global"); const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ @@ -96,10 +96,11 @@ export default function ProfilesTab() { onConfirm: async () => { const updatedProfiles = tableProfiles ?.filter( - (profile) => profile.name !== selectedProfile?.name && !profile.global + (profile) => + profile.name !== selectedProfile?.name && !profile.global, ) .map((profile) => - normalizeProfile(profile) + normalizeProfile(profile), ); try { @@ -153,7 +154,7 @@ export default function ProfilesTab() { }); addAlert( t("realm-settings:updateClientProfilesSuccess"), - AlertVariant.success + AlertVariant.success, ); setKey(key + 1); } catch (error) { diff --git a/js/apps/admin-ui/src/realm-settings/RealmSettingsTabs.tsx b/js/apps/admin-ui/src/realm-settings/RealmSettingsTabs.tsx index 9f31ffd354a2..4d63d2c734ec 100644 --- a/js/apps/admin-ui/src/realm-settings/RealmSettingsTabs.tsx +++ b/js/apps/admin-ui/src/realm-settings/RealmSettingsTabs.tsx @@ -193,8 +193,8 @@ export const RealmSettingsTabs = ({ Object.fromEntries( (r.attributes["acr.loa.map"] as KeyValueType[]) .filter(({ key }) => key !== "") - .map(({ key, value }) => [key, value]) - ) + .map(({ key, value }) => [key, value]), + ), ); } @@ -245,7 +245,7 @@ export const RealmSettingsTabs = ({ toClientPolicies({ realm: realmName, tab, - }) + }), ); const clientPoliciesProfilesTab = useClientPoliciesTab("profiles"); @@ -377,7 +377,7 @@ export const RealmSettingsTabs = ({ tooltip={ } @@ -394,7 +394,7 @@ export const RealmSettingsTabs = ({ tooltip={ } diff --git a/js/apps/admin-ui/src/realm-settings/TokensTab.tsx b/js/apps/admin-ui/src/realm-settings/TokensTab.tsx index 04c47fc420a5..a643b11c9e3a 100644 --- a/js/apps/admin-ui/src/realm-settings/TokensTab.tsx +++ b/js/apps/admin-ui/src/realm-settings/TokensTab.tsx @@ -49,7 +49,7 @@ export const RealmSettingsTokensTab = ({ useState(false); const defaultSigAlgOptions = sortProviders( - serverInfo.providers!["signature"].providers + serverInfo.providers!["signature"].providers, ); const form = useForm(); @@ -265,7 +265,7 @@ export const RealmSettingsTokensTab = ({ onMinus={() => field.onChange(field.value! - 1)} onChange={(event) => field.onChange( - Number((event.target as HTMLInputElement).value) + Number((event.target as HTMLInputElement).value), ) } /> @@ -325,7 +325,7 @@ export const RealmSettingsTokensTab = ({ labelIcon={ @@ -443,7 +443,7 @@ export const RealmSettingsTokensTab = ({ labelIcon={ diff --git a/js/apps/admin-ui/src/realm-settings/UserRegistration.tsx b/js/apps/admin-ui/src/realm-settings/UserRegistration.tsx index fa0e757cf6b4..d87b5b66b505 100644 --- a/js/apps/admin-ui/src/realm-settings/UserRegistration.tsx +++ b/js/apps/admin-ui/src/realm-settings/UserRegistration.tsx @@ -24,7 +24,7 @@ export const UserRegistration = () => { useFetch( () => adminClient.realms.findOne({ realm: realmName }), setRealm, - [] + [], ); if (!realm) { @@ -37,7 +37,7 @@ export const UserRegistration = () => { try { await adminClient.roles.createComposite( { roleId: realm.defaultRole!.id!, realm: realmName }, - compositeArray + compositeArray, ); setKey(key + 1); addAlert(t("roles:addAssociatedRolesSuccess"), AlertVariant.success); diff --git a/js/apps/admin-ui/src/realm-settings/event-config/AddEventTypesDialog.tsx b/js/apps/admin-ui/src/realm-settings/event-config/AddEventTypesDialog.tsx index 0fa89dcb5393..7bdd4d67e070 100644 --- a/js/apps/admin-ui/src/realm-settings/event-config/AddEventTypesDialog.tsx +++ b/js/apps/admin-ui/src/realm-settings/event-config/AddEventTypesDialog.tsx @@ -49,7 +49,7 @@ export const AddEventTypesDialog = ({ ariaLabelKey="addTypes" onSelect={(selected) => setSelectedTypes(selected)} eventTypes={enums!["eventType"].filter( - (type) => !configured.includes(type) + (type) => !configured.includes(type), )} /> diff --git a/js/apps/admin-ui/src/realm-settings/event-config/EventsTab.tsx b/js/apps/admin-ui/src/realm-settings/event-config/EventsTab.tsx index 2c5d1e5062d9..c9eead23b9f0 100644 --- a/js/apps/admin-ui/src/realm-settings/event-config/EventsTab.tsx +++ b/js/apps/admin-ui/src/realm-settings/event-config/EventsTab.tsx @@ -92,13 +92,13 @@ export const EventsTab = ({ realm }: EventsTabProps) => { }); reload(); }, - [key] + [key], ); const save = async (config: EventsConfigForm) => { const updatedEventListener = !isEqual( events?.eventsListeners, - config.eventsListeners + config.eventsListeners, ); const { adminEventsExpiration, ...eventConfig } = config; @@ -108,28 +108,28 @@ export const EventsTab = ({ realm }: EventsTabProps) => { { ...realm, attributes: { ...(realm.attributes || {}), adminEventsExpiration }, - } + }, ); } try { await adminClient.realms.updateConfigEvents( { realm: realmName }, - eventConfig + eventConfig, ); setupForm({ ...events, ...eventConfig, adminEventsExpiration }); addAlert( updatedEventListener ? t("realm-settings:saveEventListenersSuccess") : t("realm-settings:eventConfigSuccessfully"), - AlertVariant.success + AlertVariant.success, ); } catch (error) { addError( updatedEventListener ? t("realm-settings:saveEventListenersError") : t("realm-settings:eventConfigError"), - error + error, ); } }; @@ -202,7 +202,7 @@ export const EventsTab = ({ realm }: EventsTabProps) => { eventTypes={events?.enabledEventTypes || []} onDelete={(value) => { const enabledEventTypes = events?.enabledEventTypes?.filter( - (e) => e !== value.id + (e) => e !== value.id, ); addEvents(enabledEventTypes); setEvents({ ...events, enabledEventTypes }); diff --git a/js/apps/admin-ui/src/realm-settings/keys/KeysListTab.tsx b/js/apps/admin-ui/src/realm-settings/keys/KeysListTab.tsx index 922ad4deeaec..5229504e2b5d 100644 --- a/js/apps/admin-ui/src/realm-settings/keys/KeysListTab.tsx +++ b/js/apps/admin-ui/src/realm-settings/keys/KeysListTab.tsx @@ -97,13 +97,13 @@ export const KeysListTab = ({ realmComponents }: KeysListTabProps) => { return keysMetaData.keys?.map((key) => { const provider = realmComponents.find( (component: ComponentRepresentation) => - component.id === key.providerId + component.id === key.providerId, ); return { ...key, provider: provider?.name } as KeyData; })!; }, setKeyData, - [] + [], ); const [togglePublicKeyDialog, PublicKeyDialog] = useConfirmDialog({ @@ -142,7 +142,7 @@ export const KeysListTab = ({ realmComponents }: KeysListTabProps) => { setFilteredKeyData( filterType !== FILTER_OPTIONS[0] ? keyData!.filter(({ status }) => status === filterType) - : undefined + : undefined, ) } /> diff --git a/js/apps/admin-ui/src/realm-settings/keys/KeysProvidersTab.tsx b/js/apps/admin-ui/src/realm-settings/keys/KeysProvidersTab.tsx index bbbfdd71493e..b5a7f32776ab 100644 --- a/js/apps/admin-ui/src/realm-settings/keys/KeysProvidersTab.tsx +++ b/js/apps/admin-ui/src/realm-settings/keys/KeysProvidersTab.tsx @@ -55,7 +55,7 @@ export const KeysProvidersTab = ({ const [searchVal, setSearchVal] = useState(""); const [filteredComponents, setFilteredComponents] = useState( - [] + [], ); const [isCreateModalOpen, handleModalToggle] = useToggle(); @@ -75,7 +75,7 @@ export const KeysProvidersTab = ({ realmComponents.map((component) => { const provider = keyProviderComponentTypes.find( (componentType: ComponentTypeRepresentation) => - component.providerId === componentType.id + component.providerId === componentType.id, ); return { @@ -83,7 +83,7 @@ export const KeysProvidersTab = ({ providerDescription: provider?.helpText, }; }), - [realmComponents] + [realmComponents], ); const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ @@ -115,7 +115,7 @@ export const KeysProvidersTab = ({ const filteredComponents = components.filter( (component) => component.name?.includes(searchVal) || - component.providerId?.includes(searchVal) + component.providerId?.includes(searchVal), ); setFilteredComponents(filteredComponents); } else { @@ -216,7 +216,7 @@ export const KeysProvidersTab = ({ ).toString(), ], }, - } + }, ); }); diff --git a/js/apps/admin-ui/src/realm-settings/keys/KeysTab.tsx b/js/apps/admin-ui/src/realm-settings/keys/KeysTab.tsx index 06104d52d9b6..3d5d0cf0befa 100644 --- a/js/apps/admin-ui/src/realm-settings/keys/KeysTab.tsx +++ b/js/apps/admin-ui/src/realm-settings/keys/KeysTab.tsx @@ -48,7 +48,7 @@ export const KeysTab = () => { realm: realmName, }), (components) => setRealmComponents(sortByPriority(components)), - [key] + [key], ); const useTab = (tab: KeySubTab) => diff --git a/js/apps/admin-ui/src/realm-settings/keys/key-providers/KeyProviderForm.tsx b/js/apps/admin-ui/src/realm-settings/keys/key-providers/KeyProviderForm.tsx index 16a8e9b17caa..5989da904fbb 100644 --- a/js/apps/admin-ui/src/realm-settings/keys/key-providers/KeyProviderForm.tsx +++ b/js/apps/admin-ui/src/realm-settings/keys/key-providers/KeyProviderForm.tsx @@ -59,7 +59,7 @@ export const KeyProviderForm = ({ if (component.config) Object.entries(component.config).forEach( ([key, value]) => - (component.config![key] = Array.isArray(value) ? value : [value]) + (component.config![key] = Array.isArray(value) ? value : [value]), ); try { if (id) { @@ -68,7 +68,7 @@ export const KeyProviderForm = ({ { ...component, providerType: KEY_PROVIDER_TYPE, - } + }, ); addAlert(t("saveProviderSuccess"), AlertVariant.success); } else { @@ -94,7 +94,7 @@ export const KeyProviderForm = ({ reset({ ...result }); } }, - [] + [], ); return ( diff --git a/js/apps/admin-ui/src/realm-settings/routes/AddClientPolicy.tsx b/js/apps/admin-ui/src/realm-settings/routes/AddClientPolicy.tsx index 29bc19039f56..d4d677559a3a 100644 --- a/js/apps/admin-ui/src/realm-settings/routes/AddClientPolicy.tsx +++ b/js/apps/admin-ui/src/realm-settings/routes/AddClientPolicy.tsx @@ -17,7 +17,7 @@ export const AddClientPolicyRoute: AppRouteObject = { }; export const toAddClientPolicy = ( - params: AddClientPolicyParams + params: AddClientPolicyParams, ): Partial => ({ pathname: generatePath(AddClientPolicyRoute.path, params), }); diff --git a/js/apps/admin-ui/src/realm-settings/routes/AddClientProfile.tsx b/js/apps/admin-ui/src/realm-settings/routes/AddClientProfile.tsx index 9db23e445598..010c7a86f77d 100644 --- a/js/apps/admin-ui/src/realm-settings/routes/AddClientProfile.tsx +++ b/js/apps/admin-ui/src/realm-settings/routes/AddClientProfile.tsx @@ -20,7 +20,7 @@ export const AddClientProfileRoute: AppRouteObject = { }; export const toAddClientProfile = ( - params: AddClientProfileParams + params: AddClientProfileParams, ): Partial => ({ pathname: generatePath(AddClientProfileRoute.path, params), }); diff --git a/js/apps/admin-ui/src/realm-settings/routes/AddCondition.tsx b/js/apps/admin-ui/src/realm-settings/routes/AddCondition.tsx index b8cbab1ec3c8..a4b37bf292f8 100644 --- a/js/apps/admin-ui/src/realm-settings/routes/AddCondition.tsx +++ b/js/apps/admin-ui/src/realm-settings/routes/AddCondition.tsx @@ -9,7 +9,7 @@ export type NewClientPolicyConditionParams = { }; const NewClientPolicyCondition = lazy( - () => import("../NewClientPolicyCondition") + () => import("../NewClientPolicyCondition"), ); export const NewClientPolicyConditionRoute: AppRouteObject = { @@ -22,7 +22,7 @@ export const NewClientPolicyConditionRoute: AppRouteObject = { }; export const toNewClientPolicyCondition = ( - params: NewClientPolicyConditionParams + params: NewClientPolicyConditionParams, ): Partial => ({ pathname: generatePath(NewClientPolicyConditionRoute.path, params), }); diff --git a/js/apps/admin-ui/src/realm-settings/routes/ClientPolicies.tsx b/js/apps/admin-ui/src/realm-settings/routes/ClientPolicies.tsx index b6e4375d2e64..70a5cf827e53 100644 --- a/js/apps/admin-ui/src/realm-settings/routes/ClientPolicies.tsx +++ b/js/apps/admin-ui/src/realm-settings/routes/ClientPolicies.tsx @@ -22,7 +22,7 @@ export const ClientPoliciesRoute: AppRouteObject = { }; export const toClientPolicies = ( - params: ClientPoliciesParams + params: ClientPoliciesParams, ): Partial => ({ pathname: generatePath(ClientPoliciesRoute.path, params), }); diff --git a/js/apps/admin-ui/src/realm-settings/routes/ClientProfile.tsx b/js/apps/admin-ui/src/realm-settings/routes/ClientProfile.tsx index ab1413541599..65977a75733c 100644 --- a/js/apps/admin-ui/src/realm-settings/routes/ClientProfile.tsx +++ b/js/apps/admin-ui/src/realm-settings/routes/ClientProfile.tsx @@ -20,7 +20,7 @@ export const ClientProfileRoute: AppRouteObject = { }; export const toClientProfile = ( - params: ClientProfileParams + params: ClientProfileParams, ): Partial => ({ pathname: generatePath(ClientProfileRoute.path, params), }); diff --git a/js/apps/admin-ui/src/realm-settings/routes/EditAttributesGroup.tsx b/js/apps/admin-ui/src/realm-settings/routes/EditAttributesGroup.tsx index 123517a4ca3e..da2d3ee50671 100644 --- a/js/apps/admin-ui/src/realm-settings/routes/EditAttributesGroup.tsx +++ b/js/apps/admin-ui/src/realm-settings/routes/EditAttributesGroup.tsx @@ -9,7 +9,7 @@ export type EditAttributesGroupParams = { }; const AttributesGroupDetails = lazy( - () => import("../user-profile/AttributesGroupDetails") + () => import("../user-profile/AttributesGroupDetails"), ); export const EditAttributesGroupRoute: AppRouteObject = { @@ -22,7 +22,7 @@ export const EditAttributesGroupRoute: AppRouteObject = { }; export const toEditAttributesGroup = ( - params: EditAttributesGroupParams + params: EditAttributesGroupParams, ): Partial => ({ pathname: generatePath(EditAttributesGroupRoute.path, params), }); diff --git a/js/apps/admin-ui/src/realm-settings/routes/EditClientPolicy.tsx b/js/apps/admin-ui/src/realm-settings/routes/EditClientPolicy.tsx index 7dbc27e76c84..cb57cea5bd52 100644 --- a/js/apps/admin-ui/src/realm-settings/routes/EditClientPolicy.tsx +++ b/js/apps/admin-ui/src/realm-settings/routes/EditClientPolicy.tsx @@ -20,7 +20,7 @@ export const EditClientPolicyRoute: AppRouteObject = { }; export const toEditClientPolicy = ( - params: EditClientPolicyParams + params: EditClientPolicyParams, ): Partial => ({ pathname: generatePath(EditClientPolicyRoute.path, params), }); diff --git a/js/apps/admin-ui/src/realm-settings/routes/EditCondition.tsx b/js/apps/admin-ui/src/realm-settings/routes/EditCondition.tsx index 9b1ba2a39737..ec15d9bac4c8 100644 --- a/js/apps/admin-ui/src/realm-settings/routes/EditCondition.tsx +++ b/js/apps/admin-ui/src/realm-settings/routes/EditCondition.tsx @@ -10,7 +10,7 @@ export type EditClientPolicyConditionParams = { }; const NewClientPolicyCondition = lazy( - () => import("../NewClientPolicyCondition") + () => import("../NewClientPolicyCondition"), ); export const EditClientPolicyConditionRoute: AppRouteObject = { @@ -23,7 +23,7 @@ export const EditClientPolicyConditionRoute: AppRouteObject = { }; export const toEditClientPolicyCondition = ( - params: EditClientPolicyConditionParams + params: EditClientPolicyConditionParams, ): Partial => ({ pathname: generatePath(EditClientPolicyConditionRoute.path, params), }); diff --git a/js/apps/admin-ui/src/realm-settings/routes/KeyProvider.tsx b/js/apps/admin-ui/src/realm-settings/routes/KeyProvider.tsx index ed5d76f19d51..7c0faa2977f3 100644 --- a/js/apps/admin-ui/src/realm-settings/routes/KeyProvider.tsx +++ b/js/apps/admin-ui/src/realm-settings/routes/KeyProvider.tsx @@ -20,7 +20,7 @@ export type KeyProviderParams = { }; const KeyProviderForm = lazy( - () => import("../keys/key-providers/KeyProviderForm") + () => import("../keys/key-providers/KeyProviderForm"), ); export const KeyProviderFormRoute: AppRouteObject = { diff --git a/js/apps/admin-ui/src/realm-settings/routes/NewAttributesGroup.tsx b/js/apps/admin-ui/src/realm-settings/routes/NewAttributesGroup.tsx index 0a0ffadf323f..b28496952c13 100644 --- a/js/apps/admin-ui/src/realm-settings/routes/NewAttributesGroup.tsx +++ b/js/apps/admin-ui/src/realm-settings/routes/NewAttributesGroup.tsx @@ -8,7 +8,7 @@ export type NewAttributesGroupParams = { }; const AttributesGroupDetails = lazy( - () => import("../user-profile/AttributesGroupDetails") + () => import("../user-profile/AttributesGroupDetails"), ); export const NewAttributesGroupRoute: AppRouteObject = { @@ -21,7 +21,7 @@ export const NewAttributesGroupRoute: AppRouteObject = { }; export const toNewAttributesGroup = ( - params: NewAttributesGroupParams + params: NewAttributesGroupParams, ): Partial => ({ pathname: generatePath(NewAttributesGroupRoute.path, params), }); diff --git a/js/apps/admin-ui/src/realm-settings/security-defences/BruteForceDetection.tsx b/js/apps/admin-ui/src/realm-settings/security-defences/BruteForceDetection.tsx index 3ba81c9fca8b..0004077efd89 100644 --- a/js/apps/admin-ui/src/realm-settings/security-defences/BruteForceDetection.tsx +++ b/js/apps/admin-ui/src/realm-settings/security-defences/BruteForceDetection.tsx @@ -99,7 +99,7 @@ export const BruteForceDetection = ({ onMinus={() => field.onChange(field.value - 1)} onChange={(event) => field.onChange( - Number((event.target as HTMLInputElement).value) + Number((event.target as HTMLInputElement).value), ) } /> @@ -141,7 +141,7 @@ export const BruteForceDetection = ({ labelIcon={ @@ -161,7 +161,7 @@ export const BruteForceDetection = ({ onMinus={() => field.onChange(field.value - 1)} onChange={(event) => field.onChange( - Number((event.target as HTMLInputElement).value) + Number((event.target as HTMLInputElement).value), ) } /> diff --git a/js/apps/admin-ui/src/realm-settings/user-profile/AttributesGroupForm.tsx b/js/apps/admin-ui/src/realm-settings/user-profile/AttributesGroupForm.tsx index 5c91d61d8c69..dfc4006964c2 100644 --- a/js/apps/admin-ui/src/realm-settings/user-profile/AttributesGroupForm.tsx +++ b/js/apps/admin-ui/src/realm-settings/user-profile/AttributesGroupForm.tsx @@ -39,7 +39,7 @@ function transformAnnotations(input: KeyValueType[]): Record { return Object.fromEntries( input .filter((annotation) => annotation.key.length > 0) - .map((annotation) => [annotation.key, annotation.value] as const) + .map((annotation) => [annotation.key, annotation.value] as const), ); } @@ -64,7 +64,7 @@ export default function AttributesGroupForm() { const matchingGroup = useMemo( () => config?.groups?.find(({ name }) => name === params.name), - [config?.groups] + [config?.groups], ); useEffect(() => { diff --git a/js/apps/admin-ui/src/realm-settings/user-profile/AttributesGroupTab.tsx b/js/apps/admin-ui/src/realm-settings/user-profile/AttributesGroupTab.tsx index 464074bc5002..19777f3e8555 100644 --- a/js/apps/admin-ui/src/realm-settings/user-profile/AttributesGroupTab.tsx +++ b/js/apps/admin-ui/src/realm-settings/user-profile/AttributesGroupTab.tsx @@ -46,7 +46,7 @@ export const AttributesGroupTab = () => { continueButtonVariant: ButtonVariant.danger, onConfirm() { const groups = (config?.groups ?? []).filter( - (group) => group !== groupToDelete + (group) => group !== groupToDelete, ); save( @@ -54,7 +54,7 @@ export const AttributesGroupTab = () => { { successMessageKey: "realm-settings:deleteSuccess", errorMessageKey: "realm-settings:deleteAttributeGroupError", - } + }, ); }, }); diff --git a/js/apps/admin-ui/src/realm-settings/user-profile/AttributesTab.tsx b/js/apps/admin-ui/src/realm-settings/user-profile/AttributesTab.tsx index 4f201b5c374f..97681c948a52 100644 --- a/js/apps/admin-ui/src/realm-settings/user-profile/AttributesTab.tsx +++ b/js/apps/admin-ui/src/realm-settings/user-profile/AttributesTab.tsx @@ -41,7 +41,7 @@ export const AttributesTab = () => { const executeMove = async ( attribute: UserProfileAttribute, - newIndex: number + newIndex: number, ) => { const fromIndex = config?.attributes!.findIndex((attr) => { return attr.name === attribute.name; @@ -57,12 +57,12 @@ export const AttributesTab = () => { { successMessageKey: "realm-settings:updatedUserProfileSuccess", errorMessageKey: "realm-settings:updatedUserProfileError", - } + }, ); }; const updatedAttributes = config?.attributes!.filter( - (attribute) => attribute.name !== attributeToDelete + (attribute) => attribute.name !== attributeToDelete, ); const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ @@ -78,7 +78,7 @@ export const AttributesTab = () => { { successMessageKey: "realm-settings:deleteAttributeSuccess", errorMessageKey: "realm-settings:deleteAttributeError", - } + }, ); setAttributeToDelete(""); }, @@ -118,7 +118,9 @@ export const AttributesTab = () => { setData( filter === "allGroups" ? config.attributes - : config.attributes?.filter((attr) => attr.group === filter) + : config.attributes?.filter( + (attr) => attr.group === filter, + ), ); toggleIsFilterTypeDropdownOpen(); }} @@ -178,7 +180,7 @@ export const AttributesTab = () => { toAttribute({ realm: realmName, attributeName: component.name, - }) + }), ); }, }, diff --git a/js/apps/admin-ui/src/realm-settings/user-profile/UserProfileContext.tsx b/js/apps/admin-ui/src/realm-settings/user-profile/UserProfileContext.tsx index 29af81c94308..c339c0574ab0 100644 --- a/js/apps/admin-ui/src/realm-settings/user-profile/UserProfileContext.tsx +++ b/js/apps/admin-ui/src/realm-settings/user-profile/UserProfileContext.tsx @@ -17,7 +17,7 @@ type UserProfileProps = { export type SaveCallback = ( updatedConfig: UserProfileConfig, - options?: SaveOptions + options?: SaveOptions, ) => Promise; export type SaveOptions = { @@ -40,7 +40,7 @@ export const UserProfileProvider = ({ children }: PropsWithChildren) => { useFetch( () => adminClient.users.getProfile({ realm }), (config) => setConfig(config), - [refreshCount] + [refreshCount], ); const save: SaveCallback = async (updatedConfig, options) => { @@ -56,7 +56,7 @@ export const UserProfileProvider = ({ children }: PropsWithChildren) => { setRefreshCount(refreshCount + 1); addAlert( t(options?.successMessageKey ?? "realm-settings:userProfileSuccess"), - AlertVariant.success + AlertVariant.success, ); return true; @@ -64,7 +64,7 @@ export const UserProfileProvider = ({ children }: PropsWithChildren) => { setIsSaving(false); addError( options?.errorMessageKey ?? "realm-settings:userProfileError", - error + error, ); return false; diff --git a/js/apps/admin-ui/src/realm-settings/user-profile/attribute/AddValidatorDialog.tsx b/js/apps/admin-ui/src/realm-settings/user-profile/attribute/AddValidatorDialog.tsx index 7bf45c63fb1c..5cc44899bb79 100644 --- a/js/apps/admin-ui/src/realm-settings/user-profile/attribute/AddValidatorDialog.tsx +++ b/js/apps/admin-ui/src/realm-settings/user-profile/attribute/AddValidatorDialog.tsx @@ -68,7 +68,7 @@ export const AddValidatorDialog = ({
validator.key + (validator) => validator.key, )} onChange={setSelectedValidator} /> diff --git a/js/apps/admin-ui/src/realm-settings/user-profile/attribute/AttributeGeneralSettings.tsx b/js/apps/admin-ui/src/realm-settings/user-profile/attribute/AttributeGeneralSettings.tsx index 7865072cad0b..cc1e9567ec2f 100644 --- a/js/apps/admin-ui/src/realm-settings/user-profile/attribute/AttributeGeneralSettings.tsx +++ b/js/apps/admin-ui/src/realm-settings/user-profile/attribute/AttributeGeneralSettings.tsx @@ -171,7 +171,7 @@ export const AttributeGeneralSettings = () => { if (value) { form.setValue( "selector.scopes", - clientScopes.map((s) => s.name) + clientScopes.map((s) => s.name), ); } else { form.setValue("selector.scopes", []); @@ -191,7 +191,7 @@ export const AttributeGeneralSettings = () => { } else { form.setValue( "selector.scopes", - clientScopes.map((s) => s.name) + clientScopes.map((s) => s.name), ); } }} @@ -321,7 +321,7 @@ export const AttributeGeneralSettings = () => { if (value) { form.setValue( "required.scopes", - clientScopes.map((s) => s.name) + clientScopes.map((s) => s.name), ); } else { form.setValue("required.scopes", []); @@ -341,7 +341,7 @@ export const AttributeGeneralSettings = () => { } else { form.setValue( "required.scopes", - clientScopes.map((s) => s.name) + clientScopes.map((s) => s.name), ); } }} @@ -372,7 +372,7 @@ export const AttributeGeneralSettings = () => { if (field.value) { changedValue = field.value.includes(option) ? field.value.filter( - (item: string) => item !== option + (item: string) => item !== option, ) : [...field.value, option]; } else { diff --git a/js/apps/admin-ui/src/realm-settings/user-profile/attribute/AttributeValidations.tsx b/js/apps/admin-ui/src/realm-settings/user-profile/attribute/AttributeValidations.tsx index fb5edf69d359..d1a23903e726 100644 --- a/js/apps/admin-ui/src/realm-settings/user-profile/attribute/AttributeValidations.tsx +++ b/js/apps/admin-ui/src/realm-settings/user-profile/attribute/AttributeValidations.tsx @@ -51,7 +51,7 @@ export const AttributeValidations = () => { continueButtonVariant: ButtonVariant.danger, onConfirm: async () => { const updatedValidators = validators.filter( - (validator) => validator.key !== validatorToDelete + (validator) => validator.key !== validatorToDelete, ); setValue("validations", [...updatedValidators]); diff --git a/js/apps/admin-ui/src/realm-settings/user-profile/attribute/ValidatorSelect.tsx b/js/apps/admin-ui/src/realm-settings/user-profile/attribute/ValidatorSelect.tsx index b5bd8b454df3..4e15daaed1eb 100644 --- a/js/apps/admin-ui/src/realm-settings/user-profile/attribute/ValidatorSelect.tsx +++ b/js/apps/admin-ui/src/realm-settings/user-profile/attribute/ValidatorSelect.tsx @@ -19,7 +19,7 @@ export const ValidatorSelect = ({ useServerInfo().componentTypes?.["org.keycloak.validate.Validator"] || []; const validators = useMemo( () => allValidator.filter(({ id }) => !selectedValidators.includes(id)), - [selectedValidators] + [selectedValidators], ); const [open, toggle] = useToggle(); const [value, setValue] = useState(); diff --git a/js/apps/admin-ui/src/root/AuthWall.tsx b/js/apps/admin-ui/src/root/AuthWall.tsx index 6997d64d65b6..9333fdf09d3a 100644 --- a/js/apps/admin-ui/src/root/AuthWall.tsx +++ b/js/apps/admin-ui/src/root/AuthWall.tsx @@ -6,7 +6,7 @@ import { useAccess } from "../context/access/Access"; function hasProp( data: object, - prop: K + prop: K, ): data is Record { return prop in data; } diff --git a/js/apps/admin-ui/src/sessions/RevocationModal.tsx b/js/apps/admin-ui/src/sessions/RevocationModal.tsx index e6543e2f18e7..30aac0042f16 100644 --- a/js/apps/admin-ui/src/sessions/RevocationModal.tsx +++ b/js/apps/admin-ui/src/sessions/RevocationModal.tsx @@ -53,7 +53,7 @@ export const RevocationModal = ({ (realm) => { setRealm(realm); }, - [key] + [key], ); const parseResult = (result: GlobalRequestResult, prefixKey: string) => { @@ -67,20 +67,20 @@ export const RevocationModal = ({ t("clients:" + prefixKey + "Success", { successNodes: result.successRequests, }), - AlertVariant.success + AlertVariant.success, ); addAlert( t("clients:" + prefixKey + "Fail", { failedNodes: result.failedRequests, }), - AlertVariant.danger + AlertVariant.danger, ); } else { addAlert( t("clients:" + prefixKey + "Success", { successNodes: result.successRequests, }), - AlertVariant.success + AlertVariant.success, ); } }; @@ -92,7 +92,7 @@ export const RevocationModal = ({ { realm: realmName, notBefore: Date.now() / 1000, - } + }, ); addAlert(t("notBeforeSuccess"), AlertVariant.success); @@ -108,7 +108,7 @@ export const RevocationModal = ({ { realm: realmName, notBefore: 0, - } + }, ); addAlert(t("notBeforeClearedSuccess"), AlertVariant.success); refresh(); diff --git a/js/apps/admin-ui/src/sessions/SessionsSection.tsx b/js/apps/admin-ui/src/sessions/SessionsSection.tsx index bebb8498cf85..b02a4e2b4cf2 100644 --- a/js/apps/admin-ui/src/sessions/SessionsSection.tsx +++ b/js/apps/admin-ui/src/sessions/SessionsSection.tsx @@ -84,7 +84,7 @@ export default function SessionsSection() { max: `${max}`, type: filterType, search: search || "", - } + }, ); setNoSessions(data.length === 0); return data; diff --git a/js/apps/admin-ui/src/sessions/SessionsTable.tsx b/js/apps/admin-ui/src/sessions/SessionsTable.tsx index 19180eafd270..360d38cb87ae 100644 --- a/js/apps/admin-ui/src/sessions/SessionsTable.tsx +++ b/js/apps/admin-ui/src/sessions/SessionsTable.tsx @@ -119,7 +119,7 @@ export default function SessionsTable({ ]; return defaultColumns.filter( - ({ name }) => !hiddenColumns.includes(name as ColumnName) + ({ name }) => !hiddenColumns.includes(name as ColumnName), ); }, [realm, hiddenColumns]); diff --git a/js/apps/admin-ui/src/user-federation/ManagePriorityDialog.tsx b/js/apps/admin-ui/src/user-federation/ManagePriorityDialog.tsx index 35f1a1dc52d6..225950a05c4f 100644 --- a/js/apps/admin-ui/src/user-federation/ManagePriorityDialog.tsx +++ b/js/apps/admin-ui/src/user-federation/ManagePriorityDialog.tsx @@ -36,7 +36,7 @@ export const ManagePriorityDialog = ({ const [id, setId] = useState(""); const [liveText, setLiveText] = useState(""); const [order, setOrder] = useState( - components.map((component) => component.name!) + components.map((component) => component.name!), ); const onDragStart = (id: string) => { @@ -73,7 +73,7 @@ export const ManagePriorityDialog = ({ component.config!.priority = [index.toString()]; return adminClient.components.update( { id: component.id! }, - component + component, ); }); diff --git a/js/apps/admin-ui/src/user-federation/UserFederationKerberosSettings.tsx b/js/apps/admin-ui/src/user-federation/UserFederationKerberosSettings.tsx index 66a7ba35ee37..2217bcb88938 100644 --- a/js/apps/admin-ui/src/user-federation/UserFederationKerberosSettings.tsx +++ b/js/apps/admin-ui/src/user-federation/UserFederationKerberosSettings.tsx @@ -43,7 +43,7 @@ export default function UserFederationKerberosSettings() { throw new Error(t("common:notFound")); } }, - [] + [], ); const setupForm = (component: ComponentRepresentation) => { diff --git a/js/apps/admin-ui/src/user-federation/UserFederationLdapForm.tsx b/js/apps/admin-ui/src/user-federation/UserFederationLdapForm.tsx index 5935c2bba7db..ccac8f70e7c1 100644 --- a/js/apps/admin-ui/src/user-federation/UserFederationLdapForm.tsx +++ b/js/apps/admin-ui/src/user-federation/UserFederationLdapForm.tsx @@ -91,7 +91,7 @@ export const UserFederationLdapForm = ({ }; export function serializeFormData( - formData: LdapComponentRepresentation + formData: LdapComponentRepresentation, ): LdapComponentRepresentation { const { config } = formData; diff --git a/js/apps/admin-ui/src/user-federation/UserFederationLdapSettings.tsx b/js/apps/admin-ui/src/user-federation/UserFederationLdapSettings.tsx index be2f1d99957b..fc3cf8b3f2c1 100644 --- a/js/apps/admin-ui/src/user-federation/UserFederationLdapSettings.tsx +++ b/js/apps/admin-ui/src/user-federation/UserFederationLdapSettings.tsx @@ -54,7 +54,7 @@ export default function UserFederationLdapSettings() { setComponent(component); setupForm(component); }, - [id, refreshCount] + [id, refreshCount], ); const useTab = (tab: UserFederationLdapTab) => @@ -67,12 +67,12 @@ export default function UserFederationLdapSettings() { form.reset(component); form.setValue( "config.periodicChangedUsersSync", - component.config?.["changedSyncPeriod"]?.[0] !== "-1" + component.config?.["changedSyncPeriod"]?.[0] !== "-1", ); form.setValue( "config.periodicFullSync", - component.config?.["fullSyncPeriod"]?.[0] !== "-1" + component.config?.["fullSyncPeriod"]?.[0] !== "-1", ); }; @@ -80,7 +80,7 @@ export default function UserFederationLdapSettings() { try { await adminClient.components.update( { id: id! }, - serializeFormData(formData) + serializeFormData(formData), ); addAlert(t("saveSuccess"), AlertVariant.success); refresh(); diff --git a/js/apps/admin-ui/src/user-federation/UserFederationSection.tsx b/js/apps/admin-ui/src/user-federation/UserFederationSection.tsx index 4cd8e213f452..c96bace3392f 100644 --- a/js/apps/admin-ui/src/user-federation/UserFederationSection.tsx +++ b/js/apps/admin-ui/src/user-federation/UserFederationSection.tsx @@ -67,7 +67,7 @@ export default function UserFederationSection() { (userFederations) => { setUserFederations(userFederations); }, - [key] + [key], ); const ufAddProviderDropdownItems = useMemo( @@ -84,7 +84,7 @@ export default function UserFederationSection() { : toUpperCase(p.id)} )), - [] + [], ); const lowerButtonProps = { @@ -211,7 +211,7 @@ export default function UserFederationSection() { key={p.id} onClick={() => navigate( - toNewCustomUserFederation({ realm, providerId: p.id! }) + toNewCustomUserFederation({ realm, providerId: p.id! }), ) } data-testid={`${p.id}-card`} diff --git a/js/apps/admin-ui/src/user-federation/custom/CustomProviderSettings.tsx b/js/apps/admin-ui/src/user-federation/custom/CustomProviderSettings.tsx index 7649df9ecc82..ef15d0950fb2 100644 --- a/js/apps/admin-ui/src/user-federation/custom/CustomProviderSettings.tsx +++ b/js/apps/admin-ui/src/user-federation/custom/CustomProviderSettings.tsx @@ -69,7 +69,7 @@ export default function CustomProviderSettings() { throw new Error(t("common:notFound")); } }, - [] + [], ); useFetch( @@ -78,7 +78,7 @@ export default function CustomProviderSettings() { realm: realmName, }), (realm) => setParentId(realm?.id!), - [] + [], ); const save = async (component: ComponentRepresentation) => { @@ -88,7 +88,7 @@ export default function CustomProviderSettings() { Object.entries(component.config || {}).map(([key, value]) => [ key, Array.isArray(value) ? value : [value], - ]) + ]), ), providerId, providerType: "org.keycloak.storage.UserStorageProvider", diff --git a/js/apps/admin-ui/src/user-federation/kerberos/KerberosSettingsRequired.tsx b/js/apps/admin-ui/src/user-federation/kerberos/KerberosSettingsRequired.tsx index d3349909177c..b0ebb4a32e18 100644 --- a/js/apps/admin-ui/src/user-federation/kerberos/KerberosSettingsRequired.tsx +++ b/js/apps/admin-ui/src/user-federation/kerberos/KerberosSettingsRequired.tsx @@ -44,7 +44,7 @@ export const KerberosSettingsRequired = ({ useFetch( () => adminClient.realms.findOne({ realm }), (result) => form.setValue("parentId", result!.id), - [] + [], ); return ( @@ -253,7 +253,7 @@ export const KerberosSettingsRequired = ({ labelIcon={ diff --git a/js/apps/admin-ui/src/user-federation/ldap/LdapSettingsAdvanced.tsx b/js/apps/admin-ui/src/user-federation/ldap/LdapSettingsAdvanced.tsx index 9d125c93d13b..d048c236d3bc 100644 --- a/js/apps/admin-ui/src/user-federation/ldap/LdapSettingsAdvanced.tsx +++ b/js/apps/admin-ui/src/user-federation/ldap/LdapSettingsAdvanced.tsx @@ -37,11 +37,11 @@ export const LdapSettingsAdvanced = ({ const settings = convertFormToSettings(form); const ldapOids = await adminClient.realms.ldapServerCapabilities( { realm }, - { ...settings, componentId: id } + { ...settings, componentId: id }, ); addAlert(t("testSuccess")); const passwordModifyOid = ldapOids.filter( - (id: { oid: string }) => id.oid === PASSWORD_MODIFY_OID + (id: { oid: string }) => id.oid === PASSWORD_MODIFY_OID, ); form.setValue("config.usePasswordModifyExtendedOp", [ (passwordModifyOid.length > 0).toString(), diff --git a/js/apps/admin-ui/src/user-federation/ldap/LdapSettingsConnection.tsx b/js/apps/admin-ui/src/user-federation/ldap/LdapSettingsConnection.tsx index 4ccc717db8d3..2caa7023244f 100644 --- a/js/apps/admin-ui/src/user-federation/ldap/LdapSettingsConnection.tsx +++ b/js/apps/admin-ui/src/user-federation/ldap/LdapSettingsConnection.tsx @@ -70,7 +70,7 @@ export const LdapSettingsConnection = ({ const settings = convertFormToSettings(form); await adminClient.realms.testLDAPConnection( { realm }, - { ...settings, action: testType, componentId: id } + { ...settings, action: testType, componentId: id }, ); addAlert(t("testSuccess"), AlertVariant.success); } catch (error) { @@ -95,7 +95,7 @@ export const LdapSettingsConnection = ({ @@ -106,7 +106,7 @@ export const LdapSettingsConnection = ({ labelIcon={ diff --git a/js/apps/admin-ui/src/user-federation/ldap/LdapSettingsGeneral.tsx b/js/apps/admin-ui/src/user-federation/ldap/LdapSettingsGeneral.tsx index f13d146d5f94..273ec7acd107 100644 --- a/js/apps/admin-ui/src/user-federation/ldap/LdapSettingsGeneral.tsx +++ b/js/apps/admin-ui/src/user-federation/ldap/LdapSettingsGeneral.tsx @@ -37,7 +37,7 @@ export const LdapSettingsGeneral = ({ useFetch( () => adminClient.realms.findOne({ realm }), (result) => form.setValue("parentId", result!.id), - [] + [], ); const [isVendorDropdownOpen, setIsVendorDropdownOpen] = useState(false); @@ -49,7 +49,7 @@ export const LdapSettingsGeneral = ({ form.setValue("config.uuidLDAPAttribute[0]", "objectGUID"); form.setValue( "config.userObjectClasses[0]", - "person, organizationalPerson, user" + "person, organizationalPerson, user", ); break; case "rhds": @@ -58,7 +58,7 @@ export const LdapSettingsGeneral = ({ form.setValue("config.uuidLDAPAttribute[0]", "nsuniqueid"); form.setValue( "config.userObjectClasses[0]", - "inetOrgPerson, organizationalPerson" + "inetOrgPerson, organizationalPerson", ); break; case "tivoli": @@ -67,7 +67,7 @@ export const LdapSettingsGeneral = ({ form.setValue("config.uuidLDAPAttribute[0]", "uniqueidentifier"); form.setValue( "config.userObjectClasses[0]", - "inetOrgPerson, organizationalPerson" + "inetOrgPerson, organizationalPerson", ); break; case "edirectory": @@ -76,7 +76,7 @@ export const LdapSettingsGeneral = ({ form.setValue("config.uuidLDAPAttribute[0]", "guid"); form.setValue( "config.userObjectClasses[0]", - "inetOrgPerson, organizationalPerson" + "inetOrgPerson, organizationalPerson", ); break; case "other": @@ -85,7 +85,7 @@ export const LdapSettingsGeneral = ({ form.setValue("config.uuidLDAPAttribute[0]", "entryUUID"); form.setValue( "config.userObjectClasses[0]", - "inetOrgPerson, organizationalPerson" + "inetOrgPerson, organizationalPerson", ); break; default: diff --git a/js/apps/admin-ui/src/user-federation/ldap/LdapSettingsKerberosIntegration.tsx b/js/apps/admin-ui/src/user-federation/ldap/LdapSettingsKerberosIntegration.tsx index 4995c0af6ee9..c6254f323165 100644 --- a/js/apps/admin-ui/src/user-federation/ldap/LdapSettingsKerberosIntegration.tsx +++ b/js/apps/admin-ui/src/user-federation/ldap/LdapSettingsKerberosIntegration.tsx @@ -43,7 +43,7 @@ export const LdapSettingsKerberosIntegration = ({ labelIcon={ @@ -222,7 +222,7 @@ export const LdapSettingsKerberosIntegration = ({ labelIcon={ diff --git a/js/apps/admin-ui/src/user-federation/ldap/mappers/LdapMapperDetails.tsx b/js/apps/admin-ui/src/user-federation/ldap/mappers/LdapMapperDetails.tsx index a3aeb77472f6..c3dc22b76a95 100644 --- a/js/apps/admin-ui/src/user-federation/ldap/mappers/LdapMapperDetails.tsx +++ b/js/apps/admin-ui/src/user-federation/ldap/mappers/LdapMapperDetails.tsx @@ -73,7 +73,7 @@ export default function LdapMapperDetails() { if (fetchedMapper) setupForm(fetchedMapper); }, - [] + [], ); const setupForm = (mapper: ComponentRepresentation) => { @@ -90,7 +90,7 @@ export default function LdapMapperDetails() { result[key] = Array.isArray(value) ? value : [value]; return result; }, - {} as Record + {} as Record, ), }; @@ -98,7 +98,7 @@ export default function LdapMapperDetails() { if (mapperId === "new") { await adminClient.components.create(map); navigate( - toUserFederationLdap({ realm, id: mapper.parentId!, tab: "mappers" }) + toUserFederationLdap({ realm, id: mapper.parentId!, tab: "mappers" }), ); } else { await adminClient.components.update({ id: mapperId }, map); @@ -108,16 +108,16 @@ export default function LdapMapperDetails() { t( mapperId === "new" ? "common:mappingCreatedSuccess" - : "common:mappingUpdatedSuccess" + : "common:mappingUpdatedSuccess", ), - AlertVariant.success + AlertVariant.success, ); } catch (error) { addError( mapperId === "new" ? "common:mappingCreatedError" : "common:mappingUpdatedError", - error + error, ); } }; @@ -132,7 +132,7 @@ export default function LdapMapperDetails() { addAlert( t("syncLDAPGroupsSuccessful", { result: result.status, - }) + }), ); } catch (error) { addError("user-federation:syncLDAPGroupsError", error); @@ -188,7 +188,7 @@ export default function LdapMapperDetails() { key="fedSync" onClick={() => sync("fedToKeycloak")} > - {t(mapper?.metadata.fedToKeycloakSyncMessage)} + {t(mapper.metadata.fedToKeycloakSyncMessage)} , ] : []), @@ -200,7 +200,7 @@ export default function LdapMapperDetails() { sync("keycloakToFed"); }} > - {t(mapper?.metadata.keycloakToFedSyncMessage)} + {t(mapper.metadata.keycloakToFedSyncMessage)} , ] : []), @@ -350,7 +350,7 @@ export default function LdapMapperDetails() { : navigate( `/${realm}/user-federation/ldap/${ mapping!.parentId - }/mappers` + }/mappers`, ) } data-testid="ldap-mapper-cancel" diff --git a/js/apps/admin-ui/src/user-federation/ldap/mappers/LdapMapperList.tsx b/js/apps/admin-ui/src/user-federation/ldap/mappers/LdapMapperList.tsx index 27b07d456638..87012df8549b 100644 --- a/js/apps/admin-ui/src/user-federation/ldap/mappers/LdapMapperList.tsx +++ b/js/apps/admin-ui/src/user-federation/ldap/mappers/LdapMapperList.tsx @@ -62,11 +62,11 @@ export const LdapMapperList = ({ toCreate, toDetail }: LdapMapperListProps) => { name: mapper.name, type: mapper.providerId, })), - mapByKey("name") - ) + mapByKey("name"), + ), ); }, - [key] + [key], ); const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ diff --git a/js/apps/admin-ui/src/user-federation/routes/CustomUserFederation.tsx b/js/apps/admin-ui/src/user-federation/routes/CustomUserFederation.tsx index df6928a2e444..750c88450e12 100644 --- a/js/apps/admin-ui/src/user-federation/routes/CustomUserFederation.tsx +++ b/js/apps/admin-ui/src/user-federation/routes/CustomUserFederation.tsx @@ -11,7 +11,7 @@ export type CustomUserFederationRouteParams = { }; const CustomProviderSettings = lazy( - () => import("../custom/CustomProviderSettings") + () => import("../custom/CustomProviderSettings"), ); export const CustomUserFederationRoute: AppRouteObject = { @@ -24,7 +24,7 @@ export const CustomUserFederationRoute: AppRouteObject = { }; export const toCustomUserFederation = ( - params: CustomUserFederationRouteParams + params: CustomUserFederationRouteParams, ): Partial => ({ pathname: generatePath(CustomUserFederationRoute.path, params), }); diff --git a/js/apps/admin-ui/src/user-federation/routes/NewCustomUserFederation.tsx b/js/apps/admin-ui/src/user-federation/routes/NewCustomUserFederation.tsx index 81d8bf732e6d..a8a35eafc5e2 100644 --- a/js/apps/admin-ui/src/user-federation/routes/NewCustomUserFederation.tsx +++ b/js/apps/admin-ui/src/user-federation/routes/NewCustomUserFederation.tsx @@ -10,7 +10,7 @@ export type NewCustomUserFederationRouteParams = { }; const CustomProviderSettings = lazy( - () => import("../custom/CustomProviderSettings") + () => import("../custom/CustomProviderSettings"), ); export const NewCustomUserFederationRoute: AppRouteObject = { @@ -23,7 +23,7 @@ export const NewCustomUserFederationRoute: AppRouteObject = { }; export const toNewCustomUserFederation = ( - params: NewCustomUserFederationRouteParams + params: NewCustomUserFederationRouteParams, ): Partial => ({ pathname: generatePath(NewCustomUserFederationRoute.path, params), }); diff --git a/js/apps/admin-ui/src/user-federation/routes/NewKerberosUserFederation.tsx b/js/apps/admin-ui/src/user-federation/routes/NewKerberosUserFederation.tsx index 083dac8d8117..bde2930bb0b9 100644 --- a/js/apps/admin-ui/src/user-federation/routes/NewKerberosUserFederation.tsx +++ b/js/apps/admin-ui/src/user-federation/routes/NewKerberosUserFederation.tsx @@ -6,7 +6,7 @@ import type { AppRouteObject } from "../../routes"; export type NewKerberosUserFederationParams = { realm: string }; const UserFederationKerberosSettings = lazy( - () => import("../UserFederationKerberosSettings") + () => import("../UserFederationKerberosSettings"), ); export const NewKerberosUserFederationRoute: AppRouteObject = { @@ -19,7 +19,7 @@ export const NewKerberosUserFederationRoute: AppRouteObject = { }; export const toNewKerberosUserFederation = ( - params: NewKerberosUserFederationParams + params: NewKerberosUserFederationParams, ): Partial => ({ pathname: generatePath(NewKerberosUserFederationRoute.path, params), }); diff --git a/js/apps/admin-ui/src/user-federation/routes/NewLdapUserFederation.tsx b/js/apps/admin-ui/src/user-federation/routes/NewLdapUserFederation.tsx index 2ba12a5b2d1b..2be4cd430a2f 100644 --- a/js/apps/admin-ui/src/user-federation/routes/NewLdapUserFederation.tsx +++ b/js/apps/admin-ui/src/user-federation/routes/NewLdapUserFederation.tsx @@ -6,7 +6,7 @@ import type { AppRouteObject } from "../../routes"; export type NewLdapUserFederationParams = { realm: string }; const CreateUserFederationLdapSettings = lazy( - () => import("../CreateUserFederationLdapSettings") + () => import("../CreateUserFederationLdapSettings"), ); export const NewLdapUserFederationRoute: AppRouteObject = { @@ -20,7 +20,7 @@ export const NewLdapUserFederationRoute: AppRouteObject = { }; export const toNewLdapUserFederation = ( - params: NewLdapUserFederationParams + params: NewLdapUserFederationParams, ): Partial => ({ pathname: generatePath(NewLdapUserFederationRoute.path, params), }); diff --git a/js/apps/admin-ui/src/user-federation/routes/UserFederation.tsx b/js/apps/admin-ui/src/user-federation/routes/UserFederation.tsx index ee0c147db38d..45ef09ee597c 100644 --- a/js/apps/admin-ui/src/user-federation/routes/UserFederation.tsx +++ b/js/apps/admin-ui/src/user-federation/routes/UserFederation.tsx @@ -17,7 +17,7 @@ export const UserFederationRoute: AppRouteObject = { }; export const toUserFederation = ( - params: UserFederationParams + params: UserFederationParams, ): Partial => ({ pathname: generatePath(UserFederationRoute.path, params), }); diff --git a/js/apps/admin-ui/src/user-federation/routes/UserFederationKerberos.tsx b/js/apps/admin-ui/src/user-federation/routes/UserFederationKerberos.tsx index abae1ca438e0..d7ae85713132 100644 --- a/js/apps/admin-ui/src/user-federation/routes/UserFederationKerberos.tsx +++ b/js/apps/admin-ui/src/user-federation/routes/UserFederationKerberos.tsx @@ -9,7 +9,7 @@ export type UserFederationKerberosParams = { }; const UserFederationKerberosSettings = lazy( - () => import("../UserFederationKerberosSettings") + () => import("../UserFederationKerberosSettings"), ); export const UserFederationKerberosRoute: AppRouteObject = { @@ -22,7 +22,7 @@ export const UserFederationKerberosRoute: AppRouteObject = { }; export const toUserFederationKerberos = ( - params: UserFederationKerberosParams + params: UserFederationKerberosParams, ): Partial => ({ pathname: generatePath(UserFederationKerberosRoute.path, params), }); diff --git a/js/apps/admin-ui/src/user-federation/routes/UserFederationLdap.tsx b/js/apps/admin-ui/src/user-federation/routes/UserFederationLdap.tsx index 047221d1530f..f9030537457b 100644 --- a/js/apps/admin-ui/src/user-federation/routes/UserFederationLdap.tsx +++ b/js/apps/admin-ui/src/user-federation/routes/UserFederationLdap.tsx @@ -12,7 +12,7 @@ export type UserFederationLdapParams = { }; const UserFederationLdapSettings = lazy( - () => import("../UserFederationLdapSettings") + () => import("../UserFederationLdapSettings"), ); export const UserFederationLdapRoute: AppRouteObject = { @@ -30,7 +30,7 @@ export const UserFederationLdapWithTabRoute: AppRouteObject = { }; export const toUserFederationLdap = ( - params: UserFederationLdapParams + params: UserFederationLdapParams, ): Partial => { const path = params.tab ? UserFederationLdapWithTabRoute.path diff --git a/js/apps/admin-ui/src/user-federation/routes/UserFederationLdapMapper.tsx b/js/apps/admin-ui/src/user-federation/routes/UserFederationLdapMapper.tsx index 99f32b7ba9af..48219df71911 100644 --- a/js/apps/admin-ui/src/user-federation/routes/UserFederationLdapMapper.tsx +++ b/js/apps/admin-ui/src/user-federation/routes/UserFederationLdapMapper.tsx @@ -10,7 +10,7 @@ export type UserFederationLdapMapperParams = { }; const LdapMapperDetails = lazy( - () => import("../ldap/mappers/LdapMapperDetails") + () => import("../ldap/mappers/LdapMapperDetails"), ); export const UserFederationLdapMapperRoute: AppRouteObject = { @@ -23,7 +23,7 @@ export const UserFederationLdapMapperRoute: AppRouteObject = { }; export const toUserFederationLdapMapper = ( - params: UserFederationLdapMapperParams + params: UserFederationLdapMapperParams, ): Partial => ({ pathname: generatePath(UserFederationLdapMapperRoute.path, params), }); diff --git a/js/apps/admin-ui/src/user-federation/routes/UserFederationsKerberos.tsx b/js/apps/admin-ui/src/user-federation/routes/UserFederationsKerberos.tsx index 065c8977747f..7f4a41871b0f 100644 --- a/js/apps/admin-ui/src/user-federation/routes/UserFederationsKerberos.tsx +++ b/js/apps/admin-ui/src/user-federation/routes/UserFederationsKerberos.tsx @@ -16,7 +16,7 @@ export const UserFederationsKerberosRoute: AppRouteObject = { }; export const toUserFederationsKerberos = ( - params: UserFederationsKerberosParams + params: UserFederationsKerberosParams, ): Partial => ({ pathname: generatePath(UserFederationsKerberosRoute.path, params), }); diff --git a/js/apps/admin-ui/src/user-federation/routes/UserFederationsLdap.tsx b/js/apps/admin-ui/src/user-federation/routes/UserFederationsLdap.tsx index ad0d4a565223..b02fda3ecc6b 100644 --- a/js/apps/admin-ui/src/user-federation/routes/UserFederationsLdap.tsx +++ b/js/apps/admin-ui/src/user-federation/routes/UserFederationsLdap.tsx @@ -16,7 +16,7 @@ export const UserFederationsLdapRoute: AppRouteObject = { }; export const toUserFederationsLdap = ( - params: UserFederationsLdapParams + params: UserFederationsLdapParams, ): Partial => ({ pathname: generatePath(UserFederationsLdapRoute.path, params), }); diff --git a/js/apps/admin-ui/src/user-federation/shared/ExtendedHeader.tsx b/js/apps/admin-ui/src/user-federation/shared/ExtendedHeader.tsx index 43aa5bc9b837..e95029c7232d 100644 --- a/js/apps/admin-ui/src/user-federation/shared/ExtendedHeader.tsx +++ b/js/apps/admin-ui/src/user-federation/shared/ExtendedHeader.tsx @@ -81,7 +81,7 @@ export const ExtendedHeader = ({ addAlert( t("syncUsersSuccess") + `${response.added} users added, ${response.updated} users updated, ${response.removed} users removed, ${response.failed} users failed.`, - AlertVariant.success + AlertVariant.success, ); } } @@ -103,7 +103,7 @@ export const ExtendedHeader = ({ addAlert( t("syncUsersSuccess") + `${response.added} users added, ${response.updated} users updated, ${response.removed} users removed, ${response.failed} users failed.`, - AlertVariant.success + AlertVariant.success, ); } } diff --git a/js/apps/admin-ui/src/user-federation/shared/SettingsCache.tsx b/js/apps/admin-ui/src/user-federation/shared/SettingsCache.tsx index e04ab3bd5ca8..6299c0d89a8f 100644 --- a/js/apps/admin-ui/src/user-federation/shared/SettingsCache.tsx +++ b/js/apps/admin-ui/src/user-federation/shared/SettingsCache.tsx @@ -50,7 +50,7 @@ const CacheFields = ({ form }: { form: UseFormReturn }) => { hourOptions.push( {hourDisplay} - + , ); } @@ -69,7 +69,7 @@ const CacheFields = ({ form }: { form: UseFormReturn }) => { minuteOptions.push( {minuteDisplay} - + , ); } diff --git a/js/apps/admin-ui/src/user/EditUser.tsx b/js/apps/admin-ui/src/user/EditUser.tsx index 3b1b5ce3601d..428917b8400b 100644 --- a/js/apps/admin-ui/src/user/EditUser.tsx +++ b/js/apps/admin-ui/src/user/EditUser.tsx @@ -75,7 +75,7 @@ export default function EditUser() { setUser(user); setBruteForced(bruteForced); }, - [refreshCount] + [refreshCount], ); if (!user || !bruteForced) { @@ -115,7 +115,7 @@ const EditUserForm = ({ user, bruteForced, refresh }: EditUserFormProps) => { } setRealmRepresentattion(realm); }, - [] + [], ); const isFeatureEnabled = useIsFeatureEnabled(); @@ -152,7 +152,7 @@ const EditUserForm = ({ user, bruteForced, refresh }: EditUserFormProps) => { ...formUser, username: formUser.username?.trim(), attributes: { ...user.attributes, ...formUser.attributes }, - } + }, ); addAlert(t("userSaved"), AlertVariant.success); refresh(); @@ -189,7 +189,7 @@ const EditUserForm = ({ user, bruteForced, refresh }: EditUserFormProps) => { try { const data = await adminClient.users.impersonation( { id: user.id! }, - { user: user.id!, realm } + { user: user.id!, realm }, ); if (data.sameRealm) { window.location = data.redirect; diff --git a/js/apps/admin-ui/src/user/FederatedUserLink.tsx b/js/apps/admin-ui/src/user/FederatedUserLink.tsx index 47794066aac2..6b8b7e0db80d 100644 --- a/js/apps/admin-ui/src/user/FederatedUserLink.tsx +++ b/js/apps/admin-ui/src/user/FederatedUserLink.tsx @@ -30,7 +30,7 @@ export const FederatedUserLink = ({ user }: FederatedUserLinkProps) => { id: (user.federationLink || user.origin)!, }), setComponent, - [] + [], ); if (!component) return null; diff --git a/js/apps/admin-ui/src/user/UserCredentials.tsx b/js/apps/admin-ui/src/user/UserCredentials.tsx index 355688e5b48a..87900a473ab0 100644 --- a/js/apps/admin-ui/src/user/UserCredentials.tsx +++ b/js/apps/admin-ui/src/user/UserCredentials.tsx @@ -93,21 +93,21 @@ export const UserCredentials = ({ user }: UserCredentialsProps) => { }, Object.create(null)); const groupedCredentialsArray = Object.keys(groupedCredentials).map( - (key) => ({ key, value: groupedCredentials[key] }) + (key) => ({ key, value: groupedCredentials[key] }), ); setGroupedUserCredentials( groupedCredentialsArray.map((groupedCredential) => ({ ...groupedCredential, isExpanded: false, - })) + })), ); }, - [key] + [key], ); const passwordTypeFinder = userCredentials.find( - (credential) => credential.type === "password" + (credential) => credential.type === "password", ); const toggleModal = () => setIsOpen(!isOpen); @@ -179,7 +179,7 @@ export const UserCredentials = ({ user }: UserCredentialsProps) => { ? groupedCredential.value.map((c) => c.id!) : []), ]), - [groupedUserCredentials] + [groupedUserCredentials], ); const onDragStart = (evt: ReactDragEvent) => { @@ -267,7 +267,7 @@ export const UserCredentials = ({ user }: UserCredentialsProps) => { } else { const dragId = curListItem.id; const draggingToItemIndex = Array.from( - bodyRef.current?.children || [] + bodyRef.current?.children || [], ).findIndex((item) => item.id === dragId); if (draggingToItemIndex === state.draggingToItemIndex) { return; @@ -275,7 +275,7 @@ export const UserCredentials = ({ user }: UserCredentialsProps) => { const tempItemOrder = moveItem( itemOrder, state.draggedItemId, - draggingToItemIndex + draggingToItemIndex, ); move(tempItemOrder); setState({ @@ -337,7 +337,7 @@ export const UserCredentials = ({ user }: UserCredentialsProps) => { useFetch( () => adminClient.users.getUserStorageCredentialTypes({ id: user.id! }), setCredentialTypes, - [] + [], ); if (!credentialTypes) { @@ -434,7 +434,7 @@ export const UserCredentials = ({ user }: UserCredentialsProps) => { } draggableRow={{ id: `draggable-row-${groupedCredential.value.map( - ({ id }) => id + ({ id }) => id, )}`, }} /> @@ -452,7 +452,7 @@ export const UserCredentials = ({ user }: UserCredentialsProps) => { ...credential, isExpanded: !credential.isExpanded, } - : credential + : credential, ); setGroupedUserCredentials(rows); }, @@ -488,7 +488,7 @@ export const UserCredentials = ({ user }: UserCredentialsProps) => { className="kc-draggable-dropdown-type-icon" draggableRow={{ id: `draggable-row-${groupedCredential.value.map( - ({ id }) => id + ({ id }) => id, )}`, }} /> diff --git a/js/apps/admin-ui/src/user/UserForm.tsx b/js/apps/admin-ui/src/user/UserForm.tsx index 99d0356cfc7f..f49d071991df 100644 --- a/js/apps/admin-ui/src/user/UserForm.tsx +++ b/js/apps/admin-ui/src/user/UserForm.tsx @@ -109,7 +109,7 @@ export const UserForm = ({ } = useFormContext(); const watchUsernameInput = watch("username"); const [selectedGroups, setSelectedGroups] = useState( - [] + [], ); const [open, setOpen] = useState(false); const [locked, setLocked] = useState(isLocked); diff --git a/js/apps/admin-ui/src/user/UserGroups.tsx b/js/apps/admin-ui/src/user/UserGroups.tsx index 0a276d12706b..151594384c8e 100644 --- a/js/apps/admin-ui/src/user/UserGroups.tsx +++ b/js/apps/admin-ui/src/user/UserGroups.tsx @@ -35,7 +35,7 @@ export const UserGroups = ({ user }: UserGroupsProps) => { const refresh = () => setKey(key + 1); const [selectedGroups, setSelectedGroups] = useState( - [] + [], ); const [isDirectMembership, setDirectMembership] = useState(true); @@ -79,7 +79,7 @@ export const UserGroups = ({ user }: UserGroupsProps) => { ...paths.map((p) => ({ name: p, path: g.path?.substring(0, g.path.indexOf(p) + p.length), - })) + })), ); }); @@ -109,8 +109,8 @@ export const UserGroups = ({ user }: UserGroupsProps) => { adminClient.users.delFromGroup({ id: user.id!, groupId: group.id!, - }) - ) + }), + ), ); addAlert(t("removedGroupMembership"), AlertVariant.success); @@ -133,8 +133,8 @@ export const UserGroups = ({ user }: UserGroupsProps) => { adminClient.users.addToGroup({ id: user.id!, groupId: group.id!, - }) - ) + }), + ), ); addAlert(t("addedGroupMembership"), AlertVariant.success); @@ -175,7 +175,7 @@ export const UserGroups = ({ user }: UserGroupsProps) => { isDirectMembership ? setSelectedGroups(groups) : setSelectedGroups( - intersectionBy(groups, directMembershipList, "id") + intersectionBy(groups, directMembershipList, "id"), ) } isRowDisabled={(group) => diff --git a/js/apps/admin-ui/src/user/UserIdPModal.tsx b/js/apps/admin-ui/src/user/UserIdPModal.tsx index 89e090b7703f..eebc06a24e0c 100644 --- a/js/apps/admin-ui/src/user/UserIdPModal.tsx +++ b/js/apps/admin-ui/src/user/UserIdPModal.tsx @@ -41,7 +41,7 @@ export const UserIdpModal = ({ }); const onSubmit = async ( - federatedIdentity: FederatedIdentityRepresentation + federatedIdentity: FederatedIdentityRepresentation, ) => { try { await adminClient.users.addToFederatedIdentity({ diff --git a/js/apps/admin-ui/src/user/UserIdentityProviderLinks.tsx b/js/apps/admin-ui/src/user/UserIdentityProviderLinks.tsx index 67687bde35cc..0ac1acfc1b88 100644 --- a/js/apps/admin-ui/src/user/UserIdentityProviderLinks.tsx +++ b/js/apps/admin-ui/src/user/UserIdentityProviderLinks.tsx @@ -57,7 +57,7 @@ export const UserIdentityProviderLinks = ({ })) as WithProviderId[]; for (const element of allFedIds) { element.providerId = allProviders.find( - (item) => item.alias === element.identityProvider + (item) => item.alias === element.identityProvider, )?.providerId!; } @@ -74,11 +74,11 @@ export const UserIdentityProviderLinks = ({ const availableIdPsLoader = async () => { const linkedNames = (await getFederatedIdentities()).map( - (x) => x.identityProvider + (x) => x.identityProvider, ); return (await getAvailableIdPs())?.filter( - (item) => !linkedNames.includes(item.alias) + (item) => !linkedNames.includes(item.alias), )!; }; @@ -122,7 +122,7 @@ export const UserIdentityProviderLinks = ({ const badgeRenderer1 = (idp: FederatedIdentityRepresentation) => { const groupName = identityProviders?.find( - (provider) => provider["id"] === idp.identityProvider + (provider) => provider["id"] === idp.identityProvider, )?.groupName!; return (

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

- From caafd657a9e45a9a3fbecd42594a53a6e4f57384 Mon Sep 17 00:00:00 2001 From: Jean-Francois Denise Date: Thu, 24 Aug 2023 10:03:19 +0200 Subject: [PATCH 120/135] Some more fixes for issue22623 (cherry picked from commit 7aec0031b82628e2ba4becd04558235a526aae23) --- .../saml-adapter-galleon-pack/pom.xml | 1 + .../src/main/resources/license/licenses.xml | 30 +++++++++---------- .../content/docs/licenses/mit.txt | 21 ------------- 3 files changed, 16 insertions(+), 36 deletions(-) delete mode 100644 distribution/galleon-feature-packs/saml-adapter-galleon-pack/src/main/resources/packages/docs.licenses/content/docs/licenses/mit.txt diff --git a/distribution/galleon-feature-packs/saml-adapter-galleon-pack/pom.xml b/distribution/galleon-feature-packs/saml-adapter-galleon-pack/pom.xml index 344c966fd75a..781e426c7382 100644 --- a/distribution/galleon-feature-packs/saml-adapter-galleon-pack/pom.xml +++ b/distribution/galleon-feature-packs/saml-adapter-galleon-pack/pom.xml @@ -270,6 +270,7 @@ ${basedir}/target/resources/license/licenses.xml ${license.directory}/keycloak-saml-adapter-galleon-pack-licenses.xml + wildfly-common-ee-dependency-management|wildfly-ee-galleon-pack|wildfly-legacy-ee-bom|wildfly-standard-ee-bom\z diff --git a/distribution/galleon-feature-packs/saml-adapter-galleon-pack/src/main/resources/license/licenses.xml b/distribution/galleon-feature-packs/saml-adapter-galleon-pack/src/main/resources/license/licenses.xml index 938a11b5ccd6..e105fdd61f95 100644 --- a/distribution/galleon-feature-packs/saml-adapter-galleon-pack/src/main/resources/license/licenses.xml +++ b/distribution/galleon-feature-packs/saml-adapter-galleon-pack/src/main/resources/license/licenses.xml @@ -6,7 +6,7 @@ keycloak-adapter-spi - Apache Software License 2.0 + Apache License 2.0 https://raw.githubusercontent.com/keycloak/keycloak/999-SNAPSHOT/LICENSE.txt @@ -16,7 +16,7 @@ keycloak-undertow-adapter-spi - Apache Software License 2.0 + Apache License 2.0 https://raw.githubusercontent.com/keycloak/keycloak/999-SNAPSHOT/LICENSE.txt @@ -26,7 +26,7 @@ keycloak-common - Apache Software License 2.0 + Apache License 2.0 https://raw.githubusercontent.com/keycloak/keycloak/999-SNAPSHOT/LICENSE.txt @@ -36,7 +36,7 @@ keycloak-core - Apache Software License 2.0 + Apache License 2.0 https://raw.githubusercontent.com/keycloak/keycloak/999-SNAPSHOT/LICENSE.txt @@ -46,7 +46,7 @@ keycloak-crypto-default - Apache Software License 2.0 + Apache License 2.0 https://raw.githubusercontent.com/keycloak/keycloak/999-SNAPSHOT/LICENSE.txt @@ -56,7 +56,7 @@ keycloak-jboss-adapter-core - Apache Software License 2.0 + Apache License 2.0 https://raw.githubusercontent.com/keycloak/keycloak/999-SNAPSHOT/LICENSE.txt @@ -66,7 +66,7 @@ keycloak-saml-adapter-api-public - Apache Software License 2.0 + Apache License 2.0 https://raw.githubusercontent.com/keycloak/keycloak/999-SNAPSHOT/LICENSE.txt @@ -76,7 +76,7 @@ keycloak-saml-adapter-core - Apache Software License 2.0 + Apache License 2.0 https://raw.githubusercontent.com/keycloak/keycloak/999-SNAPSHOT/LICENSE.txt @@ -86,7 +86,7 @@ keycloak-saml-core - Apache Software License 2.0 + Apache License 2.0 https://raw.githubusercontent.com/keycloak/keycloak/999-SNAPSHOT/LICENSE.txt @@ -96,7 +96,7 @@ keycloak-saml-core-public - Apache Software License 2.0 + Apache License 2.0 https://raw.githubusercontent.com/keycloak/keycloak/999-SNAPSHOT/LICENSE.txt @@ -106,27 +106,27 @@ keycloak-saml-undertow-adapter - Apache Software License 2.0 + Apache License 2.0 https://raw.githubusercontent.com/keycloak/keycloak/999-SNAPSHOT/LICENSE.txt
org.keycloak - keycloak-saml-wildfly-elytron-adapter + keycloak-saml-wildfly-elytron-jakarta-adapter - Apache Software License 2.0 + Apache License 2.0 https://raw.githubusercontent.com/keycloak/keycloak/999-SNAPSHOT/LICENSE.txt org.keycloak - keycloak-saml-wildfly-subsystem + keycloak-saml-wildfly-jakarta-subsystem - Apache Software License 2.0 + Apache License 2.0 https://raw.githubusercontent.com/keycloak/keycloak/999-SNAPSHOT/LICENSE.txt diff --git a/distribution/galleon-feature-packs/saml-adapter-galleon-pack/src/main/resources/packages/docs.licenses/content/docs/licenses/mit.txt b/distribution/galleon-feature-packs/saml-adapter-galleon-pack/src/main/resources/packages/docs.licenses/content/docs/licenses/mit.txt deleted file mode 100644 index 056140b6dd81..000000000000 --- a/distribution/galleon-feature-packs/saml-adapter-galleon-pack/src/main/resources/packages/docs.licenses/content/docs/licenses/mit.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) ${license.git.copyrightYears} The Waffle Project Contributors: https://github.com/Waffle/waffle/graphs/contributors - -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. From 4481b1b3a8d0e670c87ad546a09751249300bca0 Mon Sep 17 00:00:00 2001 From: Farah Juma Date: Mon, 21 Aug 2023 22:23:08 +0200 Subject: [PATCH 121/135] Update the Keycloak SAML adapter subsystem to no longer use the AttributeDefinition#getAttributeMarshaller method Closes https://github.com/keycloak/keycloak/issues/22593 Signed-off-by: Peter Skopek (cherry picked from commit ec08a7bb739fab971e9de327cad71730fdaf3021) --- .../wildfly/wildfly-jakarta-subsystem/pom.xml | 5 +++++ .../saml/wildfly/wildfly-subsystem/pom.xml | 5 +++++ .../extension/KeycloakSubsystemParser.java | 20 +++++++++---------- pom.xml | 6 ++++++ 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/adapters/saml/wildfly/wildfly-jakarta-subsystem/pom.xml b/adapters/saml/wildfly/wildfly-jakarta-subsystem/pom.xml index 1a14b2bdcb11..6b5e54ce982f 100755 --- a/adapters/saml/wildfly/wildfly-jakarta-subsystem/pom.xml +++ b/adapters/saml/wildfly/wildfly-jakarta-subsystem/pom.xml @@ -140,6 +140,11 @@ true + + org.wildfly.common + wildfly-common + test + org.wildfly.core wildfly-subsystem-test-framework diff --git a/adapters/saml/wildfly/wildfly-subsystem/pom.xml b/adapters/saml/wildfly/wildfly-subsystem/pom.xml index 20115d145d98..6db425157b8b 100755 --- a/adapters/saml/wildfly/wildfly-subsystem/pom.xml +++ b/adapters/saml/wildfly/wildfly-subsystem/pom.xml @@ -86,6 +86,11 @@ true + + org.wildfly.common + wildfly-common + test + org.wildfly.core wildfly-subsystem-test-framework diff --git a/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemParser.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemParser.java index 42f624a75c9d..4cf90c4d7645 100755 --- a/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemParser.java +++ b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemParser.java @@ -498,7 +498,7 @@ void writeSps(final XMLExtendedStreamWriter writer, final ModelNode model) throw writer.writeAttribute(Constants.XML.ENTITY_ID, sp.getName()); ModelNode spAttributes = sp.getValue(); for (SimpleAttributeDefinition attr : ServiceProviderDefinition.ATTRIBUTES) { - attr.marshallAsAttribute(spAttributes, false, writer); + attr.getMarshaller().marshallAsAttribute(attr, spAttributes, false, writer); } writeKeys(writer, spAttributes.get(Constants.Model.KEY)); writePrincipalNameMapping(writer, spAttributes); @@ -521,7 +521,7 @@ void writeIdentityProvider(XMLExtendedStreamWriter writer, ModelNode model) thro ModelNode idpAttributes = idp.getValue(); for (SimpleAttributeDefinition attr : IdentityProviderDefinition.ATTRIBUTES) { - attr.marshallAsAttribute(idpAttributes, false, writer); + attr.getMarshaller().marshallAsAttribute(attr, idpAttributes, false, writer); } writeSingleSignOn(writer, idpAttributes.get(Constants.Model.SINGLE_SIGN_ON)); @@ -539,7 +539,7 @@ void writeSingleSignOn(XMLExtendedStreamWriter writer, ModelNode model) throws X } writer.writeStartElement(Constants.XML.SINGLE_SIGN_ON); for (SimpleAttributeDefinition attr : SingleSignOnDefinition.ATTRIBUTES) { - attr.marshallAsAttribute(model, false, writer); + attr.getMarshaller().marshallAsAttribute(attr, model, false, writer); } writer.writeEndElement(); } @@ -550,7 +550,7 @@ void writeSingleLogout(XMLExtendedStreamWriter writer, ModelNode model) throws X } writer.writeStartElement(Constants.XML.SINGLE_LOGOUT); for (SimpleAttributeDefinition attr : SingleLogoutDefinition.ATTRIBUTES) { - attr.marshallAsAttribute(model, false, writer); + attr.getMarshaller().marshallAsAttribute(attr, model, false, writer); } writer.writeEndElement(); } @@ -569,10 +569,10 @@ void writeKeys(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStream ModelNode keyAttributes = key.getValue(); for (SimpleAttributeDefinition attr : KeyDefinition.ATTRIBUTES) { - attr.marshallAsAttribute(keyAttributes, false, writer); + attr.getMarshaller().marshallAsAttribute(attr, keyAttributes, false, writer); } for (SimpleAttributeDefinition attr : KeyDefinition.ELEMENTS) { - attr.marshallAsElement(keyAttributes, false, writer); + attr.getMarshaller().marshallAsElement(attr, keyAttributes, false, writer); } writeKeyStore(writer, keyAttributes.get(Constants.Model.KEY_STORE)); @@ -599,7 +599,7 @@ void writeAllowedClockSkew(XMLExtendedStreamWriter writer, ModelNode allowedCloc return; } writer.writeStartElement(Constants.XML.ALLOWED_CLOCK_SKEW); - AllowedClockSkew.ALLOWED_CLOCK_SKEW_UNIT.marshallAsAttribute(allowedClockSkew, false, writer); + AllowedClockSkew.ALLOWED_CLOCK_SKEW_UNIT.getMarshaller().marshallAsAttribute(AllowedClockSkew.ALLOWED_CLOCK_SKEW_UNIT, allowedClockSkew, false, writer); ModelNode allowedClockSkewValue = allowedClockSkew.get(Constants.Model.ALLOWED_CLOCK_SKEW_VALUE); char[] chars = allowedClockSkewValue.asString().toCharArray(); writer.writeCharacters(chars, 0, chars.length); @@ -612,7 +612,7 @@ void writeKeyStore(XMLExtendedStreamWriter writer, ModelNode model) throws XMLSt } writer.writeStartElement(Constants.XML.KEY_STORE); for (SimpleAttributeDefinition attr : KeyStoreDefinition.ATTRIBUTES) { - attr.marshallAsAttribute(model, false, writer); + attr.getMarshaller().marshallAsAttribute(attr, model, false, writer); } writePrivateKey(writer, model); writeCertificate(writer, model); @@ -626,7 +626,7 @@ void writeCertificate(XMLExtendedStreamWriter writer, ModelNode model) throws XM } writer.writeStartElement(Constants.XML.CERTIFICATE); SimpleAttributeDefinition attr = KeyStoreCertificateDefinition.CERTIFICATE_ALIAS; - attr.marshallAsAttribute(model, false, writer); + attr.getMarshaller().marshallAsAttribute(attr, model, false, writer); writer.writeEndElement(); } @@ -639,7 +639,7 @@ void writePrivateKey(XMLExtendedStreamWriter writer, ModelNode model) throws XML } writer.writeStartElement(Constants.XML.PRIVATE_KEY); for (SimpleAttributeDefinition attr : KeyStorePrivateKeyDefinition.ATTRIBUTES) { - attr.marshallAsAttribute(model, false, writer); + attr.getMarshaller().marshallAsAttribute(attr, model, false, writer); } writer.writeEndElement(); } diff --git a/pom.xml b/pom.xml index 8d7095ae1f65..47c7776974e3 100644 --- a/pom.xml +++ b/pom.xml @@ -670,6 +670,12 @@ httpclient ${apache.httpcomponents.version} + + org.wildfly.common + wildfly-common + ${wildfly.common.version} + provided + org.wildfly.core wildfly-controller From 2113d62e6abeb238448e306c0d277610a9de4a56 Mon Sep 17 00:00:00 2001 From: Peter Skopek Date: Wed, 30 Aug 2023 15:24:09 +0200 Subject: [PATCH 122/135] Tidy up SAML Adapter Galleon Feature Pack build Signed-off-by: Peter Skopek (cherry picked from commit bca5fe8051d5ddc7b78d1177ef2d154bce982210) --- adapters/saml/wildfly/wildfly-jakarta-subsystem/pom.xml | 5 ----- adapters/saml/wildfly/wildfly-subsystem/pom.xml | 5 ----- .../galleon-feature-packs/saml-adapter-galleon-pack/pom.xml | 6 ++---- .../wildfly-feature-pack-build-eap.xml | 1 - pom.xml | 3 ++- 5 files changed, 4 insertions(+), 16 deletions(-) diff --git a/adapters/saml/wildfly/wildfly-jakarta-subsystem/pom.xml b/adapters/saml/wildfly/wildfly-jakarta-subsystem/pom.xml index 6b5e54ce982f..1a14b2bdcb11 100755 --- a/adapters/saml/wildfly/wildfly-jakarta-subsystem/pom.xml +++ b/adapters/saml/wildfly/wildfly-jakarta-subsystem/pom.xml @@ -140,11 +140,6 @@ true - - org.wildfly.common - wildfly-common - test - org.wildfly.core wildfly-subsystem-test-framework diff --git a/adapters/saml/wildfly/wildfly-subsystem/pom.xml b/adapters/saml/wildfly/wildfly-subsystem/pom.xml index 6db425157b8b..20115d145d98 100755 --- a/adapters/saml/wildfly/wildfly-subsystem/pom.xml +++ b/adapters/saml/wildfly/wildfly-subsystem/pom.xml @@ -86,11 +86,6 @@ true - - org.wildfly.common - wildfly-common - test - org.wildfly.core wildfly-subsystem-test-framework diff --git a/distribution/galleon-feature-packs/saml-adapter-galleon-pack/pom.xml b/distribution/galleon-feature-packs/saml-adapter-galleon-pack/pom.xml index 781e426c7382..04337b1b3eb2 100644 --- a/distribution/galleon-feature-packs/saml-adapter-galleon-pack/pom.xml +++ b/distribution/galleon-feature-packs/saml-adapter-galleon-pack/pom.xml @@ -32,8 +32,6 @@ ${basedir}/../../saml-adapters/wildfly-adapter/wildfly-jakarta-modules/src/main/resources - 6.4.0.Final - 2.3.1.Final ${project.build.directory}/resources/packages/docs.licenses/content/docs/licenses @@ -217,7 +215,7 @@ org.wildfly.galleon-plugins wildfly-galleon-maven-plugin - ${version.org.wildfly.galleon-plugins} + ${org.wildfly.galleon-plugins.version} - 27.0.1.Final + 29.0.0.Final 1.2.13.Final 21.1.0.Final @@ -206,6 +206,7 @@ 6.4.2.Final 5.1.0.Final + 2.3.1.Final true From e879367aab7352075c2fe392503e3dffac2a26bb Mon Sep 17 00:00:00 2001 From: Peter Skopek Date: Tue, 5 Sep 2023 23:19:39 +0200 Subject: [PATCH 123/135] Set distinct wildfly-common versions for Quarkus and WildFly based modules Signed-off-by: Peter Skopek (cherry picked from commit 35c2d52a54e119f0357b97ee138febfd4e8b5d62) --- adapters/oidc/wildfly-elytron/pom.xml | 1 + adapters/oidc/wildfly/wildfly-subsystem/pom.xml | 5 +++++ adapters/saml/wildfly/wildfly-jakarta-subsystem/pom.xml | 6 ++++++ adapters/saml/wildfly/wildfly-subsystem/pom.xml | 6 ++++++ .../saml-adapter-galleon-pack/pom.xml | 2 +- pom.xml | 7 ++++--- 6 files changed, 23 insertions(+), 4 deletions(-) diff --git a/adapters/oidc/wildfly-elytron/pom.xml b/adapters/oidc/wildfly-elytron/pom.xml index 8a51fc6b28b2..1d8f5c9b5b86 100755 --- a/adapters/oidc/wildfly-elytron/pom.xml +++ b/adapters/oidc/wildfly-elytron/pom.xml @@ -35,6 +35,7 @@ org.wildfly.common wildfly-common + ${wildfly.common.wildfly.aligned.version} provided diff --git a/adapters/oidc/wildfly/wildfly-subsystem/pom.xml b/adapters/oidc/wildfly/wildfly-subsystem/pom.xml index ca80fcea4750..15f36da7fef0 100755 --- a/adapters/oidc/wildfly/wildfly-subsystem/pom.xml +++ b/adapters/oidc/wildfly/wildfly-subsystem/pom.xml @@ -68,6 +68,11 @@ wildfly-web-common provided + + org.wildfly.common + wildfly-common + ${wildfly.common.wildfly.aligned.version} + org.wildfly.security wildfly-elytron diff --git a/adapters/saml/wildfly/wildfly-jakarta-subsystem/pom.xml b/adapters/saml/wildfly/wildfly-jakarta-subsystem/pom.xml index 1a14b2bdcb11..016a1037a8d7 100755 --- a/adapters/saml/wildfly/wildfly-jakarta-subsystem/pom.xml +++ b/adapters/saml/wildfly/wildfly-jakarta-subsystem/pom.xml @@ -140,6 +140,12 @@ true + + org.wildfly.common + wildfly-common + ${wildfly.common.wildfly.aligned.version} + + org.wildfly.core wildfly-subsystem-test-framework diff --git a/adapters/saml/wildfly/wildfly-subsystem/pom.xml b/adapters/saml/wildfly/wildfly-subsystem/pom.xml index 20115d145d98..90b38b0f7462 100755 --- a/adapters/saml/wildfly/wildfly-subsystem/pom.xml +++ b/adapters/saml/wildfly/wildfly-subsystem/pom.xml @@ -86,6 +86,12 @@ true + + org.wildfly.common + wildfly-common + ${wildfly.common.wildfly.aligned.version} + + org.wildfly.core wildfly-subsystem-test-framework diff --git a/distribution/galleon-feature-packs/saml-adapter-galleon-pack/pom.xml b/distribution/galleon-feature-packs/saml-adapter-galleon-pack/pom.xml index 04337b1b3eb2..b88ea26a3833 100644 --- a/distribution/galleon-feature-packs/saml-adapter-galleon-pack/pom.xml +++ b/distribution/galleon-feature-packs/saml-adapter-galleon-pack/pom.xml @@ -230,7 +230,7 @@ org.wildfly.common wildfly-common - ${wildfly.common.version} + ${wildfly.common.wildfly.aligned.version} diff --git a/pom.xml b/pom.xml index 346670961b5b..d7d27bc80555 100644 --- a/pom.xml +++ b/pom.xml @@ -116,7 +116,8 @@ 1.9.0.Final 9.4.40.v20210413 6.0.3 - 1.6.0.Final + 1.5.4.Final-format-001 + 1.6.0.Final 2.2.3 15.4 1.5.4 @@ -674,7 +675,7 @@ org.wildfly.common wildfly-common - ${wildfly.common.version} + ${wildfly.common.quarkus.aligned.version} provided @@ -1867,7 +1868,7 @@ org.wildfly.common wildfly-common - ${wildfly.common.version} + ${wildfly.common.wildfly.aligned.version} From 4f2115c6420cade6f7f952078b726599f20d794a Mon Sep 17 00:00:00 2001 From: rmartinc Date: Mon, 4 Sep 2023 18:05:35 +0200 Subject: [PATCH 124/135] Add a new identity provider for LinkedIn based on OIDC Closes https://github.com/keycloak/keycloak/issues/22383 --- .../java/org/keycloak/util/JWKSUtils.java | 11 +- .../linked-in-add-identity-provider.png | Bin 44775 -> 44687 bytes .../identity-broker/social/linked-in.adoc | 13 +- .../topics/keycloak/changes-22_0_2.adoc | 6 + .../locales/en/identity-providers-help.json | 1 + .../public/locales/en/identity-providers.json | 1 + .../add/ExtendedNonDiscoverySettings.tsx | 1 + js/libs/ui-shared/src/icons/IconMapper.tsx | 1 + .../broker/oidc/OIDCIdentityProvider.java | 22 ++-- .../oidc/OIDCIdentityProviderConfig.java | 12 ++ .../oidc/mappers/UsernameTemplateMapper.java | 2 + .../linkedin/LinkedInIdentityProvider.java | 1 + .../LinkedInIdentityProviderFactory.java | 3 +- .../LinkedInOIDCIdentityProvider.java | 56 +++++++++ .../LinkedInOIDCIdentityProviderFactory.java | 114 ++++++++++++++++++ .../linkedin/LinkedInPublicKeyLoader.java | 51 ++++++++ .../linkedin/LinkedInUserAttributeMapper.java | 2 +- ...roker.social.SocialIdentityProviderFactory | 3 +- .../testsuite/admin/IdentityProviderTest.java | 7 +- .../KcOidcBrokerNonceParameterTest.java | 43 ++++++- .../testsuite/broker/SocialLoginTest.java | 23 ++-- 21 files changed, 330 insertions(+), 43 deletions(-) create mode 100644 services/src/main/java/org/keycloak/social/linkedin/LinkedInOIDCIdentityProvider.java create mode 100644 services/src/main/java/org/keycloak/social/linkedin/LinkedInOIDCIdentityProviderFactory.java create mode 100644 services/src/main/java/org/keycloak/social/linkedin/LinkedInPublicKeyLoader.java diff --git a/core/src/main/java/org/keycloak/util/JWKSUtils.java b/core/src/main/java/org/keycloak/util/JWKSUtils.java index 60b9ea83011e..886f12d82442 100644 --- a/core/src/main/java/org/keycloak/util/JWKSUtils.java +++ b/core/src/main/java/org/keycloak/util/JWKSUtils.java @@ -49,19 +49,24 @@ public static Map getKeysForUse(JSONWebKeySet keySet, JWK.Use } public static PublicKeysWrapper getKeyWrappersForUse(JSONWebKeySet keySet, JWK.Use requestedUse) { + return getKeyWrappersForUse(keySet, requestedUse, false); + } + + public static PublicKeysWrapper getKeyWrappersForUse(JSONWebKeySet keySet, JWK.Use requestedUse, boolean useRequestedUseWhenNull) { List result = new ArrayList<>(); for (JWK jwk : keySet.getKeys()) { JWKParser parser = JWKParser.create(jwk); - if (jwk.getPublicKeyUse() == null) { + if (jwk.getPublicKeyUse() == null && !useRequestedUseWhenNull) { logger.debugf("Ignoring JWK key '%s'. Missing required field 'use'.", jwk.getKeyId()); - } else if (requestedUse.asString().equals(jwk.getPublicKeyUse()) && parser.isKeyTypeSupported(jwk.getKeyType())) { + } else if ((requestedUse.asString().equals(jwk.getPublicKeyUse()) || (jwk.getPublicKeyUse() == null && useRequestedUseWhenNull)) + && parser.isKeyTypeSupported(jwk.getKeyType())) { KeyWrapper keyWrapper = new KeyWrapper(); keyWrapper.setKid(jwk.getKeyId()); if (jwk.getAlgorithm() != null) { keyWrapper.setAlgorithm(jwk.getAlgorithm()); } keyWrapper.setType(jwk.getKeyType()); - keyWrapper.setUse(getKeyUse(jwk.getPublicKeyUse())); + keyWrapper.setUse(getKeyUse(requestedUse.asString())); keyWrapper.setPublicKey(parser.toPublicKey()); result.add(keyWrapper); } diff --git a/docs/documentation/server_admin/images/linked-in-add-identity-provider.png b/docs/documentation/server_admin/images/linked-in-add-identity-provider.png index aca916d5422983fe83e97423ba112647fb71a3d6..d9fa0ed7a85296a66901b053ad5865a8e218fb49 100644 GIT binary patch literal 44687 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 UQHMB^De)_^==BS6=WpHrAJjn|B>(^b literal 44775 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�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 VUa>o|K|C_ytKwG@F5i0azW~yWz|8;v diff --git a/docs/documentation/server_admin/topics/identity-broker/social/linked-in.adoc b/docs/documentation/server_admin/topics/identity-broker/social/linked-in.adoc index 45c0311c5f08..6de26c0d817c 100644 --- a/docs/documentation/server_admin/topics/identity-broker/social/linked-in.adoc +++ b/docs/documentation/server_admin/topics/identity-broker/social/linked-in.adoc @@ -5,24 +5,17 @@ .Procedure . Click *Identity Providers* in the menu. -. From the `Add provider` list, select `LinkedIn`. +. From the `Add provider` list, select `LinkedIn OpenID Connect`. + .Add identity provider image:images/linked-in-add-identity-provider.png[Add Identity Provider] + . Copy the value of *Redirect URI* to your clipboard. -. In a separate browser tab, https://www.linkedin.com/developer/apps[create an app]. +. In a separate browser tab, https://developer.linkedin.com[create an app] in the LinkedIn developer portal. .. After you create the app, click the *Auth* tab. .. Enter the value of *Redirect URI* into the *Authorized redirect URLs for your app* field. .. Note *Your Client ID* and *Your Client Secret*. +.. Click the *Products* tab and *Request access* for the *Sign In with LinkedIn using OpenID Connect* product. . In {project_name}, paste the value of the *Client ID* into the *Client ID* field. . In {project_name}, paste the value of the *Client Secret* into the *Client Secret* field. . Click *Add*. - -.Configuration -* With `Profile Projection` you can configure the `projection` parameter for profile requests. -* For example, `(id,firstName,lastName,profilePicture(displayImage~:playableStreams))` produces the following profile request URL: -[source,txt] ----- -https://api.linkedin.com/v2/me?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams)) ----- \ No newline at end of file diff --git a/docs/documentation/upgrading/topics/keycloak/changes-22_0_2.adoc b/docs/documentation/upgrading/topics/keycloak/changes-22_0_2.adoc index b32b6681d41d..7cff9286e6f9 100644 --- a/docs/documentation/upgrading/topics/keycloak/changes-22_0_2.adoc +++ b/docs/documentation/upgrading/topics/keycloak/changes-22_0_2.adoc @@ -1,3 +1,9 @@ = Never expires option removed from client advanced settings combos The option `Never expires` is now removed from all the combos of the Advanced Settings client tab. This option was misleading because the different lifespans or idle timeouts were never infinite, but limited by the general user session or realm values. Therefore, this option is removed in favor of the other two remaining options: `Inherits from the realm settings` (the client uses general realm timeouts) and `Expires in` (the value is overriden for the client). Internally the `Never expires` was represented by `-1`. Now that value is shown with a warning in the Admin Console and cannot be set directly by the administrator. + += New LinkedIn OpenID Connect social provider + +A new social identity provider called *LinkedIn OpenID Connect* has been introduced for the business and employment-focused platform. LinkedIn released recently a new product for developers called link:https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin-v2[Sign In with LinkedIn using OpenID Connect]. The product provides a new way to authenticate members using OpenID Connect, but the default *OpenID Connect v1.0* identity provider does not work with it at present time. For that reason, {project_name} adds this new identity provider as the specific social provider for the new product. + +The old LinkedIn way based on OAuth seems to be completely removed from the link:https://developer.linkedin.com[developer portal]. How the existing LinkedIn social provider is working with current applications is not clear. {project_name} maintains the old provider but deprecated, and it will be removed in future versions. Its name was changed to *LinkedIn (deprecated)* to avoid misunderstandings. \ No newline at end of file diff --git a/js/apps/admin-ui/public/locales/en/identity-providers-help.json b/js/apps/admin-ui/public/locales/en/identity-providers-help.json index ec5007677c33..3578170fbe26 100644 --- a/js/apps/admin-ui/public/locales/en/identity-providers-help.json +++ b/js/apps/admin-ui/public/locales/en/identity-providers-help.json @@ -14,6 +14,7 @@ "passCurrentLocale": "Pass the current locale to the identity provider as a ui_locales parameter.", "logoutUrl": "End session endpoint to use to logout user from external IDP.", "backchannelLogout": "Does the external IDP support backchannel logout?", + "disableNonce": "Do not send the nonce parameter in the authentication request. The nonce parameter is sent and verified by default.", "disableUserInfo": "Disable usage of User Info service to obtain additional user information? Default is to use this OIDC service.", "userInfoUrl": "The User Info Url. This is optional.", "issuer": "The issuer identifier for the issuer of the response. If not provided, no validation will be performed.", diff --git a/js/apps/admin-ui/public/locales/en/identity-providers.json b/js/apps/admin-ui/public/locales/en/identity-providers.json index 5977d3882a08..5689710f554e 100644 --- a/js/apps/admin-ui/public/locales/en/identity-providers.json +++ b/js/apps/admin-ui/public/locales/en/identity-providers.json @@ -98,6 +98,7 @@ "tokenUrl": "Token URL", "logoutUrl": "Logout URL", "backchannelLogout": "Backchannel logout", + "disableNonce": "Disable nonce", "disableUserInfo": "Disable user info", "userInfoUrl": "User Info URL", "issuer": "Issuer", diff --git a/js/apps/admin-ui/src/identity-providers/add/ExtendedNonDiscoverySettings.tsx b/js/apps/admin-ui/src/identity-providers/add/ExtendedNonDiscoverySettings.tsx index e5d778fe0348..7c417362df8f 100644 --- a/js/apps/admin-ui/src/identity-providers/add/ExtendedNonDiscoverySettings.tsx +++ b/js/apps/admin-ui/src/identity-providers/add/ExtendedNonDiscoverySettings.tsx @@ -46,6 +46,7 @@ export const ExtendedNonDiscoverySettings = () => { label="backchannelLogout" /> + 0) { @@ -580,11 +582,15 @@ private String verifyAccessToken(AccessTokenResponse tokenResponse) { return accessToken; } + protected KeyWrapper getIdentityProviderKeyWrapper(JWSInput jws) { + return PublicKeyStorageManager.getIdentityProviderKeyWrapper(session, session.getContext().getRealm(), getConfig(), jws); + } + protected boolean verify(JWSInput jws) { if (!getConfig().isValidateSignature()) return true; try { - KeyWrapper key = PublicKeyStorageManager.getIdentityProviderKeyWrapper(session, session.getContext().getRealm(), getConfig(), jws); + KeyWrapper key = getIdentityProviderKeyWrapper(jws); if (key == null) { logger.debugf("Failed to verify token, key not found for algorithm %s", jws.getHeader().getRawAlgorithm()); return false; @@ -906,11 +912,13 @@ protected BrokeredIdentityContext exchangeExternalImpl(EventBuilder event, Multi @Override protected UriBuilder createAuthorizationUrl(AuthenticationRequest request) { UriBuilder uriBuilder = super.createAuthorizationUrl(request); - String nonce = Base64Url.encode(SecretGenerator.getInstance().randomBytes(16)); AuthenticationSessionModel authenticationSession = request.getAuthenticationSession(); - authenticationSession.setClientNote(BROKER_NONCE_PARAM, nonce); - uriBuilder.queryParam(OIDCLoginProtocol.NONCE_PARAM, nonce); + if (!getConfig().isDisableNonce()) { + String nonce = Base64Url.encode(SecretGenerator.getInstance().randomBytes(16)); + authenticationSession.setClientNote(BROKER_NONCE_PARAM, nonce); + uriBuilder.queryParam(OIDCLoginProtocol.NONCE_PARAM, nonce); + } String maxAge = request.getAuthenticationSession().getClientNote(OIDCLoginProtocol.MAX_AGE_PARAM); @@ -925,8 +933,8 @@ protected UriBuilder createAuthorizationUrl(AuthenticationRequest request) { public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, BrokeredIdentityContext context) { AuthenticationSessionModel authenticationSession = session.getContext().getAuthenticationSession(); - if (authenticationSession == null) { - // no interacting with the brokered OP, likely doing token exchanges + if (authenticationSession == null || getConfig().isDisableNonce()) { + // no interacting with the brokered OP, likely doing token exchanges or no nonce return; } diff --git a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderConfig.java b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderConfig.java index d27f92768178..f6b87fd3176f 100755 --- a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderConfig.java +++ b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderConfig.java @@ -120,6 +120,18 @@ public void setDisableUserInfoService(boolean disable) { getConfig().put("disableUserInfo", String.valueOf(disable)); } + public boolean isDisableNonce() { + return Boolean.parseBoolean(getConfig().get("disableNonce")); + } + + public void setDisableNonce(boolean disableNonce) { + if (disableNonce) { + getConfig().put("disableNonce", Boolean.TRUE.toString()); + } else { + getConfig().remove("disableNonce"); + } + } + public int getAllowedClockSkew() { String allowedClockSkew = getConfig().get(ALLOWED_CLOCK_SKEW); if (allowedClockSkew == null || allowedClockSkew.isEmpty()) { diff --git a/services/src/main/java/org/keycloak/broker/oidc/mappers/UsernameTemplateMapper.java b/services/src/main/java/org/keycloak/broker/oidc/mappers/UsernameTemplateMapper.java index 4160c78998c1..ee354a37c1d9 100755 --- a/services/src/main/java/org/keycloak/broker/oidc/mappers/UsernameTemplateMapper.java +++ b/services/src/main/java/org/keycloak/broker/oidc/mappers/UsernameTemplateMapper.java @@ -35,6 +35,7 @@ import org.keycloak.social.google.GoogleIdentityProviderFactory; import org.keycloak.social.instagram.InstagramIdentityProviderFactory; import org.keycloak.social.linkedin.LinkedInIdentityProviderFactory; +import org.keycloak.social.linkedin.LinkedInOIDCIdentityProviderFactory; import org.keycloak.social.microsoft.MicrosoftIdentityProviderFactory; import org.keycloak.social.openshift.OpenshiftV3IdentityProviderFactory; import org.keycloak.social.openshift.OpenshiftV4IdentityProviderFactory; @@ -72,6 +73,7 @@ public class UsernameTemplateMapper extends AbstractClaimMapper { GoogleIdentityProviderFactory.PROVIDER_ID, InstagramIdentityProviderFactory.PROVIDER_ID, LinkedInIdentityProviderFactory.PROVIDER_ID, + LinkedInOIDCIdentityProviderFactory.PROVIDER_ID, MicrosoftIdentityProviderFactory.PROVIDER_ID, OpenshiftV3IdentityProviderFactory.PROVIDER_ID, OpenshiftV4IdentityProviderFactory.PROVIDER_ID, diff --git a/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProvider.java b/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProvider.java index df0479b88e54..e744050eec29 100755 --- a/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProvider.java +++ b/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProvider.java @@ -36,6 +36,7 @@ * * @author Vlastimil Elias (velias at redhat dot com) */ +@Deprecated public class LinkedInIdentityProvider extends AbstractOAuth2IdentityProvider implements SocialIdentityProvider { private static final Logger log = Logger.getLogger(LinkedInIdentityProvider.class); diff --git a/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProviderFactory.java b/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProviderFactory.java index f067ea2de86f..69f5e2abe5a1 100755 --- a/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProviderFactory.java +++ b/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProviderFactory.java @@ -29,6 +29,7 @@ /** * @author Vlastimil Elias (velias at redhat dot com) */ +@Deprecated public class LinkedInIdentityProviderFactory extends AbstractIdentityProviderFactory implements SocialIdentityProviderFactory { @@ -36,7 +37,7 @@ public class LinkedInIdentityProviderFactory extends AbstractIdentityProviderFac @Override public String getName() { - return "LinkedIn"; + return "LinkedIn (deprecated)"; } @Override diff --git a/services/src/main/java/org/keycloak/social/linkedin/LinkedInOIDCIdentityProvider.java b/services/src/main/java/org/keycloak/social/linkedin/LinkedInOIDCIdentityProvider.java new file mode 100644 index 000000000000..2a4e10bf67cc --- /dev/null +++ b/services/src/main/java/org/keycloak/social/linkedin/LinkedInOIDCIdentityProvider.java @@ -0,0 +1,56 @@ +/* + * Copyright 2023 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.social.linkedin; + +import org.keycloak.broker.oidc.OIDCIdentityProvider; +import org.keycloak.broker.oidc.OIDCIdentityProviderConfig; +import org.keycloak.broker.social.SocialIdentityProvider; +import org.keycloak.crypto.KeyWrapper; +import org.keycloak.jose.jws.JWSInput; +import org.keycloak.keys.PublicKeyLoader; +import org.keycloak.keys.PublicKeyStorageProvider; +import org.keycloak.keys.PublicKeyStorageUtils; +import org.keycloak.models.KeycloakSession; + +/** + *

Specific OIDC LinkedIn provider for Sign In with LinkedIn using OpenID Connect + * product app.

+ * + * @author rmartinc + */ +public class LinkedInOIDCIdentityProvider extends OIDCIdentityProvider implements SocialIdentityProvider { + + public static final String DEFAULT_SCOPE = "openid profile email"; + + public LinkedInOIDCIdentityProvider(KeycloakSession session, OIDCIdentityProviderConfig config) { + super(session, config); + } + + @Override + protected String getDefaultScopes() { + return DEFAULT_SCOPE; + } + + @Override + protected KeyWrapper getIdentityProviderKeyWrapper(JWSInput jws) { + // workaround to load keys published with no "use" as signature + PublicKeyLoader loader = new LinkedInPublicKeyLoader(session, getConfig()); + PublicKeyStorageProvider keyStorage = session.getProvider(PublicKeyStorageProvider.class); + String modelKey = PublicKeyStorageUtils.getIdpModelCacheKey(session.getContext().getRealm().getId(), getConfig().getInternalId()); + return keyStorage.getPublicKey(modelKey, jws.getHeader().getKeyId(), jws.getHeader().getRawAlgorithm(), loader); + } +} diff --git a/services/src/main/java/org/keycloak/social/linkedin/LinkedInOIDCIdentityProviderFactory.java b/services/src/main/java/org/keycloak/social/linkedin/LinkedInOIDCIdentityProviderFactory.java new file mode 100644 index 000000000000..9130062a57db --- /dev/null +++ b/services/src/main/java/org/keycloak/social/linkedin/LinkedInOIDCIdentityProviderFactory.java @@ -0,0 +1,114 @@ +/* + * Copyright 2023 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.social.linkedin; + +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.io.IOException; +import java.util.List; +import org.keycloak.broker.oidc.OIDCIdentityProviderConfig; +import org.keycloak.broker.provider.AbstractIdentityProviderFactory; +import org.keycloak.broker.provider.util.SimpleHttp; +import org.keycloak.broker.social.SocialIdentityProviderFactory; +import org.keycloak.models.IdentityProviderModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.provider.ProviderConfigurationBuilder; + +/** + *

Specific OIDC LinkedIn provider for Sign In with LinkedIn using OpenID Connect + * product app. LinkedIn currently has two issues with default OIDC provider + * implementation:

+ * + *
    + *
  1. The jwks endpoint does not contain use claim for the signature key.
  2. + *
  3. The nonce in the authentication request is not returned back in the ID Token.
  4. + *
+ * + *

This factory workarounds the default provider to overcome the issues.

+ * + * @author rmartinc + */ +public class LinkedInOIDCIdentityProviderFactory extends AbstractIdentityProviderFactory implements SocialIdentityProviderFactory { + + public static final String PROVIDER_ID = "linkedin-openid-connect"; + public static final String WELL_KNOWN_URL = "https://www.linkedin.com/oauth/.well-known/openid-configuration"; + + // well known oidc metadata is cached as static property + private static OIDCConfigurationRepresentation metadata; + + @Override + public String getName() { + return "LinkedIn OpenID Connect"; + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public LinkedInOIDCIdentityProvider create(KeycloakSession session, IdentityProviderModel model) { + OIDCConfigurationRepresentation local = metadata; + if (local == null) { + local = getWellKnownMetadata(session); + if (local.getIssuer() == null || local.getTokenEndpoint() == null || local.getAuthorizationEndpoint()== null || local.getJwksUri() == null) { + throw new RuntimeException("Invalid data in the OIDC LinkedIn well-known address."); + } + metadata = local; + } + OIDCIdentityProviderConfig config = new OIDCIdentityProviderConfig(model); + config.setIssuer(local.getIssuer()); + config.setAuthorizationUrl(local.getAuthorizationEndpoint()); + config.setTokenUrl(local.getTokenEndpoint()); + if (local.getUserinfoEndpoint() != null) { + config.setUserInfoUrl(local.getUserinfoEndpoint()); + } + config.setUseJwksUrl(true); + config.setJwksUrl(local.getJwksUri()); + config.setValidateSignature(true); + config.setDisableNonce(true); // linkedin does not manage nonce correctly + return new LinkedInOIDCIdentityProvider(session, config); + } + + @Override + public OIDCIdentityProviderConfig createConfig() { + return new OIDCIdentityProviderConfig(); + } + + private static OIDCConfigurationRepresentation getWellKnownMetadata(KeycloakSession session) { + try (SimpleHttp.Response response = SimpleHttp.doGet(WELL_KNOWN_URL, session) + .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON) + .asResponse()) { + if (Response.Status.fromStatusCode(response.getStatus()).getFamily() != Response.Status.Family.SUCCESSFUL) { + throw new RuntimeException("Error calling the OIDC LinkedIn well-known address. Http status " + response.getStatus()); + } + return response.asJson(OIDCConfigurationRepresentation.class); + } catch (IOException e) { + throw new RuntimeException("Error calling the OIDC LinkedIn well-known address.", e); + } + } + + @Override + public List getConfigProperties() { + // we can add some common OIDC config parameters here if needed + return ProviderConfigurationBuilder.create() + .build(); + } +} diff --git a/services/src/main/java/org/keycloak/social/linkedin/LinkedInPublicKeyLoader.java b/services/src/main/java/org/keycloak/social/linkedin/LinkedInPublicKeyLoader.java new file mode 100644 index 000000000000..48e4bb29db17 --- /dev/null +++ b/services/src/main/java/org/keycloak/social/linkedin/LinkedInPublicKeyLoader.java @@ -0,0 +1,51 @@ +/* + * Copyright 2023 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.social.linkedin; + +import org.keycloak.broker.oidc.OIDCIdentityProviderConfig; +import org.keycloak.crypto.PublicKeysWrapper; +import org.keycloak.jose.jwk.JSONWebKeySet; +import org.keycloak.jose.jwk.JWK; +import org.keycloak.keys.PublicKeyLoader; +import org.keycloak.models.KeycloakSession; +import org.keycloak.protocol.oidc.utils.JWKSHttpUtils; +import org.keycloak.util.JWKSUtils; + +/** + *

Specific public key loader that assumes that use for the keys is the requested one. + * The LinkedIn OpenID Connect implementation does not add the compulsory + * use claim in the jwks endpoint.

+ * + * @author rmartinc + */ +public class LinkedInPublicKeyLoader implements PublicKeyLoader { + + private final KeycloakSession session; + private final OIDCIdentityProviderConfig config; + + public LinkedInPublicKeyLoader(KeycloakSession session, OIDCIdentityProviderConfig config) { + this.session = session; + this.config = config; + } + + @Override + public PublicKeysWrapper loadKeys() throws Exception { + String jwksUrl = config.getJwksUrl(); + JSONWebKeySet jwks = JWKSHttpUtils.sendJwksRequest(session, jwksUrl); + return JWKSUtils.getKeyWrappersForUse(jwks, JWK.Use.SIG, true); + } +} diff --git a/services/src/main/java/org/keycloak/social/linkedin/LinkedInUserAttributeMapper.java b/services/src/main/java/org/keycloak/social/linkedin/LinkedInUserAttributeMapper.java index 43ec4b257762..35a695b3720c 100644 --- a/services/src/main/java/org/keycloak/social/linkedin/LinkedInUserAttributeMapper.java +++ b/services/src/main/java/org/keycloak/social/linkedin/LinkedInUserAttributeMapper.java @@ -25,7 +25,7 @@ */ public class LinkedInUserAttributeMapper extends AbstractJsonUserAttributeMapper { - private static final String[] cp = new String[] { LinkedInIdentityProviderFactory.PROVIDER_ID }; + private static final String[] cp = new String[] { LinkedInIdentityProviderFactory.PROVIDER_ID, LinkedInOIDCIdentityProviderFactory.PROVIDER_ID }; @Override public String[] getCompatibleProviders() { diff --git a/services/src/main/resources/META-INF/services/org.keycloak.broker.social.SocialIdentityProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.broker.social.SocialIdentityProviderFactory index 82811600200f..8bcc24617a16 100755 --- a/services/src/main/resources/META-INF/services/org.keycloak.broker.social.SocialIdentityProviderFactory +++ b/services/src/main/resources/META-INF/services/org.keycloak.broker.social.SocialIdentityProviderFactory @@ -20,6 +20,7 @@ org.keycloak.social.paypal.PayPalIdentityProviderFactory org.keycloak.social.github.GitHubIdentityProviderFactory org.keycloak.social.google.GoogleIdentityProviderFactory org.keycloak.social.linkedin.LinkedInIdentityProviderFactory +org.keycloak.social.linkedin.LinkedInOIDCIdentityProviderFactory org.keycloak.social.stackoverflow.StackoverflowIdentityProviderFactory org.keycloak.social.twitter.TwitterIdentityProviderFactory org.keycloak.social.microsoft.MicrosoftIdentityProviderFactory @@ -27,4 +28,4 @@ org.keycloak.social.openshift.OpenshiftV3IdentityProviderFactory org.keycloak.social.openshift.OpenshiftV4IdentityProviderFactory org.keycloak.social.gitlab.GitLabIdentityProviderFactory org.keycloak.social.bitbucket.BitbucketIdentityProviderFactory -org.keycloak.social.instagram.InstagramIdentityProviderFactory \ No newline at end of file +org.keycloak.social.instagram.InstagramIdentityProviderFactory diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java index e87be8cb2eca..38a8b6369e79 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java @@ -875,7 +875,12 @@ public void testInstalledIdentityProviders() { response = realm.identityProviders().getIdentityProviders("linkedin"); Assert.assertEquals("Status", 200, response.getStatus()); body = response.readEntity(Map.class); - assertProviderInfo(body, "linkedin", "LinkedIn"); + assertProviderInfo(body, "linkedin", "LinkedIn (deprecated)"); + + response = realm.identityProviders().getIdentityProviders("linkedin-openid-connect"); + Assert.assertEquals("Status", 200, response.getStatus()); + body = response.readEntity(Map.class); + assertProviderInfo(body, "linkedin-openid-connect", "LinkedIn OpenID Connect"); response = realm.identityProviders().getIdentityProviders("microsoft"); Assert.assertEquals("Status", 200, response.getStatus()); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerNonceParameterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerNonceParameterTest.java index cec7734d8dcf..3bbd82c63a85 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerNonceParameterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerNonceParameterTest.java @@ -2,15 +2,25 @@ import org.junit.Assert; import org.junit.Test; +import org.keycloak.admin.client.resource.IdentityProviderResource; +import org.keycloak.broker.oidc.OIDCIdentityProvider; import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInputException; +import org.keycloak.protocol.ProtocolMapperUtils; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; +import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper; import org.keycloak.representations.IDToken; import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.IdentityProviderRepresentation; +import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.testsuite.util.ClientBuilder; import org.keycloak.testsuite.util.OAuthClient; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Map; import static org.keycloak.testsuite.broker.BrokerTestTools.getConsumerRoot; @@ -23,11 +33,23 @@ protected BrokerConfiguration getBrokerConfiguration() { public List createConsumerClients() { List clients = new ArrayList<>(super.createConsumerClients()); - clients.add(ClientBuilder.create().clientId("consumer-client") + ClientRepresentation client = ClientBuilder.create().clientId("consumer-client") .publicClient() .redirectUris(getConsumerRoot() + "/auth/realms/master/app/auth/*") - .publicClient().build()); - + .publicClient().build(); + + // add the federated ID token to the protocol ID token + ProtocolMapperRepresentation consumerSessionNoteToClaimMapper = new ProtocolMapperRepresentation(); + consumerSessionNoteToClaimMapper.setName(OIDCIdentityProvider.FEDERATED_ID_TOKEN); + consumerSessionNoteToClaimMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + consumerSessionNoteToClaimMapper.setProtocolMapper(UserSessionNoteMapper.PROVIDER_ID); + consumerSessionNoteToClaimMapper.setConfig(Map.of(ProtocolMapperUtils.USER_SESSION_NOTE, OIDCIdentityProvider.FEDERATED_ID_TOKEN, + OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, OIDCIdentityProvider.FEDERATED_ID_TOKEN, + OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, Boolean.TRUE.toString())); + client.setProtocolMappers(Arrays.asList(consumerSessionNoteToClaimMapper)); + + clients.add(client); + return clients; } }; @@ -48,12 +70,23 @@ protected void loginUser() { IDToken idToken = toIdToken(response.getIdToken()); Assert.assertEquals("123456", idToken.getNonce()); + String federatedIdTokenString = (String) idToken.getOtherClaims().get(OIDCIdentityProvider.FEDERATED_ID_TOKEN); + Assert.assertNotNull(federatedIdTokenString); + IDToken federatedIdToken = toIdToken(federatedIdTokenString); + Assert.assertNotNull(federatedIdToken.getNonce()); } @Test public void testNonceNotSet() { updateExecutions(AbstractBrokerTest::disableUpdateProfileOnFirstLogin); + // do not send nonce at IDP provider level either + IdentityProviderResource idpRes = adminClient.realm(bc.consumerRealmName()).identityProviders().get(BrokerTestConstants.IDP_OIDC_ALIAS); + IdentityProviderRepresentation idpRep = idpRes.toRepresentation(); + OIDCIdentityProviderConfigRep cfg = new OIDCIdentityProviderConfigRep(idpRep); + cfg.setDisableNonce(true); + idpRes.update(idpRep); + oauth.realm(bc.consumerRealmName()); oauth.clientId("consumer-client"); oauth.nonce(null); @@ -65,6 +98,10 @@ public void testNonceNotSet() { IDToken idToken = toIdToken(response.getIdToken()); Assert.assertNull(idToken.getNonce()); + String federatedIdTokenString = (String) idToken.getOtherClaims().get(OIDCIdentityProvider.FEDERATED_ID_TOKEN); + Assert.assertNotNull(federatedIdTokenString); + IDToken federatedIdToken = toIdToken(federatedIdTokenString); + Assert.assertNull(federatedIdToken.getNonce()); } protected IDToken toIdToken(String encoded) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/SocialLoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/SocialLoginTest.java index 8a897c2ed7e9..435e078c35a2 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/SocialLoginTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/SocialLoginTest.java @@ -83,7 +83,6 @@ import static org.keycloak.testsuite.broker.SocialLoginTest.Provider.GOOGLE_NON_MATCHING_HOSTED_DOMAIN; import static org.keycloak.testsuite.broker.SocialLoginTest.Provider.INSTAGRAM; import static org.keycloak.testsuite.broker.SocialLoginTest.Provider.LINKEDIN; -import static org.keycloak.testsuite.broker.SocialLoginTest.Provider.LINKEDIN_WITH_PROJECTION; import static org.keycloak.testsuite.broker.SocialLoginTest.Provider.MICROSOFT; import static org.keycloak.testsuite.broker.SocialLoginTest.Provider.OPENSHIFT; import static org.keycloak.testsuite.broker.SocialLoginTest.Provider.OPENSHIFT4; @@ -125,8 +124,7 @@ public enum Provider { GITHUB("github", GitHubLoginPage.class), GITHUB_PRIVATE_EMAIL("github", "github-private-email", GitHubLoginPage.class), TWITTER("twitter", TwitterConsentLoginPage.class), - LINKEDIN("linkedin", LinkedInLoginPage.class), - LINKEDIN_WITH_PROJECTION("linkedin", LinkedInLoginPage.class), + LINKEDIN("linkedin-openid-connect", LinkedInLoginPage.class), MICROSOFT("microsoft", MicrosoftLoginPage.class), PAYPAL("paypal", PayPalLoginPage.class), STACKOVERFLOW("stackoverflow", StackOverflowLoginPage.class), @@ -393,15 +391,7 @@ public void twitterLogin() { @Test public void linkedinLogin() { setTestProvider(LINKEDIN); - performLogin(); - appPage.assertCurrent(); - } - - @Test - public void linkedinLoginWithProjection() { - setTestProvider(LINKEDIN_WITH_PROJECTION); - addAttributeMapper("picture", - "profilePicture.displayImage~.elements[0].identifiers[0].identifier"); + addAttributeMapper("picture", "picture", "linkedin-user-attribute-mapper"); performLogin(); appPage.assertCurrent(); assertAttribute("picture", getConfig("profile.picture")); @@ -449,9 +439,6 @@ public IdentityProviderRepresentation buildIdp(Provider provider) { if (provider == GOOGLE_NON_MATCHING_HOSTED_DOMAIN) { idp.getConfig().put("hostedDomain", "non-matching-hosted-domain"); } - if (provider == LINKEDIN_WITH_PROJECTION) { - idp.getConfig().put("profileProjection", "(id,firstName,lastName,profilePicture(displayImage~:playableStreams))"); - } if (provider == STACKOVERFLOW) { idp.getConfig().put("key", getConfig(provider, "clientKey")); } @@ -469,13 +456,17 @@ public IdentityProviderRepresentation buildIdp(Provider provider) { } private void addAttributeMapper(String name, String jsonField) { + addAttributeMapper(name, jsonField, currentTestProvider.id + "-user-attribute-mapper"); + } + + private void addAttributeMapper(String name, String jsonField, String mapperName) { IdentityProviderResource identityProvider = adminClient.realm(REALM).identityProviders().get(currentTestProvider.id); IdentityProviderRepresentation identityProviderRepresentation = identityProvider.toRepresentation(); //Add birthday mapper IdentityProviderMapperRepresentation mapperRepresentation = new IdentityProviderMapperRepresentation(); mapperRepresentation.setName(name); mapperRepresentation.setIdentityProviderAlias(identityProviderRepresentation.getAlias()); - mapperRepresentation.setIdentityProviderMapper(currentTestProvider.id + "-user-attribute-mapper"); + mapperRepresentation.setIdentityProviderMapper(mapperName); mapperRepresentation.setConfig(ImmutableMap.builder() .put(IdentityProviderMapperModel.SYNC_MODE, IdentityProviderMapperSyncMode.IMPORT.toString()) .put(AbstractJsonUserAttributeMapper.CONF_JSON_FIELD, jsonField) From 2f2706a9b10c92b50a02d4735c5868d6c3e84be5 Mon Sep 17 00:00:00 2001 From: Alexander Schwartz Date: Fri, 8 Sep 2023 08:17:29 +0200 Subject: [PATCH 125/135] Remove MS SQL JDBC driver from the Keycloak product (#23060) Closes #22983 (cherry picked from commit 2eb37dbe4f8882c6362c124a273718c2f93d3483) --- docs/guides/server/db.adoc | 43 +++++++++++++++++-- pom.xml | 5 ++- quarkus/runtime/pom.xml | 10 +++++ quarkus/tests/integration/pom.xml | 5 +++ quarkus/tests/junit5/pom.xml | 4 ++ .../it/utils/RawKeycloakDistribution.java | 10 ++++- .../servers/auth-server/quarkus/pom.xml | 7 +++ 7 files changed, 78 insertions(+), 6 deletions(-) diff --git a/docs/guides/server/db.adoc b/docs/guides/server/db.adoc index 2e430dc35d4e..c3fb2282dd25 100644 --- a/docs/guides/server/db.adoc +++ b/docs/guides/server/db.adoc @@ -30,11 +30,13 @@ only exists for development use-cases. The `dev-file` database is not suitable f <@profile.ifProduct> -== Installing a database driver (Oracle) +== Installing a database driver -Database drivers are shipped as part of Keycloak except for the Oracle Database driver which needs to be installed separately. +Database drivers are shipped as part of Keycloak except for the Oracle Database and Micrsoft SQL Server drivers which need to be installed separately. -Install the Oracle Database driver if you want to connect to an Oracle Database, or skip this section if you want to connect to a different database. +Install the necessary driver if you want to connect to one of these databases or skip this section if you want to connect to a different database for which the database driver is already included. + +=== Installing the Oracle Database driver To install the Oracle Database driver for Keycloak: @@ -70,6 +72,41 @@ See the <@links.server id="containers" /> {section} for details on how to build Then continue configuring the database as described in the next section. +=== Installing the Microsoft SQL Server driver + +To install the Microsoft SQL Server driver for Keycloak: + +. Download the `mssql-jdbc` JAR file from one of the following sources: + +.. Download a version from the https://learn.microsoft.com/en-us/sql/connect/jdbc/download-microsoft-jdbc-driver-for-sql-server[Microsoft JDBC Driver for SQL Server page]. + +.. Maven Central via `link:++https://repo1.maven.org/maven2/com/microsoft/sqlserver/mssql-jdbc/${properties["mssql-jdbc.version"]}/mssql-jdbc-${properties["mssql-jdbc.version"]}.jar++[mssql-jdbc]`. + +.. Installation media recommended by the database vendor for the specific database in use. + +. When running the unzipped distribution: Place the `mssql-jdbc` in Keycloak's `providers` folder + +. When running containers: Build a custom Keycloak image and add the JARs in the `providers` folder. When building a custom image for the Keycloak Operator, those images need to be optimized images with all build-time options of Keycloak set. ++ +A minimal Dockerfile to build an image which can be used with the Keycloak Operator and includes Microsoft SQL Server JDBC drivers downloaded from Maven Central looks like the following: ++ +[source,dockerfile] +---- +FROM quay.io/keycloak/keycloak:latest +ADD --chown=keycloak:keycloak https://repo1.maven.org/maven2/com/microsoft/sqlserver/mssql-jdbc/${properties["mssql-jdbc.version"]}/mssql-jdbc-${properties["mssql-jdbc.version"]}.jar /opt/keycloak/providers/mssql-jdbc.jar +# Setting the build parameter for the database: +ENV KC_DB=mssql +# Add all other build parameters needed, for example enable health and metrics: +ENV KC_HEALTH_ENABLED=true +ENV KC_METRICS_ENABLED=true +# To be able to use the image with the Keycloak Operator, it needs to be optimized, which requires Keycloak's build step: +RUN /opt/keycloak/bin/kc.sh build +---- ++ +See the <@links.server id="containers" /> {section} for details on how to build optimized images. + +Then continue configuring the database as described in the next section. + == Configuring a database diff --git a/pom.xml b/pom.xml index d7d27bc80555..1017bd5bc0d9 100644 --- a/pom.xml +++ b/pom.xml @@ -148,9 +148,10 @@ 10.11 3.1.4 2022-latest - 9.2.0.jre8 - + + 12.2.0.jre11 19.3 + 23.2.0.0 diff --git a/quarkus/runtime/pom.xml b/quarkus/runtime/pom.xml index 3762f7ac7288..ad1a9e9e8574 100644 --- a/quarkus/runtime/pom.xml +++ b/quarkus/runtime/pom.xml @@ -746,6 +746,16 @@
+ + io.quarkus + quarkus-jdbc-mssql + + + com.microsoft.sqlserver + mssql-jdbc + + + diff --git a/quarkus/tests/integration/pom.xml b/quarkus/tests/integration/pom.xml index 3ee0ab2f254a..203ea0e8cf64 100644 --- a/quarkus/tests/integration/pom.xml +++ b/quarkus/tests/integration/pom.xml @@ -71,6 +71,11 @@ bctls-fips test + + com.microsoft.sqlserver + mssql-jdbc + test + diff --git a/quarkus/tests/junit5/pom.xml b/quarkus/tests/junit5/pom.xml index 638e662d4dc7..87960df0bb4f 100644 --- a/quarkus/tests/junit5/pom.xml +++ b/quarkus/tests/junit5/pom.xml @@ -92,6 +92,10 @@ org.testcontainers mssqlserver + + com.microsoft.sqlserver + mssql-jdbc + org.apache.maven.wagon wagon-http-shared diff --git a/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java b/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java index 15bb4885b92b..9bd421993b1e 100644 --- a/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java +++ b/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java @@ -389,6 +389,10 @@ private Path prepareDistribution() { if (!inited || (reCreate || !dPath.toFile().exists())) { FileUtil.deleteDirectory(dPath); ZipUtils.unzip(distFile.toPath(), distRootPath); + if (System.getProperty("product") != null) { + // MS SQL Server driver might be excluded if running as a product build + copyProvider(dPath, "com.microsoft.sqlserver", "mssql-jdbc"); + } } // make sure script is executable @@ -525,8 +529,12 @@ public void copyOrReplaceFile(Path file, Path targetFile) { } public void copyProvider(String groupId, String artifactId) { + copyProvider(getDistPath(), groupId, artifactId); + } + + private static void copyProvider(Path distPath, String groupId, String artifactId) { try { - Files.copy(Maven.resolveArtifact(groupId, artifactId), getDistPath().resolve("providers").resolve(artifactId + ".jar")); + Files.copy(Maven.resolveArtifact(groupId, artifactId), distPath.resolve("providers").resolve(artifactId + ".jar")); } catch (IOException cause) { throw new RuntimeException("Failed to copy JAR file to 'providers' directory", cause); } diff --git a/testsuite/integration-arquillian/servers/auth-server/quarkus/pom.xml b/testsuite/integration-arquillian/servers/auth-server/quarkus/pom.xml index 6aa1ca72acbb..138d19435b18 100644 --- a/testsuite/integration-arquillian/servers/auth-server/quarkus/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/quarkus/pom.xml @@ -371,6 +371,13 @@ jar ${auth.server.home}/providers + + com.microsoft.sqlserver + mssql-jdbc + ${mssql-jdbc.version} + jar + ${auth.server.home}/providers + From 47b97b9404f8b6bc5cfeb290db86194abc38ba96 Mon Sep 17 00:00:00 2001 From: Marek Posolda Date: Fri, 8 Sep 2023 08:05:05 +0200 Subject: [PATCH 126/135] Registration flow fixed (#23064) Closes #21514 Co-authored-by: Vilmos Nagy Co-authored-by: Alexander Schwartz Co-authored-by: Marek Posolda (cherry picked from commit 506e2537acf33c216833735ac90fc2ffe299bcfe) --- .../keycloak/authentication/FormContext.java | 2 +- .../main/java/org/keycloak/events/Errors.java | 1 + .../forms/RegistrationUserCreation.java | 14 +++++- .../testsuite/forms/RegisterTest.java | 48 ++++++++++++++++++- 4 files changed, 61 insertions(+), 4 deletions(-) diff --git a/server-spi-private/src/main/java/org/keycloak/authentication/FormContext.java b/server-spi-private/src/main/java/org/keycloak/authentication/FormContext.java index fb340ca640dd..f901af097951 100755 --- a/server-spi-private/src/main/java/org/keycloak/authentication/FormContext.java +++ b/server-spi-private/src/main/java/org/keycloak/authentication/FormContext.java @@ -58,7 +58,7 @@ public interface FormContext { AuthenticationExecutionModel getExecution(); /** - * Current user attached to this flow. It can return null if no uesr has been identified yet + * Current user attached to this flow. It can return null if no user has been identified yet * * @return */ diff --git a/server-spi-private/src/main/java/org/keycloak/events/Errors.java b/server-spi-private/src/main/java/org/keycloak/events/Errors.java index 429be9143cec..e152a7cb8965 100755 --- a/server-spi-private/src/main/java/org/keycloak/events/Errors.java +++ b/server-spi-private/src/main/java/org/keycloak/events/Errors.java @@ -38,6 +38,7 @@ public interface Errors { String USER_DISABLED = "user_disabled"; String USER_TEMPORARILY_DISABLED = "user_temporarily_disabled"; String INVALID_USER_CREDENTIALS = "invalid_user_credentials"; + String DIFFERENT_USER_AUTHENTICATING = "different_user_authenticating"; String DIFFERENT_USER_AUTHENTICATED = "different_user_authenticated"; String USER_DELETE_ERROR = "user_delete_error"; diff --git a/services/src/main/java/org/keycloak/authentication/forms/RegistrationUserCreation.java b/services/src/main/java/org/keycloak/authentication/forms/RegistrationUserCreation.java index 84b33afd4226..e1b583592ade 100755 --- a/services/src/main/java/org/keycloak/authentication/forms/RegistrationUserCreation.java +++ b/services/src/main/java/org/keycloak/authentication/forms/RegistrationUserCreation.java @@ -18,6 +18,8 @@ package org.keycloak.authentication.forms; import org.keycloak.Config; +import org.keycloak.authentication.AuthenticationFlowError; +import org.keycloak.authentication.AuthenticationFlowException; import org.keycloak.authentication.FormAction; import org.keycloak.authentication.FormActionFactory; import org.keycloak.authentication.FormContext; @@ -106,11 +108,13 @@ public void validate(ValidationContext context) { @Override public void buildPage(FormContext context, LoginFormsProvider form) { - + checkNotOtherUserAuthenticating(context); } @Override public void success(FormContext context) { + checkNotOtherUserAuthenticating(context); + MultivaluedMap formData = context.getHttpRequest().getDecodedFormParameters(); String email = formData.getFirst(UserModel.EMAIL); @@ -148,6 +152,14 @@ public void success(FormContext context) { } } + private void checkNotOtherUserAuthenticating(FormContext context) { + if (context.getUser() != null) { + // the user probably did some back navigation in the browser, hitting this page in a strange state + context.getEvent().detail(Details.EXISTING_USER, context.getUser().getUsername()); + throw new AuthenticationFlowException(AuthenticationFlowError.GENERIC_AUTHENTICATION_ERROR, Errors.DIFFERENT_USER_AUTHENTICATING, Messages.EXPIRED_ACTION); + } + } + @Override public boolean requiresUser() { return false; diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java index cff5c589f62b..25115aad02f6 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java @@ -30,17 +30,20 @@ import org.keycloak.authentication.forms.RegistrationTermsAndConditions; import org.keycloak.authentication.forms.RegistrationUserCreation; import org.keycloak.events.Details; +import org.keycloak.events.Errors; import org.keycloak.events.EventType; import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.utils.DefaultAuthenticationFlows; import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; import org.keycloak.testsuite.AssertEvents; -import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; import org.keycloak.testsuite.pages.AppPage; import org.keycloak.testsuite.pages.AppPage.RequestType; +import org.keycloak.testsuite.pages.ErrorPage; import org.keycloak.testsuite.pages.LoginPage; +import org.keycloak.testsuite.pages.LoginPasswordResetPage; import org.keycloak.testsuite.pages.RegisterPage; import org.keycloak.testsuite.pages.VerifyEmailPage; import org.keycloak.testsuite.updaters.RealmAttributeUpdater; @@ -79,12 +82,18 @@ public class RegisterTest extends AbstractTestRealmKeycloakTest { @Page protected LoginPage loginPage; + @Page + protected ErrorPage errorPage; + @Page protected RegisterPage registerPage; @Page protected VerifyEmailPage verifyEmailPage; + @Page + protected LoginPasswordResetPage resetPasswordPage; + @Rule public GreenMailRule greenMail = new GreenMailRule(); @@ -669,7 +678,7 @@ public void registerUserMissingTermsAcceptance() { .removeDetail(Details.EMAIL) .error("invalid_registration").assertEvent(); } finally { - configureRegistrationFlowWithCustomRegistrationPageForm(UUID.randomUUID().toString()); + revertRegistrationFlow(); } } @@ -697,6 +706,34 @@ public void registerUserSuccessTermsAcceptance() { } } + @Test + public void testRegisterShouldFailBeforeUserCreationWhenUserIsInContext() { + loginPage.open(); + loginPage.clickRegister(); + registerPage.clickBackToLogin(); + loginPage.assertCurrent(testRealm().toRepresentation().getRealm()); + + loginPage.resetPassword(); + resetPasswordPage.assertCurrent(); + resetPasswordPage.changePassword("test-user@localhost"); + + driver.navigate().back(); + driver.navigate().back(); + events.clear(); + driver.navigate().back(); + + errorPage.assertCurrent(); + Assert.assertEquals("Action expired. Please continue with login now.", errorPage.getError()); + + events.expectRegister("registerUserMissingTermsAcceptance", "registerUserMissingTermsAcceptance@email") + .removeDetail(Details.USERNAME) + .removeDetail(Details.EMAIL) + .removeDetail(Details.REGISTER_METHOD) + .detail(Details.EXISTING_USER, "test-user@localhost") + .detail(Details.AUTHENTICATION_ERROR_DETAIL, Errors.DIFFERENT_USER_AUTHENTICATING) + .error(Errors.GENERIC_AUTHENTICATION_ERROR).assertEvent(); + } + protected RealmAttributeUpdater configureRealmRegistrationEmailAsUsername(final boolean value) { return getRealmAttributeUpdater().setRegistrationEmailAsUsername(value); } @@ -784,4 +821,11 @@ private void configureRegistrationFlowWithCustomRegistrationPageForm(String newF ); } + private void revertRegistrationFlow() { + testingClient.server("test").run(session -> FlowUtil.inCurrentRealm(session) + .selectFlow(DefaultAuthenticationFlows.REGISTRATION_FLOW) + .defineAsRegistrationFlow() + ); + } + } From f52af8d63be8a70275f7837c65cf3ba679e216c4 Mon Sep 17 00:00:00 2001 From: rmartinc Date: Fri, 8 Sep 2023 10:48:49 +0200 Subject: [PATCH 127/135] Add old LinkedIn provider to the deprecated profile Closes https://github.com/keycloak/keycloak/issues/23067 --- .../src/main/java/org/keycloak/common/Profile.java | 4 +++- .../test/java/org/keycloak/common/ProfileTest.java | 3 ++- .../upgrading/topics/keycloak/changes-22_0_2.adoc | 6 +++++- ...lpCommandDistTest.testBuildHelp.unix.approved.txt | 10 ++++++---- ...ommandDistTest.testBuildHelp.windows.approved.txt | 10 ++++++---- ...pCommandDistTest.testExportHelp.unix.approved.txt | 12 +++++++----- ...mmandDistTest.testExportHelpAll.unix.approved.txt | 12 +++++++----- ...pCommandDistTest.testImportHelp.unix.approved.txt | 12 +++++++----- ...mmandDistTest.testImportHelpAll.unix.approved.txt | 12 +++++++----- ...ommandDistTest.testStartDevHelp.unix.approved.txt | 12 +++++++----- ...andDistTest.testStartDevHelp.windows.approved.txt | 12 +++++++----- ...andDistTest.testStartDevHelpAll.unix.approved.txt | 12 +++++++----- ...DistTest.testStartDevHelpAll.windows.approved.txt | 12 +++++++----- ...lpCommandDistTest.testStartHelp.unix.approved.txt | 12 +++++++----- ...ommandDistTest.testStartHelp.windows.approved.txt | 12 +++++++----- ...ommandDistTest.testStartHelpAll.unix.approved.txt | 12 +++++++----- ...andDistTest.testStartHelpAll.windows.approved.txt | 12 +++++++----- .../linkedin/LinkedInIdentityProviderFactory.java | 9 ++++++++- .../testsuite/admin/IdentityProviderTest.java | 9 ++------- .../admin/partialimport/PartialImportTest.java | 2 +- 20 files changed, 117 insertions(+), 80 deletions(-) diff --git a/common/src/main/java/org/keycloak/common/Profile.java b/common/src/main/java/org/keycloak/common/Profile.java index f05658d439cf..2c85c5859f2a 100755 --- a/common/src/main/java/org/keycloak/common/Profile.java +++ b/common/src/main/java/org/keycloak/common/Profile.java @@ -88,7 +88,9 @@ public enum Feature { JS_ADAPTER("Host keycloak.js and keycloak-authz.js through the Keycloak sever", Type.DEFAULT), - FIPS("FIPS 140-2 mode", Type.DISABLED_BY_DEFAULT); + FIPS("FIPS 140-2 mode", Type.DISABLED_BY_DEFAULT), + + LINKEDIN_OAUTH("LinkedIn Social Identity Provider based on OAuth", Type.DEPRECATED); private final Type type; private String label; diff --git a/common/src/test/java/org/keycloak/common/ProfileTest.java b/common/src/test/java/org/keycloak/common/ProfileTest.java index d32dc2241b97..419f2b65e2ab 100644 --- a/common/src/test/java/org/keycloak/common/ProfileTest.java +++ b/common/src/test/java/org/keycloak/common/ProfileTest.java @@ -82,7 +82,8 @@ public void checkDefaults() { Profile.Feature.MAP_STORAGE, Profile.Feature.DECLARATIVE_USER_PROFILE, Profile.Feature.CLIENT_SECRET_ROTATION, - Profile.Feature.UPDATE_EMAIL + Profile.Feature.UPDATE_EMAIL, + Profile.Feature.LINKEDIN_OAUTH )); // KERBEROS can be disabled (i.e. FIPS mode disables SunJGSS provider) diff --git a/docs/documentation/upgrading/topics/keycloak/changes-22_0_2.adoc b/docs/documentation/upgrading/topics/keycloak/changes-22_0_2.adoc index 7cff9286e6f9..f162e847a399 100644 --- a/docs/documentation/upgrading/topics/keycloak/changes-22_0_2.adoc +++ b/docs/documentation/upgrading/topics/keycloak/changes-22_0_2.adoc @@ -6,4 +6,8 @@ The option `Never expires` is now removed from all the combos of the Advanced Se A new social identity provider called *LinkedIn OpenID Connect* has been introduced for the business and employment-focused platform. LinkedIn released recently a new product for developers called link:https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin-v2[Sign In with LinkedIn using OpenID Connect]. The product provides a new way to authenticate members using OpenID Connect, but the default *OpenID Connect v1.0* identity provider does not work with it at present time. For that reason, {project_name} adds this new identity provider as the specific social provider for the new product. -The old LinkedIn way based on OAuth seems to be completely removed from the link:https://developer.linkedin.com[developer portal]. How the existing LinkedIn social provider is working with current applications is not clear. {project_name} maintains the old provider but deprecated, and it will be removed in future versions. Its name was changed to *LinkedIn (deprecated)* to avoid misunderstandings. \ No newline at end of file +The old LinkedIn way based on OAuth seems to be completely removed from the link:https://developer.linkedin.com[developer portal]. How the existing LinkedIn social provider is working with current applications is not clear. {project_name} maintains the old provider renamed to *LinkedIn (deprecated)*, but in a deprecated feature called *linkedin-oauth* which is disabled by default. It will be removed in future versions. Please enable it again at startup if needed: + +``` +kc.[sh|bat] start --features linkedin-oauth ... +``` \ No newline at end of file diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.unix.approved.txt index 186afdeb357a..cf49b54d8156 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.unix.approved.txt @@ -48,15 +48,17 @@ Feature: account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. HTTP/TLS: diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.windows.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.windows.approved.txt index ceaef24641e3..f42a4f5b8cfb 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.windows.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.windows.approved.txt @@ -48,15 +48,17 @@ Feature: account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. HTTP/TLS: diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelp.unix.approved.txt index 9eea95e52c8e..5f7957f70a86 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelp.unix.approved.txt @@ -59,15 +59,17 @@ Feature: account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. Config: @@ -140,4 +142,4 @@ Export: --users-per-file Set the number of users per file. It is used only if 'users' is set to 'different_files'. Increasing this number leads to exponentially increasing - export times. Default: 50. \ No newline at end of file + export times. Default: 50. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelpAll.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelpAll.unix.approved.txt index c1a7243987c7..8e0836120169 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelpAll.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelpAll.unix.approved.txt @@ -122,15 +122,17 @@ Feature: account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. Config: @@ -203,4 +205,4 @@ Export: --users-per-file Set the number of users per file. It is used only if 'users' is set to 'different_files'. Increasing this number leads to exponentially increasing - export times. Default: 50. \ No newline at end of file + export times. Default: 50. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelp.unix.approved.txt index 0c4cfa472ef7..05a349373bc2 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelp.unix.approved.txt @@ -59,15 +59,17 @@ Feature: account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. Config: @@ -134,4 +136,4 @@ Import: --file Set the path to a file that will be read. --override Set if existing data should be overwritten. If set to false, data will be - ignored. Default: true. \ No newline at end of file + ignored. Default: true. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelpAll.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelpAll.unix.approved.txt index 976f047c73d3..c0a6700fbc6a 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelpAll.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelpAll.unix.approved.txt @@ -122,15 +122,17 @@ Feature: account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. Config: @@ -197,4 +199,4 @@ Import: --file Set the path to a file that will be read. --override Set if existing data should be overwritten. If set to false, data will be - ignored. Default: true. \ No newline at end of file + ignored. Default: true. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.unix.approved.txt index c7eab71995b7..e020467d91e4 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.unix.approved.txt @@ -75,15 +75,17 @@ Feature: account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. Hostname: @@ -252,4 +254,4 @@ Security: Do NOT start the server using this command when deploying to production. Use 'kc.sh start-dev --help-all' to list all available options, including build -options. \ No newline at end of file +options. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.windows.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.windows.approved.txt index 6aafbcae132e..14fa51cdfa3b 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.windows.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.windows.approved.txt @@ -73,15 +73,17 @@ Feature: account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. Hostname: @@ -235,4 +237,4 @@ Security: Do NOT start the server using this command when deploying to production. Use 'kc.bat start-dev --help-all' to list all available options, including -build options. \ No newline at end of file +build options. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.unix.approved.txt index 4c665109e1d5..949a6972b2e3 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.unix.approved.txt @@ -138,15 +138,17 @@ Feature: account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. Hostname: @@ -315,4 +317,4 @@ Security: Do NOT start the server using this command when deploying to production. Use 'kc.sh start-dev --help-all' to list all available options, including build -options. \ No newline at end of file +options. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.windows.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.windows.approved.txt index d618d3200067..d2225dbb3af0 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.windows.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.windows.approved.txt @@ -136,15 +136,17 @@ Feature: account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. Hostname: @@ -298,4 +300,4 @@ Security: Do NOT start the server using this command when deploying to production. Use 'kc.bat start-dev --help-all' to list all available options, including -build options. \ No newline at end of file +build options. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.unix.approved.txt index c5d2e6ee31ab..7bb881b9ed5a 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.unix.approved.txt @@ -81,15 +81,17 @@ Feature: account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. Hostname: @@ -262,4 +264,4 @@ By default, this command tries to update the server configuration by running a $ kc.sh start '--optimized' By doing that, the server should start faster based on any previous -configuration you have set when manually running the 'build' command. \ No newline at end of file +configuration you have set when manually running the 'build' command. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.windows.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.windows.approved.txt index 40342111e8cc..481c0aeb76aa 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.windows.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.windows.approved.txt @@ -79,15 +79,17 @@ Feature: account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. Hostname: @@ -245,4 +247,4 @@ By default, this command tries to update the server configuration by running a $ kc.bat start '--optimized' By doing that, the server should start faster based on any previous -configuration you have set when manually running the 'build' command. \ No newline at end of file +configuration you have set when manually running the 'build' command. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.unix.approved.txt index ad7561c19b91..49d9362ebf16 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.unix.approved.txt @@ -144,15 +144,17 @@ Feature: account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. Hostname: @@ -325,4 +327,4 @@ By default, this command tries to update the server configuration by running a $ kc.sh start '--optimized' By doing that, the server should start faster based on any previous -configuration you have set when manually running the 'build' command. \ No newline at end of file +configuration you have set when manually running the 'build' command. diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.windows.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.windows.approved.txt index fc4082dd7d03..1981f1988886 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.windows.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.windows.approved.txt @@ -142,15 +142,17 @@ Feature: account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, authorization, ciba, client-policies, client-secret-rotation, declarative-user-profile, docker, dynamic-scopes, fips, impersonation, - js-adapter, kerberos, map-storage, par, preview, recovery-codes, scripts, - step-up-authentication, token-exchange, update-email, web-authn. + js-adapter, kerberos, linkedin-oauth, map-storage, par, preview, + recovery-codes, scripts, step-up-authentication, token-exchange, + update-email, web-authn. Hostname: @@ -308,4 +310,4 @@ By default, this command tries to update the server configuration by running a $ kc.bat start '--optimized' By doing that, the server should start faster based on any previous -configuration you have set when manually running the 'build' command. \ No newline at end of file +configuration you have set when manually running the 'build' command. diff --git a/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProviderFactory.java b/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProviderFactory.java index 69f5e2abe5a1..a03845d9a529 100755 --- a/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProviderFactory.java +++ b/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProviderFactory.java @@ -18,9 +18,11 @@ import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig; import org.keycloak.broker.provider.AbstractIdentityProviderFactory; +import org.keycloak.common.Profile; import org.keycloak.models.IdentityProviderModel; import org.keycloak.broker.social.SocialIdentityProviderFactory; import org.keycloak.models.KeycloakSession; +import org.keycloak.provider.EnvironmentDependentProviderFactory; import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigurationBuilder; @@ -31,7 +33,7 @@ */ @Deprecated public class LinkedInIdentityProviderFactory extends AbstractIdentityProviderFactory - implements SocialIdentityProviderFactory { + implements SocialIdentityProviderFactory, EnvironmentDependentProviderFactory { public static final String PROVIDER_ID = "linkedin"; @@ -63,4 +65,9 @@ public List getConfigProperties() { .helpText("Projection parameter for profile request. Leave empty for default projection.") .add().build(); } + + @Override + public boolean isSupported() { + return Profile.isFeatureEnabled(Profile.Feature.LINKEDIN_OAUTH); + } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java index 38a8b6369e79..85c04bc67d90 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java @@ -550,8 +550,8 @@ public void testMapperTypes() { mapperTypes = provider.getMapperTypes(); assertMapperTypes(mapperTypes, "oidc-username-idp-mapper"); - create(createRep("linkedin", "linkedin")); - provider = realm.identityProviders().get("linkedin"); + create(createRep("linkedin-openid-connect", "linkedin-openid-connect")); + provider = realm.identityProviders().get("linkedin-openid-connect"); mapperTypes = provider.getMapperTypes(); assertMapperTypes(mapperTypes, "linkedin-user-attribute-mapper", "oidc-username-idp-mapper"); @@ -872,11 +872,6 @@ public void testInstalledIdentityProviders() { body = response.readEntity(Map.class); assertProviderInfo(body, "twitter", "Twitter"); - response = realm.identityProviders().getIdentityProviders("linkedin"); - Assert.assertEquals("Status", 200, response.getStatus()); - body = response.readEntity(Map.class); - assertProviderInfo(body, "linkedin", "LinkedIn (deprecated)"); - response = realm.identityProviders().getIdentityProviders("linkedin-openid-connect"); Assert.assertEquals("Status", 200, response.getStatus()); body = response.readEntity(Map.class); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialimport/PartialImportTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialimport/PartialImportTest.java index c013c0a58ac4..a6b20c3c0bc4 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialimport/PartialImportTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialimport/PartialImportTest.java @@ -96,7 +96,7 @@ public class PartialImportTest extends AbstractAuthTest { private static final String CLIENT_PREFIX = "client"; private static final String REALM_ROLE_PREFIX = "realmRole"; private static final String CLIENT_ROLE_PREFIX = "clientRole"; - private static final String[] IDP_ALIASES = {"twitter", "github", "facebook", "google", "linkedin", "microsoft", "stackoverflow"}; + private static final String[] IDP_ALIASES = {"twitter", "github", "facebook", "google", "linkedin-openid-connect", "microsoft", "stackoverflow"}; private static final int NUM_ENTITIES = IDP_ALIASES.length; private static final ResourceServerRepresentation resourceServerSampleSettings; From 1d15f1ebd86d669827f01fa0e53830e8a6cf139b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Barto=C5=A1?= Date: Fri, 8 Sep 2023 17:52:54 +0200 Subject: [PATCH 128/135] Quarkus IT that use Oracle DB don't work with -Dproduct (#23088) Fixes #23058 --- quarkus/tests/integration/pom.xml | 12 ++++++++++++ quarkus/tests/junit5/pom.xml | 11 +++++++++++ .../keycloak/it/utils/RawKeycloakDistribution.java | 5 ++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/quarkus/tests/integration/pom.xml b/quarkus/tests/integration/pom.xml index 203ea0e8cf64..52f5a8484a92 100644 --- a/quarkus/tests/integration/pom.xml +++ b/quarkus/tests/integration/pom.xml @@ -71,11 +71,23 @@ bctls-fips test + + com.microsoft.sqlserver mssql-jdbc test + + com.oracle.database.jdbc + ojdbc11 + test + + + com.oracle.database.nls + orai18n + test + diff --git a/quarkus/tests/junit5/pom.xml b/quarkus/tests/junit5/pom.xml index 87960df0bb4f..94b7d0e77557 100644 --- a/quarkus/tests/junit5/pom.xml +++ b/quarkus/tests/junit5/pom.xml @@ -92,10 +92,21 @@ org.testcontainers mssqlserver + + com.microsoft.sqlserver mssql-jdbc + + com.oracle.database.jdbc + ojdbc11 + + + com.oracle.database.nls + orai18n + + org.apache.maven.wagon wagon-http-shared diff --git a/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java b/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java index 9bd421993b1e..9e72a1c07c2a 100644 --- a/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java +++ b/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java @@ -389,9 +389,12 @@ private Path prepareDistribution() { if (!inited || (reCreate || !dPath.toFile().exists())) { FileUtil.deleteDirectory(dPath); ZipUtils.unzip(distFile.toPath(), distRootPath); + if (System.getProperty("product") != null) { - // MS SQL Server driver might be excluded if running as a product build + // JDBC drivers might be excluded if running as a product build copyProvider(dPath, "com.microsoft.sqlserver", "mssql-jdbc"); + copyProvider(dPath, "com.oracle.database.jdbc", "ojdbc11"); + copyProvider(dPath, "com.oracle.database.nls", "orai18n"); } } From 17432514a2f26308e8a6a01a9cf00e944d9f6049 Mon Sep 17 00:00:00 2001 From: Michal Hajas Date: Fri, 8 Sep 2023 17:48:26 +0100 Subject: [PATCH 129/135] Upgrade to Infinispan 14.0.17 (#23099) Closes #23046 (cherry picked from commit 93a80e9278f3f87c4b737ba894b01ce2494658a7) Co-authored-by: Alexander Schwartz --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1017bd5bc0d9..40789bb511bd 100644 --- a/pom.xml +++ b/pom.xml @@ -82,7 +82,7 @@ 2.2.220 6.2.7.Final 6.2.7.Final - 14.0.16.Final + 14.0.17.Final 4.6.5.Final From 38522e45694149b9fddf07d1a4f4fb26309ed0ff Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Fri, 8 Sep 2023 11:43:19 -0300 Subject: [PATCH 130/135] Attributes without a value set are not rendered in the account console (#22968) Closes #22961 --- .../app/content/account-page/AccountPage.tsx | 84 +++++++++---------- 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/themes/src/main/resources/theme/keycloak.v2/account/src/app/content/account-page/AccountPage.tsx b/themes/src/main/resources/theme/keycloak.v2/account/src/app/content/account-page/AccountPage.tsx index 292e413f959f..97d3f62b1e70 100644 --- a/themes/src/main/resources/theme/keycloak.v2/account/src/app/content/account-page/AccountPage.tsx +++ b/themes/src/main/resources/theme/keycloak.v2/account/src/app/content/account-page/AccountPage.tsx @@ -197,7 +197,7 @@ export class AccountPage extends React.Component )} - {!this.isUpdateEmailFeatureEnabled && fields.email != undefined && } - { fields.firstName != undefined && + - - - } - {fields.lastName != undefined && + + + - - - } + > + {features.isInternationalizationEnabled && ( Date: Wed, 6 Sep 2023 20:28:01 -0300 Subject: [PATCH 131/135] Broker claim mapper not recognizing claims from user info endpoint Closes #12137 --- .../oidc/mappers/AbstractClaimMapper.java | 12 +-- .../BrokerLinkAndTokenExchangeTest.java | 76 +++++++++++++++++++ 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/services/src/main/java/org/keycloak/broker/oidc/mappers/AbstractClaimMapper.java b/services/src/main/java/org/keycloak/broker/oidc/mappers/AbstractClaimMapper.java index 02e41c7348bd..057e4e42ed4a 100755 --- a/services/src/main/java/org/keycloak/broker/oidc/mappers/AbstractClaimMapper.java +++ b/services/src/main/java/org/keycloak/broker/oidc/mappers/AbstractClaimMapper.java @@ -83,7 +83,7 @@ public static Object getClaimValue(BrokeredIdentityContext context, String claim } { // search ID Token Object rawIdToken = context.getContextData().get(OIDCIdentityProvider.VALIDATED_ID_TOKEN); - JsonWebToken idToken; + JsonWebToken idToken = null; if (rawIdToken instanceof String) { try { @@ -93,13 +93,13 @@ public static Object getClaimValue(BrokeredIdentityContext context, String claim } } else if (rawIdToken instanceof JsonWebToken) { idToken = (JsonWebToken) rawIdToken; - } else { - return null; } - Object value = getClaimValue(idToken, claim); - if (value != null) - return value; + if (idToken != null) { + Object value = getClaimValue(idToken, claim); + if (value != null) + return value; + } } { // Search the OIDC UserInfo claim set (if any) diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/BrokerLinkAndTokenExchangeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/BrokerLinkAndTokenExchangeTest.java index f2d3cd639664..63dbd7cf971a 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/BrokerLinkAndTokenExchangeTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/BrokerLinkAndTokenExchangeTest.java @@ -16,6 +16,7 @@ */ package org.keycloak.testsuite.adapter.servlet; +import com.google.common.collect.ImmutableMap; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.container.test.api.OperateOnDeployment; import org.jboss.arquillian.graphene.page.Page; @@ -25,26 +26,34 @@ import org.junit.Before; import org.junit.Test; import org.keycloak.OAuth2Constants; +import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.broker.oidc.OIDCIdentityProviderConfig; +import org.keycloak.broker.oidc.mappers.UserAttributeMapper; import org.keycloak.common.Profile; import org.keycloak.exportimport.ExportImportConfig; import org.keycloak.exportimport.singlefile.SingleFileExportProviderFactory; import org.keycloak.jose.jws.JWSInput; import org.keycloak.models.ClientModel; import org.keycloak.models.Constants; +import org.keycloak.models.IdentityProviderMapperModel; +import org.keycloak.models.IdentityProviderMapperSyncMode; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocolService; +import org.keycloak.protocol.oidc.mappers.HardcodedClaim; +import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.FederatedIdentityRepresentation; +import org.keycloak.representations.idm.IdentityProviderMapperRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation; +import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.UserRepresentation; @@ -77,8 +86,10 @@ import jakarta.ws.rs.core.UriBuilder; import java.io.File; import java.net.URL; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; import static org.keycloak.testsuite.admin.ApiUtil.createUserAndResetPasswordWithAdminClient; @@ -753,6 +764,71 @@ public void testExternalExchange_extractIdentityFromProfile() throws Exception { } } + @Test + public void testExternalExchangeCreateNewUserUsingMappers() throws Exception { + RealmResource parentRealm = adminClient.realms().realm(PARENT_IDP); + ProtocolMapperRepresentation claimMapper = new ProtocolMapperRepresentation(); + claimMapper.setName("custom-claim-hardcoded-mapper"); + claimMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + claimMapper.setProtocolMapper(HardcodedClaim.PROVIDER_ID); + Map config = new HashMap<>(); + config.put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, "claim-from-idp"); + config.put(HardcodedClaim.CLAIM_VALUE, "true"); + config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true"); + config.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true"); + claimMapper.setConfig(config); + ClientRepresentation client = parentRealm.clients().findByClientId(PARENT_CLIENT).get(0); + ClientResource clientResource = parentRealm.clients().get(client.getId()); + clientResource.getProtocolMappers().createMapper(claimMapper).close(); + + RealmResource childRealm = adminClient.realms().realm(CHILD_IDP); + IdentityProviderMapperRepresentation attributeMapper = new IdentityProviderMapperRepresentation(); + attributeMapper.setName("attribute-mapper"); + attributeMapper.setIdentityProviderMapper(UserAttributeMapper.PROVIDER_ID); + attributeMapper.setIdentityProviderAlias(PARENT_IDP); + attributeMapper.setConfig(ImmutableMap.builder() + .put(IdentityProviderMapperModel.SYNC_MODE, IdentityProviderMapperSyncMode.INHERIT.toString()) + .put(UserAttributeMapper.CLAIM, "claim-from-idp") + .put(UserAttributeMapper.USER_ATTRIBUTE, "claim-to-broker") + .build()); + childRealm.identityProviders().get(PARENT_IDP).addMapper(attributeMapper).close(); + + String idToken = oauth.doGrantAccessTokenRequest(PARENT_IDP, PARENT3_USERNAME, "password", null, PARENT_CLIENT, "password").getAccessToken(); + Assert.assertEquals(0, adminClient.realm(CHILD_IDP).getClientSessionStats().size()); + + try (Client httpClient = AdminClientUtil.createResteasyClient()) { + WebTarget exchangeUrl = childTokenExchangeWebTarget(httpClient); + IdentityProviderRepresentation rep = adminClient.realm(CHILD_IDP).identityProviders().get(PARENT_IDP).toRepresentation(); + rep.getConfig().put(OIDCIdentityProviderConfig.VALIDATE_SIGNATURE, String.valueOf(false)); + adminClient.realm(CHILD_IDP).identityProviders().get(PARENT_IDP).update(rep); + + AccessToken token; + try (Response response = exchangeUrl.request() + .header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader(ClientApp.DEPLOYMENT_NAME, "password")) + .post(Entity.form( + new Form() + .param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE) + .param(OAuth2Constants.SUBJECT_TOKEN, idToken) + .param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.JWT_TOKEN_TYPE) + .param(OAuth2Constants.SUBJECT_ISSUER, PARENT_IDP) + .param(OAuth2Constants.SCOPE, OAuth2Constants.SCOPE_OPENID) + ))) { + Assert.assertEquals(200, response.getStatus()); + + AccessTokenResponse tokenResponse = response.readEntity(AccessTokenResponse.class); + JWSInput jws = new JWSInput(tokenResponse.getToken()); + token = jws.readJsonContent(AccessToken.class); + } + + UserRepresentation newUser = childRealm.users().search(PARENT3_USERNAME).get(0); + Assert.assertNotNull(newUser.getAttributes()); + Assert.assertTrue(newUser.getAttributes().containsKey("claim-to-broker")); + + // cleanup remove the user + childRealm.users().get(token.getSubject()).remove(); + } + } + public void logoutAll() { adminClient.realm(CHILD_IDP).logoutAll(); adminClient.realm(PARENT_IDP).logoutAll(); From ba58b510ee93f97ea5af7de491d3c78c0a28174c Mon Sep 17 00:00:00 2001 From: stianst Date: Mon, 11 Sep 2023 09:29:58 +0200 Subject: [PATCH 132/135] Fix failure in identity_providers_test.spec.ts after old LinkedId provider was deprecated Closes #23118 --- .../cypress/e2e/identity_providers_test.spec.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/js/apps/admin-ui/cypress/e2e/identity_providers_test.spec.ts b/js/apps/admin-ui/cypress/e2e/identity_providers_test.spec.ts index 797c99dc673c..7e2c74dbb605 100644 --- a/js/apps/admin-ui/cypress/e2e/identity_providers_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/identity_providers_test.spec.ts @@ -80,7 +80,11 @@ describe("Identity provider test", () => { { testName: "Gitlab", displayName: "Gitlab", alias: "gitlab" }, { testName: "Google", displayName: "Google", alias: "google" }, { testName: "Instagram", displayName: "Instagram", alias: "instagram" }, - { testName: "Linkedin", displayName: "LinkedIn", alias: "linkedin" }, + { + testName: "LinkedIn OpenID Connect", + displayName: "LinkedIn OpenID Connect", + alias: "linkedin-openid-connect" + }, { testName: "Microsoft", displayName: "Microsoft", alias: "microsoft" }, { testName: "Openshift-v3", @@ -177,8 +181,8 @@ describe("Identity provider test", () => { it("create and delete provider by item details", () => { createProviderPage .clickCreateDropdown() - .clickItem("linkedin") - .fill("linkedin", "123") + .clickItem("linkedin-openid-connect") + .fill("linkedin-openid-connect", "123") .clickAdd(); masthead.checkNotificationMessage(createSuccessMsg, true); From afa2171f3e02b8d6721808f8c964aa00562d9a58 Mon Sep 17 00:00:00 2001 From: stianst Date: Mon, 11 Sep 2023 10:13:52 +0200 Subject: [PATCH 133/135] Fix lint errorin identity_providers_test.spec.ts --- js/apps/admin-ui/cypress/e2e/identity_providers_test.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/apps/admin-ui/cypress/e2e/identity_providers_test.spec.ts b/js/apps/admin-ui/cypress/e2e/identity_providers_test.spec.ts index 7e2c74dbb605..2443472a953a 100644 --- a/js/apps/admin-ui/cypress/e2e/identity_providers_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/identity_providers_test.spec.ts @@ -83,7 +83,7 @@ describe("Identity provider test", () => { { testName: "LinkedIn OpenID Connect", displayName: "LinkedIn OpenID Connect", - alias: "linkedin-openid-connect" + alias: "linkedin-openid-connect", }, { testName: "Microsoft", displayName: "Microsoft", alias: "microsoft" }, { From 55b2eddb0ce8322fd79dfd33e1493524bc4d5ef6 Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Tue, 12 Sep 2023 14:09:55 -0300 Subject: [PATCH 134/135] Ignore attributes when they are not prefixed with user.attributes prefix (#26) * Ignore attributes when they are not prefixed with user.attributes prefix Co-authored-by: mposolda Co-authored-by: stianst * Update docs/documentation/release_notes/topics/22_0_3.adoc * Update docs/documentation/release_notes/topics/22_0_3.adoc --------- Co-authored-by: mposolda Co-authored-by: stianst Co-authored-by: Stian Thorgersen --- .../release_notes/topics/22_0_3.adoc | 48 +++++++++++++++++++ .../userprofile/LegacyAttributes.java | 15 +++++- .../testsuite/forms/RegisterTest.java | 5 ++ 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 docs/documentation/release_notes/topics/22_0_3.adoc diff --git a/docs/documentation/release_notes/topics/22_0_3.adoc b/docs/documentation/release_notes/topics/22_0_3.adoc new file mode 100644 index 000000000000..2ebce445d44b --- /dev/null +++ b/docs/documentation/release_notes/topics/22_0_3.adoc @@ -0,0 +1,48 @@ += Security vulnerability when registering or updating user through templates + +A security vulnerability was introduced in Keycloak 22.0.2. We highly recommend not upgrading to 22.0.2, and for anyone that has deployed 22.0.2 in production to upgrade to 22.0.3 immediately. + +For users that has self-registered after Keycloak was upgraded to 22.0.2 their password is not stored securely, and can be exposed to administrators of Keycloak. This only affects users that has registered after the upgrade was rolled-out, and does not affect any previously registered users. + +Any realm using the preview declarative user profile is not affected by this issue, and only realms using the default user profile provider is affected. + +To identify if there are any affected users in your deployment you can query these by accessing the database, and running the following SQL statement: + +[source,sql] +---- +SELECT DISTINCT U.ID, U.USERNAME, U.EMAIL, U.REALM_ID FROM USER_ENTITY U + INNER JOIN USER_ATTRIBUTE UA ON U.ID = UA.USER_ID + WHERE UA.NAME IN ('password','password-confirm') +---- + +We recommend contacting any affected users as well as adding the update password required action for them. + +If there are any affected users we also recommend removing these attributes from the database by running the following SQL statement: + +[source,sql] +---- +DELETE FROM USER_ATTRIBUTE UA WHERE UA.NAME IN ('password','password-confirm') +---- + +If any backups have been done of the database after the 22.0.2 release and there are affected users, we recommend deleting these. + +== Custom user storage providers + +Any deployments with custom user storage federation providers may also be affected, please verify your custom user storage to identify if this is an issue. + +To identify if there are any federated user affected in your deployment in case the user storage provider is delegating management of attributes to Keycloak, you can query these by accessing the database, and running the following SQL statement: + +[source,sql] +---- +SELECT DISTINCT USER_ID,REALM_ID,STORAGE_PROVIDER_ID FROM FED_USER_ATTRIBUTE + WHERE NAME IN ('password','password-confirm') +---- + +If there are any affected federated users, we also recommend removing these attributes from the database by running the following SQL statement: + +[source,sql] +---- +DELETE FROM FED_USER_ATTRIBUTE UA WHERE UA.NAME IN ('password','password-confirm') +---- + +If your custom user storage provider is managing attributes itself, you should look at your custom storage to remove the `password` and `password-confirm` attributes. diff --git a/services/src/main/java/org/keycloak/userprofile/LegacyAttributes.java b/services/src/main/java/org/keycloak/userprofile/LegacyAttributes.java index c83f630fa77c..b2ce22adfe9c 100644 --- a/services/src/main/java/org/keycloak/userprofile/LegacyAttributes.java +++ b/services/src/main/java/org/keycloak/userprofile/LegacyAttributes.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Map; +import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; @@ -23,7 +24,19 @@ public LegacyAttributes(UserProfileContext context, Map attributes, U @Override protected boolean isSupportedAttribute(String name) { - return true; + if (UserProfileContext.USER_API.equals(context) || UserProfileContext.ACCOUNT.equals(context)) { + return true; + } + + if (super.isSupportedAttribute(name)) { + return true; + } + + if (name.startsWith(Constants.USER_ATTRIBUTES_PREFIX)) { + return true; + } + + return false; } @Override diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java index 25115aad02f6..a4fac8f5cb81 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java @@ -65,6 +65,7 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; /** @@ -373,6 +374,10 @@ public void registerUserSuccess() { String userId = events.expectRegister(username, "registerUserSuccess@email").assertEvent().getUserId(); assertUserRegistered(userId, username.toLowerCase(), "registerusersuccess@email"); + + UserRepresentation user = getUser(userId); + + assertNull(user.getAttributes()); } private void assertUserRegistered(String userId, String username, String email) { From 074e85b4b6b200d0554c07aba8ea1221ba79aab6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 12 Sep 2023 17:14:01 +0000 Subject: [PATCH 135/135] Set version to 22.0.3 --- adapters/oidc/adapter-core/pom.xml | 2 +- adapters/oidc/installed/pom.xml | 2 +- adapters/oidc/jakarta-servlet-filter/pom.xml | 2 +- adapters/oidc/jaxrs-oauth-client/pom.xml | 2 +- adapters/oidc/jetty/jetty-core/pom.xml | 2 +- adapters/oidc/jetty/jetty9.4/pom.xml | 2 +- adapters/oidc/jetty/pom.xml | 2 +- adapters/oidc/js/pom.xml | 2 +- adapters/oidc/pom.xml | 2 +- adapters/oidc/servlet-filter/pom.xml | 2 +- adapters/oidc/spring-boot-adapter-core/pom.xml | 2 +- adapters/oidc/spring-boot-container-bundle/pom.xml | 2 +- adapters/oidc/spring-boot2/pom.xml | 2 +- adapters/oidc/spring-security/pom.xml | 2 +- adapters/oidc/tomcat/pom.xml | 2 +- adapters/oidc/tomcat/tomcat-core/pom.xml | 2 +- adapters/oidc/tomcat/tomcat/pom.xml | 2 +- adapters/oidc/undertow/pom.xml | 2 +- adapters/oidc/wildfly-elytron/pom.xml | 2 +- adapters/oidc/wildfly/pom.xml | 2 +- adapters/oidc/wildfly/wildfly-subsystem/pom.xml | 2 +- adapters/pom.xml | 2 +- adapters/saml/core-public/pom.xml | 2 +- adapters/saml/core/pom.xml | 2 +- adapters/saml/jakarta-servlet-filter/pom.xml | 2 +- adapters/saml/jetty/jetty-core/pom.xml | 2 +- adapters/saml/jetty/jetty9.4/pom.xml | 2 +- adapters/saml/jetty/pom.xml | 2 +- adapters/saml/pom.xml | 2 +- adapters/saml/servlet-filter/pom.xml | 2 +- adapters/saml/tomcat/pom.xml | 2 +- adapters/saml/tomcat/tomcat-core/pom.xml | 2 +- adapters/saml/tomcat/tomcat/pom.xml | 2 +- adapters/saml/undertow/pom.xml | 2 +- adapters/saml/wildfly-elytron-jakarta/pom.xml | 2 +- adapters/saml/wildfly-elytron/pom.xml | 2 +- adapters/saml/wildfly/pom.xml | 2 +- adapters/saml/wildfly/wildfly-jakarta-subsystem/pom.xml | 2 +- adapters/saml/wildfly/wildfly-subsystem/pom.xml | 2 +- adapters/spi/adapter-spi/pom.xml | 2 +- adapters/spi/jakarta-servlet-adapter-spi/pom.xml | 2 +- adapters/spi/jboss-adapter-core/pom.xml | 2 +- adapters/spi/jetty-adapter-spi/pom.xml | 2 +- adapters/spi/pom.xml | 2 +- adapters/spi/servlet-adapter-spi/pom.xml | 2 +- adapters/spi/tomcat-adapter-spi/pom.xml | 2 +- adapters/spi/undertow-adapter-spi/pom.xml | 2 +- authz/client/pom.xml | 2 +- authz/policy-enforcer/pom.xml | 2 +- authz/policy/common/pom.xml | 2 +- authz/policy/pom.xml | 2 +- authz/pom.xml | 2 +- boms/adapter/pom.xml | 2 +- boms/misc/pom.xml | 2 +- boms/pom.xml | 2 +- boms/spi/pom.xml | 2 +- common/pom.xml | 2 +- core/pom.xml | 2 +- crypto/default/pom.xml | 2 +- crypto/elytron/pom.xml | 2 +- crypto/fips1402/pom.xml | 2 +- crypto/pom.xml | 2 +- dependencies/pom.xml | 2 +- dependencies/server-all/pom.xml | 2 +- dependencies/server-min/pom.xml | 2 +- distribution/adapters/jetty94-adapter-zip/pom.xml | 2 +- distribution/adapters/pom.xml | 2 +- distribution/adapters/tomcat-adapter-zip/pom.xml | 2 +- distribution/adapters/wildfly-adapter/pom.xml | 2 +- distribution/api-docs-dist/pom.xml | 2 +- distribution/downloads/pom.xml | 2 +- distribution/feature-packs/adapter-feature-pack/pom.xml | 2 +- distribution/feature-packs/pom.xml | 2 +- distribution/galleon-feature-packs/pom.xml | 2 +- .../saml-adapter-galleon-pack/pom.xml | 2 +- distribution/licenses-common/pom.xml | 2 +- distribution/maven-plugins/licenses-processor/pom.xml | 2 +- distribution/maven-plugins/pom.xml | 2 +- distribution/pom.xml | 2 +- distribution/saml-adapters/jetty94-adapter-zip/pom.xml | 2 +- distribution/saml-adapters/pom.xml | 2 +- distribution/saml-adapters/tomcat-adapter-zip/pom.xml | 2 +- distribution/saml-adapters/wildfly-adapter/pom.xml | 2 +- .../wildfly-adapter/wildfly-adapter-jakarta-zip/pom.xml | 2 +- .../wildfly-adapter/wildfly-adapter-zip/pom.xml | 2 +- .../wildfly-adapter/wildfly-jakarta-modules/pom.xml | 2 +- .../saml-adapters/wildfly-adapter/wildfly-modules/pom.xml | 2 +- docs/documentation/aggregation/pom.xml | 2 +- docs/documentation/api_documentation/pom.xml | 2 +- docs/documentation/authorization_services/pom.xml | 2 +- docs/documentation/dist/pom.xml | 2 +- docs/documentation/header-maven-plugin/pom.xml | 4 ++-- docs/documentation/pom.xml | 4 ++-- docs/documentation/release_notes/pom.xml | 2 +- docs/documentation/securing_apps/pom.xml | 2 +- docs/documentation/server_admin/pom.xml | 2 +- docs/documentation/server_development/pom.xml | 2 +- docs/documentation/tests/pom.xml | 2 +- .../topics/templates/document-attributes.adoc | 8 ++++---- docs/documentation/upgrading/pom.xml | 2 +- docs/guides/pom.xml | 2 +- docs/maven-plugin/pom.xml | 2 +- docs/pom.xml | 2 +- examples/admin-client/pom.xml | 2 +- examples/js-console/pom.xml | 2 +- examples/kerberos/pom.xml | 2 +- examples/ldap/pom.xml | 2 +- examples/pom.xml | 2 +- examples/providers/authenticator/pom.xml | 2 +- examples/providers/domain-extension/pom.xml | 2 +- examples/providers/pom.xml | 2 +- examples/providers/rest/pom.xml | 2 +- examples/saml/pom.xml | 2 +- examples/saml/servlet-filter/pom.xml | 2 +- examples/themes/pom.xml | 2 +- federation/kerberos/pom.xml | 2 +- federation/ldap/pom.xml | 2 +- federation/pom.xml | 2 +- federation/sssd/pom.xml | 2 +- integration/admin-client-jee/pom.xml | 2 +- integration/admin-client/pom.xml | 2 +- integration/client-cli/admin-cli/pom.xml | 2 +- integration/client-cli/client-cli-dist/pom.xml | 2 +- integration/client-cli/client-registration-cli/pom.xml | 2 +- integration/client-cli/pom.xml | 2 +- integration/client-registration/pom.xml | 2 +- integration/pom.xml | 2 +- js/apps/account-ui/pom.xml | 2 +- js/apps/admin-ui/pom.xml | 2 +- js/libs/keycloak-admin-client/package.json | 2 +- js/libs/keycloak-admin-client/pom.xml | 2 +- js/libs/keycloak-js/package.json | 2 +- js/libs/keycloak-js/pom.xml | 2 +- js/pom.xml | 2 +- misc/keycloak-test-helper/pom.xml | 2 +- misc/pom.xml | 2 +- .../keycloak-spring-boot-starter/pom.xml | 2 +- misc/spring-boot-starter/pom.xml | 2 +- model/build-processor/pom.xml | 2 +- model/infinispan/pom.xml | 2 +- model/jpa/pom.xml | 2 +- model/legacy-private/pom.xml | 2 +- model/legacy-services/pom.xml | 2 +- model/legacy/pom.xml | 2 +- model/map-file/pom.xml | 2 +- model/map-hot-rod/pom.xml | 2 +- model/map-jpa/pom.xml | 2 +- model/map-ldap/pom.xml | 2 +- model/map/pom.xml | 2 +- model/pom.xml | 2 +- operator/pom.xml | 2 +- pom.xml | 2 +- quarkus/config-api/pom.xml | 2 +- quarkus/container/Dockerfile | 2 +- quarkus/deployment/pom.xml | 2 +- quarkus/dist/pom.xml | 2 +- quarkus/pom.xml | 2 +- quarkus/runtime/pom.xml | 2 +- quarkus/server/pom.xml | 2 +- quarkus/tests/integration/pom.xml | 2 +- quarkus/tests/junit5/pom.xml | 2 +- quarkus/tests/pom.xml | 2 +- rest/admin-ui-ext/pom.xml | 2 +- rest/pom.xml | 2 +- saml-core-api/pom.xml | 2 +- saml-core/pom.xml | 2 +- server-spi-private/pom.xml | 2 +- server-spi/pom.xml | 2 +- services/pom.xml | 2 +- testsuite/db-allocator-plugin/pom.xml | 2 +- testsuite/integration-arquillian/pom.xml | 2 +- .../integration-arquillian/servers/adapter-spi/pom.xml | 2 +- .../servers/adapter-spi/undertow-adapter-jakarta/pom.xml | 2 +- .../adapter-spi/undertow-adapter-saml-jakarta/pom.xml | 2 +- .../adapter-spi/undertow-adapter-spi-jakarta/pom.xml | 2 +- .../servers/app-server/app-server-spi/pom.xml | 2 +- .../servers/app-server/jboss/eap/pom.xml | 2 +- .../servers/app-server/jboss/eap6/pom.xml | 2 +- .../servers/app-server/jboss/pom.xml | 2 +- .../servers/app-server/jboss/wildfly/pom.xml | 2 +- .../servers/app-server/jetty/94/pom.xml | 2 +- .../servers/app-server/jetty/common/pom.xml | 2 +- .../servers/app-server/jetty/pom.xml | 2 +- .../servers/app-server/karaf/fuse63/pom.xml | 2 +- .../servers/app-server/karaf/fuse7x/pom.xml | 2 +- .../servers/app-server/karaf/pom.xml | 2 +- .../integration-arquillian/servers/app-server/pom.xml | 2 +- .../servers/app-server/tomcat/common/pom.xml | 2 +- .../servers/app-server/tomcat/pom.xml | 2 +- .../servers/app-server/tomcat/tomcat8/pom.xml | 2 +- .../servers/app-server/tomcat/tomcat9/pom.xml | 2 +- .../servers/app-server/undertow/pom.xml | 2 +- .../integration-arquillian/servers/auth-server/pom.xml | 2 +- .../servers/auth-server/quarkus/pom.xml | 2 +- .../servers/auth-server/services/pom.xml | 2 +- .../services/testsuite-providers-deployment/pom.xml | 2 +- .../auth-server/services/testsuite-providers/pom.xml | 2 +- .../servers/auth-server/undertow/pom.xml | 2 +- .../servers/cache-server/infinispan/datagrid/pom.xml | 2 +- .../servers/cache-server/infinispan/infinispan/pom.xml | 2 +- .../servers/cache-server/infinispan/pom.xml | 2 +- .../servers/cache-server/legacy/datagrid/pom.xml | 2 +- .../servers/cache-server/legacy/infinispan/pom.xml | 2 +- .../servers/cache-server/legacy/pom.xml | 2 +- .../integration-arquillian/servers/cache-server/pom.xml | 2 +- .../integration-arquillian/servers/migration/pom.xml | 2 +- testsuite/integration-arquillian/servers/pom.xml | 2 +- .../test-apps/app-profile-jee/pom.xml | 2 +- .../test-apps/cors/angular-product/pom.xml | 2 +- .../test-apps/cors/database-service/pom.xml | 2 +- testsuite/integration-arquillian/test-apps/cors/pom.xml | 2 +- .../test-apps/fuse/camel-fuse7-undertow/pom.xml | 2 +- .../integration-arquillian/test-apps/fuse/camel/pom.xml | 2 +- .../test-apps/fuse/customer-app-fuse/pom.xml | 2 +- .../test-apps/fuse/cxf-jaxrs-fuse7-undertow/pom.xml | 2 +- .../test-apps/fuse/cxf-jaxrs/pom.xml | 2 +- .../test-apps/fuse/cxf-jaxws-fuse7-undertow/pom.xml | 2 +- .../test-apps/fuse/cxf-jaxws/pom.xml | 2 +- .../test-apps/fuse/external-config/pom.xml | 2 +- .../test-apps/fuse/features/pom.xml | 2 +- testsuite/integration-arquillian/test-apps/fuse/pom.xml | 2 +- .../test-apps/fuse/product-app-fuse/pom.xml | 2 +- .../test-apps/fuse/product-app-fuse7-undertow/pom.xml | 2 +- .../test-apps/hello-world-authz-service/pom.xml | 2 +- testsuite/integration-arquillian/test-apps/pom.xml | 2 +- .../test-apps/servlet-authz/pom.xml | 2 +- .../test-apps/servlet-policy-enforcer/pom.xml | 2 +- .../integration-arquillian/test-apps/servlets/pom.xml | 2 +- .../test-apps/spring-boot-adapter-app/pom.xml | 2 +- .../test-apps/test-apps-dist/pom.xml | 2 +- testsuite/integration-arquillian/tests/base/pom.xml | 2 +- .../tests/other/adapters/jboss/pom.xml | 2 +- .../tests/other/adapters/karaf/fuse61/pom.xml | 2 +- .../tests/other/adapters/karaf/fuse62/pom.xml | 2 +- .../tests/other/adapters/karaf/karaf3/pom.xml | 2 +- .../tests/other/adapters/karaf/pom.xml | 2 +- .../integration-arquillian/tests/other/adapters/pom.xml | 2 +- .../tests/other/adapters/was/pom.xml | 2 +- .../tests/other/adapters/was/was8/pom.xml | 2 +- .../tests/other/adapters/wls/pom.xml | 2 +- .../tests/other/adapters/wls/wls12/pom.xml | 2 +- .../integration-arquillian/tests/other/base-ui/pom.xml | 2 +- .../tests/other/jpa-performance/pom.xml | 2 +- .../tests/other/mod_auth_mellon/pom.xml | 2 +- testsuite/integration-arquillian/tests/other/pom.xml | 2 +- .../tests/other/springboot-tests/pom.xml | 2 +- testsuite/integration-arquillian/tests/other/sssd/pom.xml | 2 +- .../integration-arquillian/tests/other/webauthn/pom.xml | 2 +- testsuite/integration-arquillian/tests/pom.xml | 2 +- testsuite/integration-arquillian/util/pom.xml | 2 +- testsuite/model/pom.xml | 2 +- testsuite/pom.xml | 2 +- testsuite/utils/pom.xml | 2 +- themes/pom.xml | 2 +- util/embedded-ldap/pom.xml | 2 +- util/pom.xml | 2 +- 256 files changed, 261 insertions(+), 261 deletions(-) diff --git a/adapters/oidc/adapter-core/pom.xml b/adapters/oidc/adapter-core/pom.xml index 2baddd35d190..ec3d248abd9c 100755 --- a/adapters/oidc/adapter-core/pom.xml +++ b/adapters/oidc/adapter-core/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/oidc/installed/pom.xml b/adapters/oidc/installed/pom.xml index 985b81d485f2..65771c156fee 100755 --- a/adapters/oidc/installed/pom.xml +++ b/adapters/oidc/installed/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/oidc/jakarta-servlet-filter/pom.xml b/adapters/oidc/jakarta-servlet-filter/pom.xml index c2105f8e1e94..e5407d5cf192 100755 --- a/adapters/oidc/jakarta-servlet-filter/pom.xml +++ b/adapters/oidc/jakarta-servlet-filter/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/oidc/jaxrs-oauth-client/pom.xml b/adapters/oidc/jaxrs-oauth-client/pom.xml index 1d5b773a2d1f..3440d0d9465a 100755 --- a/adapters/oidc/jaxrs-oauth-client/pom.xml +++ b/adapters/oidc/jaxrs-oauth-client/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/oidc/jetty/jetty-core/pom.xml b/adapters/oidc/jetty/jetty-core/pom.xml index f36d9780af3f..e4f49eaf8fcb 100755 --- a/adapters/oidc/jetty/jetty-core/pom.xml +++ b/adapters/oidc/jetty/jetty-core/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../../pom.xml 4.0.0 diff --git a/adapters/oidc/jetty/jetty9.4/pom.xml b/adapters/oidc/jetty/jetty9.4/pom.xml index 3ddd0b2fa930..02b7ee2398f5 100644 --- a/adapters/oidc/jetty/jetty9.4/pom.xml +++ b/adapters/oidc/jetty/jetty9.4/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../../pom.xml 4.0.0 diff --git a/adapters/oidc/jetty/pom.xml b/adapters/oidc/jetty/pom.xml index 30b8b7706bea..7e87eedcc1f7 100755 --- a/adapters/oidc/jetty/pom.xml +++ b/adapters/oidc/jetty/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml Keycloak Jetty Integration diff --git a/adapters/oidc/js/pom.xml b/adapters/oidc/js/pom.xml index 40c114db2cf2..6cee68df49e5 100644 --- a/adapters/oidc/js/pom.xml +++ b/adapters/oidc/js/pom.xml @@ -5,7 +5,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml diff --git a/adapters/oidc/pom.xml b/adapters/oidc/pom.xml index f4e37273be18..fd5f9643e30f 100755 --- a/adapters/oidc/pom.xml +++ b/adapters/oidc/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../pom.xml Keycloak OIDC Client Adapter Modules diff --git a/adapters/oidc/servlet-filter/pom.xml b/adapters/oidc/servlet-filter/pom.xml index 1e26928aa9b0..6ba7ea5e9f42 100755 --- a/adapters/oidc/servlet-filter/pom.xml +++ b/adapters/oidc/servlet-filter/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/oidc/spring-boot-adapter-core/pom.xml b/adapters/oidc/spring-boot-adapter-core/pom.xml index ea1f9c6a6736..6aad4cb1d750 100755 --- a/adapters/oidc/spring-boot-adapter-core/pom.xml +++ b/adapters/oidc/spring-boot-adapter-core/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/oidc/spring-boot-container-bundle/pom.xml b/adapters/oidc/spring-boot-container-bundle/pom.xml index fd0be595f8d6..e98488278e72 100644 --- a/adapters/oidc/spring-boot-container-bundle/pom.xml +++ b/adapters/oidc/spring-boot-container-bundle/pom.xml @@ -4,7 +4,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml spring-boot-container-bundle diff --git a/adapters/oidc/spring-boot2/pom.xml b/adapters/oidc/spring-boot2/pom.xml index 86115f6004fa..91eb8ea470ef 100755 --- a/adapters/oidc/spring-boot2/pom.xml +++ b/adapters/oidc/spring-boot2/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/oidc/spring-security/pom.xml b/adapters/oidc/spring-security/pom.xml index c85b8180a240..1837d5eee46e 100644 --- a/adapters/oidc/spring-security/pom.xml +++ b/adapters/oidc/spring-security/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/oidc/tomcat/pom.xml b/adapters/oidc/tomcat/pom.xml index e79897be4b29..e58d51a321a2 100755 --- a/adapters/oidc/tomcat/pom.xml +++ b/adapters/oidc/tomcat/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml Keycloak Tomcat Integration diff --git a/adapters/oidc/tomcat/tomcat-core/pom.xml b/adapters/oidc/tomcat/tomcat-core/pom.xml index b7859c0d7d20..b042e17eaa9e 100755 --- a/adapters/oidc/tomcat/tomcat-core/pom.xml +++ b/adapters/oidc/tomcat/tomcat-core/pom.xml @@ -21,7 +21,7 @@ keycloak-tomcat-integration-pom org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml 4.0.0 diff --git a/adapters/oidc/tomcat/tomcat/pom.xml b/adapters/oidc/tomcat/tomcat/pom.xml index 2a5d96149038..5df4719a64a2 100755 --- a/adapters/oidc/tomcat/tomcat/pom.xml +++ b/adapters/oidc/tomcat/tomcat/pom.xml @@ -21,7 +21,7 @@ keycloak-tomcat-integration-pom org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml 4.0.0 diff --git a/adapters/oidc/undertow/pom.xml b/adapters/oidc/undertow/pom.xml index 6d179ae84d86..540f1e70ba15 100755 --- a/adapters/oidc/undertow/pom.xml +++ b/adapters/oidc/undertow/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/oidc/wildfly-elytron/pom.xml b/adapters/oidc/wildfly-elytron/pom.xml index 1d8f5c9b5b86..7466134ee009 100755 --- a/adapters/oidc/wildfly-elytron/pom.xml +++ b/adapters/oidc/wildfly-elytron/pom.xml @@ -22,7 +22,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/oidc/wildfly/pom.xml b/adapters/oidc/wildfly/pom.xml index 173bf616d01a..cb1da7153437 100755 --- a/adapters/oidc/wildfly/pom.xml +++ b/adapters/oidc/wildfly/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml Keycloak WildFly Integration diff --git a/adapters/oidc/wildfly/wildfly-subsystem/pom.xml b/adapters/oidc/wildfly/wildfly-subsystem/pom.xml index 15f36da7fef0..fbef9341e887 100755 --- a/adapters/oidc/wildfly/wildfly-subsystem/pom.xml +++ b/adapters/oidc/wildfly/wildfly-subsystem/pom.xml @@ -21,7 +21,7 @@ org.keycloak keycloak-parent - 999.0.0-SNAPSHOT + 22.0.3 ../../../../pom.xml diff --git a/adapters/pom.xml b/adapters/pom.xml index fac0534171c2..5aa4b3069cd4 100755 --- a/adapters/pom.xml +++ b/adapters/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml Keycloak Adapters diff --git a/adapters/saml/core-public/pom.xml b/adapters/saml/core-public/pom.xml index a5e4e0aa3f9b..5c046466adb7 100755 --- a/adapters/saml/core-public/pom.xml +++ b/adapters/saml/core-public/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/saml/core/pom.xml b/adapters/saml/core/pom.xml index d72de7b0ee37..8fdd70b84ca5 100755 --- a/adapters/saml/core/pom.xml +++ b/adapters/saml/core/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/saml/jakarta-servlet-filter/pom.xml b/adapters/saml/jakarta-servlet-filter/pom.xml index f7ee0ee6bcea..aef3b482da64 100755 --- a/adapters/saml/jakarta-servlet-filter/pom.xml +++ b/adapters/saml/jakarta-servlet-filter/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/saml/jetty/jetty-core/pom.xml b/adapters/saml/jetty/jetty-core/pom.xml index 7ad707523966..156defcb172b 100755 --- a/adapters/saml/jetty/jetty-core/pom.xml +++ b/adapters/saml/jetty/jetty-core/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../../pom.xml 4.0.0 diff --git a/adapters/saml/jetty/jetty9.4/pom.xml b/adapters/saml/jetty/jetty9.4/pom.xml index 91e5589f0b4c..fac207523508 100644 --- a/adapters/saml/jetty/jetty9.4/pom.xml +++ b/adapters/saml/jetty/jetty9.4/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../../pom.xml 4.0.0 diff --git a/adapters/saml/jetty/pom.xml b/adapters/saml/jetty/pom.xml index dda79695de4a..77bfc8d9b339 100755 --- a/adapters/saml/jetty/pom.xml +++ b/adapters/saml/jetty/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml Keycloak SAML Jetty Integration diff --git a/adapters/saml/pom.xml b/adapters/saml/pom.xml index f941cc0876a7..d1ce6d9adfb8 100755 --- a/adapters/saml/pom.xml +++ b/adapters/saml/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../pom.xml Keycloak SAML Client Adapter Modules diff --git a/adapters/saml/servlet-filter/pom.xml b/adapters/saml/servlet-filter/pom.xml index 5fc027042952..1242e2b56772 100755 --- a/adapters/saml/servlet-filter/pom.xml +++ b/adapters/saml/servlet-filter/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/saml/tomcat/pom.xml b/adapters/saml/tomcat/pom.xml index 70bd25315d01..751e5e5c67b8 100755 --- a/adapters/saml/tomcat/pom.xml +++ b/adapters/saml/tomcat/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml Keycloak SAML Tomcat Integration diff --git a/adapters/saml/tomcat/tomcat-core/pom.xml b/adapters/saml/tomcat/tomcat-core/pom.xml index 9844e59f090d..c30181bb2294 100755 --- a/adapters/saml/tomcat/tomcat-core/pom.xml +++ b/adapters/saml/tomcat/tomcat-core/pom.xml @@ -21,7 +21,7 @@ keycloak-saml-tomcat-integration-pom org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml 4.0.0 diff --git a/adapters/saml/tomcat/tomcat/pom.xml b/adapters/saml/tomcat/tomcat/pom.xml index 0dd64f673b34..9fb7e8a8bc18 100755 --- a/adapters/saml/tomcat/tomcat/pom.xml +++ b/adapters/saml/tomcat/tomcat/pom.xml @@ -21,7 +21,7 @@ keycloak-saml-tomcat-integration-pom org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml 4.0.0 diff --git a/adapters/saml/undertow/pom.xml b/adapters/saml/undertow/pom.xml index b4d5480a78c1..335beaac0ef6 100755 --- a/adapters/saml/undertow/pom.xml +++ b/adapters/saml/undertow/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/saml/wildfly-elytron-jakarta/pom.xml b/adapters/saml/wildfly-elytron-jakarta/pom.xml index c6b6ff388d91..ca0976e023b7 100755 --- a/adapters/saml/wildfly-elytron-jakarta/pom.xml +++ b/adapters/saml/wildfly-elytron-jakarta/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/saml/wildfly-elytron/pom.xml b/adapters/saml/wildfly-elytron/pom.xml index 5dfaf1bb6e32..18c652a61bfd 100755 --- a/adapters/saml/wildfly-elytron/pom.xml +++ b/adapters/saml/wildfly-elytron/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/saml/wildfly/pom.xml b/adapters/saml/wildfly/pom.xml index 02f49d830727..3508193a5de5 100755 --- a/adapters/saml/wildfly/pom.xml +++ b/adapters/saml/wildfly/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml Keycloak SAML Wildfly Integration diff --git a/adapters/saml/wildfly/wildfly-jakarta-subsystem/pom.xml b/adapters/saml/wildfly/wildfly-jakarta-subsystem/pom.xml index 016a1037a8d7..b8dd2d05fe02 100755 --- a/adapters/saml/wildfly/wildfly-jakarta-subsystem/pom.xml +++ b/adapters/saml/wildfly/wildfly-jakarta-subsystem/pom.xml @@ -21,7 +21,7 @@ org.keycloak keycloak-parent - 999.0.0-SNAPSHOT + 22.0.3 ../../../../pom.xml diff --git a/adapters/saml/wildfly/wildfly-subsystem/pom.xml b/adapters/saml/wildfly/wildfly-subsystem/pom.xml index 90b38b0f7462..0fbc4e84e10a 100755 --- a/adapters/saml/wildfly/wildfly-subsystem/pom.xml +++ b/adapters/saml/wildfly/wildfly-subsystem/pom.xml @@ -21,7 +21,7 @@ org.keycloak keycloak-parent - 999.0.0-SNAPSHOT + 22.0.3 ../../../../pom.xml diff --git a/adapters/spi/adapter-spi/pom.xml b/adapters/spi/adapter-spi/pom.xml index aab344d284be..fb4f88d7b5f7 100755 --- a/adapters/spi/adapter-spi/pom.xml +++ b/adapters/spi/adapter-spi/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/spi/jakarta-servlet-adapter-spi/pom.xml b/adapters/spi/jakarta-servlet-adapter-spi/pom.xml index a5d8d48e4729..8c30e31223ad 100755 --- a/adapters/spi/jakarta-servlet-adapter-spi/pom.xml +++ b/adapters/spi/jakarta-servlet-adapter-spi/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/spi/jboss-adapter-core/pom.xml b/adapters/spi/jboss-adapter-core/pom.xml index a184ae5e0d8a..f513f0b75961 100755 --- a/adapters/spi/jboss-adapter-core/pom.xml +++ b/adapters/spi/jboss-adapter-core/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/spi/jetty-adapter-spi/pom.xml b/adapters/spi/jetty-adapter-spi/pom.xml index 17a0654fd008..a5a4de039573 100755 --- a/adapters/spi/jetty-adapter-spi/pom.xml +++ b/adapters/spi/jetty-adapter-spi/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/spi/pom.xml b/adapters/spi/pom.xml index 6bd05d9b412a..1e409d2e6ec7 100755 --- a/adapters/spi/pom.xml +++ b/adapters/spi/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../pom.xml Keycloak Client Adapter SPI Modules diff --git a/adapters/spi/servlet-adapter-spi/pom.xml b/adapters/spi/servlet-adapter-spi/pom.xml index 2d8f0ca1f6dd..bb4412243691 100755 --- a/adapters/spi/servlet-adapter-spi/pom.xml +++ b/adapters/spi/servlet-adapter-spi/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/spi/tomcat-adapter-spi/pom.xml b/adapters/spi/tomcat-adapter-spi/pom.xml index 0588a9826061..ef8ba16816fa 100755 --- a/adapters/spi/tomcat-adapter-spi/pom.xml +++ b/adapters/spi/tomcat-adapter-spi/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml 4.0.0 diff --git a/adapters/spi/undertow-adapter-spi/pom.xml b/adapters/spi/undertow-adapter-spi/pom.xml index 884b93c77400..f30182dc51ae 100755 --- a/adapters/spi/undertow-adapter-spi/pom.xml +++ b/adapters/spi/undertow-adapter-spi/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml 4.0.0 diff --git a/authz/client/pom.xml b/authz/client/pom.xml index d410042e7ec0..83e17ddeaaee 100644 --- a/authz/client/pom.xml +++ b/authz/client/pom.xml @@ -7,7 +7,7 @@ org.keycloak keycloak-authz-parent - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml diff --git a/authz/policy-enforcer/pom.xml b/authz/policy-enforcer/pom.xml index 767283ce988b..22535c318925 100755 --- a/authz/policy-enforcer/pom.xml +++ b/authz/policy-enforcer/pom.xml @@ -21,7 +21,7 @@ org.keycloak keycloak-authz-parent - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml 4.0.0 diff --git a/authz/policy/common/pom.xml b/authz/policy/common/pom.xml index d5b13c0cff82..10904e644e12 100644 --- a/authz/policy/common/pom.xml +++ b/authz/policy/common/pom.xml @@ -25,7 +25,7 @@ org.keycloak keycloak-authz-provider-parent - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml diff --git a/authz/policy/pom.xml b/authz/policy/pom.xml index 48091592faad..f8947105cb49 100644 --- a/authz/policy/pom.xml +++ b/authz/policy/pom.xml @@ -7,7 +7,7 @@ org.keycloak keycloak-authz-parent - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml diff --git a/authz/pom.xml b/authz/pom.xml index 31224da50309..a32a96e85cbd 100644 --- a/authz/pom.xml +++ b/authz/pom.xml @@ -7,7 +7,7 @@ org.keycloak keycloak-parent - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml diff --git a/boms/adapter/pom.xml b/boms/adapter/pom.xml index 417c7107fde5..3ae9a1a06c69 100644 --- a/boms/adapter/pom.xml +++ b/boms/adapter/pom.xml @@ -22,7 +22,7 @@ org.keycloak.bom keycloak-bom-parent - 999.0.0-SNAPSHOT + 22.0.3 org.keycloak.bom diff --git a/boms/misc/pom.xml b/boms/misc/pom.xml index 7305aa3bb610..ed2a00557371 100644 --- a/boms/misc/pom.xml +++ b/boms/misc/pom.xml @@ -22,7 +22,7 @@ org.keycloak.bom keycloak-bom-parent - 999.0.0-SNAPSHOT + 22.0.3 org.keycloak.bom diff --git a/boms/pom.xml b/boms/pom.xml index c67416d7afa0..6faddaeb86d3 100644 --- a/boms/pom.xml +++ b/boms/pom.xml @@ -27,7 +27,7 @@ org.keycloak.bom keycloak-bom-parent - 999.0.0-SNAPSHOT + 22.0.3 pom diff --git a/boms/spi/pom.xml b/boms/spi/pom.xml index 339c18bb3033..e4265628ef65 100644 --- a/boms/spi/pom.xml +++ b/boms/spi/pom.xml @@ -23,7 +23,7 @@ org.keycloak.bom keycloak-bom-parent - 999.0.0-SNAPSHOT + 22.0.3 org.keycloak.bom diff --git a/common/pom.xml b/common/pom.xml index 5f644cbc0af2..e6894a37db72 100755 --- a/common/pom.xml +++ b/common/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml 4.0.0 diff --git a/core/pom.xml b/core/pom.xml index 7e4ef767d4b6..3696abfcb901 100755 --- a/core/pom.xml +++ b/core/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml 4.0.0 diff --git a/crypto/default/pom.xml b/crypto/default/pom.xml index 2b0cf1fb234f..dacd47e18f9b 100644 --- a/crypto/default/pom.xml +++ b/crypto/default/pom.xml @@ -21,7 +21,7 @@ keycloak-crypto-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml 4.0.0 diff --git a/crypto/elytron/pom.xml b/crypto/elytron/pom.xml index 51b7d6dbeac7..04f7c619ca2c 100644 --- a/crypto/elytron/pom.xml +++ b/crypto/elytron/pom.xml @@ -21,7 +21,7 @@ keycloak-crypto-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml 4.0.0 diff --git a/crypto/fips1402/pom.xml b/crypto/fips1402/pom.xml index 7691a0f12880..8a628fb87da9 100644 --- a/crypto/fips1402/pom.xml +++ b/crypto/fips1402/pom.xml @@ -21,7 +21,7 @@ keycloak-crypto-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml 4.0.0 diff --git a/crypto/pom.xml b/crypto/pom.xml index de2f6d339653..2e56258a1436 100644 --- a/crypto/pom.xml +++ b/crypto/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml Keycloak Crypto Parent diff --git a/dependencies/pom.xml b/dependencies/pom.xml index 019146bf55c3..b7d66daf3712 100755 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/dependencies/server-all/pom.xml b/dependencies/server-all/pom.xml index 485aacbcfa71..0232a8160186 100755 --- a/dependencies/server-all/pom.xml +++ b/dependencies/server-all/pom.xml @@ -21,7 +21,7 @@ keycloak-dependencies-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/dependencies/server-min/pom.xml b/dependencies/server-min/pom.xml index 8749833cd6fd..2af57e3b0746 100755 --- a/dependencies/server-min/pom.xml +++ b/dependencies/server-min/pom.xml @@ -21,7 +21,7 @@ keycloak-dependencies-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/distribution/adapters/jetty94-adapter-zip/pom.xml b/distribution/adapters/jetty94-adapter-zip/pom.xml index 7e980c961d95..2546016f2942 100644 --- a/distribution/adapters/jetty94-adapter-zip/pom.xml +++ b/distribution/adapters/jetty94-adapter-zip/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml diff --git a/distribution/adapters/pom.xml b/distribution/adapters/pom.xml index dc45b59bf0b0..58fc3e4f7393 100755 --- a/distribution/adapters/pom.xml +++ b/distribution/adapters/pom.xml @@ -20,7 +20,7 @@ keycloak-distribution-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 Adapters Distribution Parent diff --git a/distribution/adapters/tomcat-adapter-zip/pom.xml b/distribution/adapters/tomcat-adapter-zip/pom.xml index c6f4d90b26c6..afe8ff74eb57 100755 --- a/distribution/adapters/tomcat-adapter-zip/pom.xml +++ b/distribution/adapters/tomcat-adapter-zip/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml diff --git a/distribution/adapters/wildfly-adapter/pom.xml b/distribution/adapters/wildfly-adapter/pom.xml index 96ad4d4de431..1e669fd705cd 100644 --- a/distribution/adapters/wildfly-adapter/pom.xml +++ b/distribution/adapters/wildfly-adapter/pom.xml @@ -21,7 +21,7 @@ keycloak-adapters-distribution-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 diff --git a/distribution/api-docs-dist/pom.xml b/distribution/api-docs-dist/pom.xml index a2658fcd8f5c..1fcb4395cf1f 100755 --- a/distribution/api-docs-dist/pom.xml +++ b/distribution/api-docs-dist/pom.xml @@ -21,7 +21,7 @@ keycloak-distribution-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 keycloak-api-docs-dist diff --git a/distribution/downloads/pom.xml b/distribution/downloads/pom.xml index 3651b049e9b5..e419e0c8cdb7 100755 --- a/distribution/downloads/pom.xml +++ b/distribution/downloads/pom.xml @@ -21,7 +21,7 @@ keycloak-distribution-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 keycloak-dist-downloads diff --git a/distribution/feature-packs/adapter-feature-pack/pom.xml b/distribution/feature-packs/adapter-feature-pack/pom.xml index cd9b1baf68a9..0eb473e8081f 100755 --- a/distribution/feature-packs/adapter-feature-pack/pom.xml +++ b/distribution/feature-packs/adapter-feature-pack/pom.xml @@ -19,7 +19,7 @@ org.keycloak feature-packs-parent - 999.0.0-SNAPSHOT + 22.0.3 diff --git a/distribution/feature-packs/pom.xml b/distribution/feature-packs/pom.xml index 4c66eecf261f..72d7f0ac72cc 100644 --- a/distribution/feature-packs/pom.xml +++ b/distribution/feature-packs/pom.xml @@ -20,7 +20,7 @@ keycloak-distribution-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 Feature Pack Builds diff --git a/distribution/galleon-feature-packs/pom.xml b/distribution/galleon-feature-packs/pom.xml index fcbedba3b077..f293b023767d 100644 --- a/distribution/galleon-feature-packs/pom.xml +++ b/distribution/galleon-feature-packs/pom.xml @@ -20,7 +20,7 @@ keycloak-distribution-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 Galleon Feature Pack Builds diff --git a/distribution/galleon-feature-packs/saml-adapter-galleon-pack/pom.xml b/distribution/galleon-feature-packs/saml-adapter-galleon-pack/pom.xml index b88ea26a3833..8a1f7fea3486 100644 --- a/distribution/galleon-feature-packs/saml-adapter-galleon-pack/pom.xml +++ b/distribution/galleon-feature-packs/saml-adapter-galleon-pack/pom.xml @@ -19,7 +19,7 @@ org.keycloak galleon-feature-packs-parent - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/distribution/licenses-common/pom.xml b/distribution/licenses-common/pom.xml index bf615a63ee6f..7afa84f99fce 100644 --- a/distribution/licenses-common/pom.xml +++ b/distribution/licenses-common/pom.xml @@ -20,7 +20,7 @@ keycloak-distribution-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 keycloak-distribution-licenses-common diff --git a/distribution/maven-plugins/licenses-processor/pom.xml b/distribution/maven-plugins/licenses-processor/pom.xml index 547d24d96374..ff73ff8a01cd 100644 --- a/distribution/maven-plugins/licenses-processor/pom.xml +++ b/distribution/maven-plugins/licenses-processor/pom.xml @@ -20,7 +20,7 @@ keycloak-distribution-maven-plugins-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 keycloak-distribution-licenses-maven-plugin diff --git a/distribution/maven-plugins/pom.xml b/distribution/maven-plugins/pom.xml index fe9b7db2e36c..9ff0c1ec6a4f 100644 --- a/distribution/maven-plugins/pom.xml +++ b/distribution/maven-plugins/pom.xml @@ -20,7 +20,7 @@ keycloak-distribution-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 keycloak-distribution-maven-plugins-parent diff --git a/distribution/pom.xml b/distribution/pom.xml index d5ba3d6a1533..75456290217b 100755 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml diff --git a/distribution/saml-adapters/jetty94-adapter-zip/pom.xml b/distribution/saml-adapters/jetty94-adapter-zip/pom.xml index 21315a13f1f7..bedc36701f29 100644 --- a/distribution/saml-adapters/jetty94-adapter-zip/pom.xml +++ b/distribution/saml-adapters/jetty94-adapter-zip/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml diff --git a/distribution/saml-adapters/pom.xml b/distribution/saml-adapters/pom.xml index e357db3f9b4c..629336ab9d92 100755 --- a/distribution/saml-adapters/pom.xml +++ b/distribution/saml-adapters/pom.xml @@ -20,7 +20,7 @@ keycloak-distribution-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 SAML Adapters Distribution Parent diff --git a/distribution/saml-adapters/tomcat-adapter-zip/pom.xml b/distribution/saml-adapters/tomcat-adapter-zip/pom.xml index 3710b43611de..8e734d6d7186 100755 --- a/distribution/saml-adapters/tomcat-adapter-zip/pom.xml +++ b/distribution/saml-adapters/tomcat-adapter-zip/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml diff --git a/distribution/saml-adapters/wildfly-adapter/pom.xml b/distribution/saml-adapters/wildfly-adapter/pom.xml index ab52d865dab5..af78e1b9bb45 100755 --- a/distribution/saml-adapters/wildfly-adapter/pom.xml +++ b/distribution/saml-adapters/wildfly-adapter/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../pom.xml Keycloak Wildfly SAML Adapter diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-adapter-jakarta-zip/pom.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-adapter-jakarta-zip/pom.xml index 794389bd1605..71f20e2a6c25 100755 --- a/distribution/saml-adapters/wildfly-adapter/wildfly-adapter-jakarta-zip/pom.xml +++ b/distribution/saml-adapters/wildfly-adapter/wildfly-adapter-jakarta-zip/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../../pom.xml diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-adapter-zip/pom.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-adapter-zip/pom.xml index d503885a0fb5..a8edfda9e1fe 100755 --- a/distribution/saml-adapters/wildfly-adapter/wildfly-adapter-zip/pom.xml +++ b/distribution/saml-adapters/wildfly-adapter/wildfly-adapter-zip/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../../pom.xml diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-jakarta-modules/pom.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-jakarta-modules/pom.xml index db6047180c51..d9f8de9517f6 100755 --- a/distribution/saml-adapters/wildfly-adapter/wildfly-jakarta-modules/pom.xml +++ b/distribution/saml-adapters/wildfly-adapter/wildfly-jakarta-modules/pom.xml @@ -25,7 +25,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../../pom.xml diff --git a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/pom.xml b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/pom.xml index 68cc6b70703f..c89b1e649690 100755 --- a/distribution/saml-adapters/wildfly-adapter/wildfly-modules/pom.xml +++ b/distribution/saml-adapters/wildfly-adapter/wildfly-modules/pom.xml @@ -25,7 +25,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../../../pom.xml diff --git a/docs/documentation/aggregation/pom.xml b/docs/documentation/aggregation/pom.xml index af92b7cddec8..198d5222c6b9 100644 --- a/docs/documentation/aggregation/pom.xml +++ b/docs/documentation/aggregation/pom.xml @@ -5,7 +5,7 @@ org.keycloak.documentation documentation-parent - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml diff --git a/docs/documentation/api_documentation/pom.xml b/docs/documentation/api_documentation/pom.xml index 580cefbb335e..c6535e88cab2 100644 --- a/docs/documentation/api_documentation/pom.xml +++ b/docs/documentation/api_documentation/pom.xml @@ -5,7 +5,7 @@ org.keycloak.documentation documentation-parent - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml diff --git a/docs/documentation/authorization_services/pom.xml b/docs/documentation/authorization_services/pom.xml index 6e1042aa8410..c913f900602c 100644 --- a/docs/documentation/authorization_services/pom.xml +++ b/docs/documentation/authorization_services/pom.xml @@ -5,7 +5,7 @@ org.keycloak.documentation documentation-parent - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml diff --git a/docs/documentation/dist/pom.xml b/docs/documentation/dist/pom.xml index 7ae91bdb25a5..3720326b59e7 100644 --- a/docs/documentation/dist/pom.xml +++ b/docs/documentation/dist/pom.xml @@ -5,7 +5,7 @@ org.keycloak.documentation documentation-parent - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml diff --git a/docs/documentation/header-maven-plugin/pom.xml b/docs/documentation/header-maven-plugin/pom.xml index 13fc1f635761..49db57a8765a 100644 --- a/docs/documentation/header-maven-plugin/pom.xml +++ b/docs/documentation/header-maven-plugin/pom.xml @@ -5,12 +5,12 @@ documentation-parent org.keycloak.documentation - 999.0.0-SNAPSHOT + 22.0.3 org.keycloak.documentation header-maven-plugin - 999.0.0-SNAPSHOT + 22.0.3 maven-plugin github-maven-plugin diff --git a/docs/documentation/pom.xml b/docs/documentation/pom.xml index a29f0e5d39c2..6ac4eece3d21 100644 --- a/docs/documentation/pom.xml +++ b/docs/documentation/pom.xml @@ -5,14 +5,14 @@ keycloak-docs-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml Keycloak Documentation Parent org.keycloak.documentation documentation-parent - 999.0.0-SNAPSHOT + 22.0.3 pom diff --git a/docs/documentation/release_notes/pom.xml b/docs/documentation/release_notes/pom.xml index 57d62bebae50..775d057cf1f9 100644 --- a/docs/documentation/release_notes/pom.xml +++ b/docs/documentation/release_notes/pom.xml @@ -5,7 +5,7 @@ org.keycloak.documentation documentation-parent - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml diff --git a/docs/documentation/securing_apps/pom.xml b/docs/documentation/securing_apps/pom.xml index 6d8dce060ebe..de97e59206bc 100644 --- a/docs/documentation/securing_apps/pom.xml +++ b/docs/documentation/securing_apps/pom.xml @@ -5,7 +5,7 @@ org.keycloak.documentation documentation-parent - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml diff --git a/docs/documentation/server_admin/pom.xml b/docs/documentation/server_admin/pom.xml index cb0c3fdffef2..2757bc584706 100644 --- a/docs/documentation/server_admin/pom.xml +++ b/docs/documentation/server_admin/pom.xml @@ -5,7 +5,7 @@ org.keycloak.documentation documentation-parent - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml diff --git a/docs/documentation/server_development/pom.xml b/docs/documentation/server_development/pom.xml index 6e0ec2e820c6..3fcc7092dce7 100644 --- a/docs/documentation/server_development/pom.xml +++ b/docs/documentation/server_development/pom.xml @@ -5,7 +5,7 @@ org.keycloak.documentation documentation-parent - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml diff --git a/docs/documentation/tests/pom.xml b/docs/documentation/tests/pom.xml index ea002a114304..0e4105a7b0a1 100644 --- a/docs/documentation/tests/pom.xml +++ b/docs/documentation/tests/pom.xml @@ -55,7 +55,7 @@ org.keycloak.documentation documentation-parent - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml diff --git a/docs/documentation/topics/templates/document-attributes.adoc b/docs/documentation/topics/templates/document-attributes.adoc index e108d1baccf3..97629cd989c9 100644 --- a/docs/documentation/topics/templates/document-attributes.adoc +++ b/docs/documentation/topics/templates/document-attributes.adoc @@ -2,10 +2,10 @@ :project_name_full: Keycloak :project_community: true :project_product: false -:project_version: DEV -:project_versionMvn: 999.0.0-SNAPSHOT -:project_versionNpm: 999.0.0-SNAPSHOT -:project_versionDoc: DEV +:project_version: 22.0.3 +:project_versionMvn: 22.0.3 +:project_versionNpm: 22.0.3 +:project_versionDoc: 22.0.3 :standalone: :api-management!: diff --git a/docs/documentation/upgrading/pom.xml b/docs/documentation/upgrading/pom.xml index 9c081109a538..e5d7e611ab07 100644 --- a/docs/documentation/upgrading/pom.xml +++ b/docs/documentation/upgrading/pom.xml @@ -5,7 +5,7 @@ org.keycloak.documentation documentation-parent - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml diff --git a/docs/guides/pom.xml b/docs/guides/pom.xml index a8e154b62a07..41ddcc977302 100644 --- a/docs/guides/pom.xml +++ b/docs/guides/pom.xml @@ -19,7 +19,7 @@ keycloak-docs-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml diff --git a/docs/maven-plugin/pom.xml b/docs/maven-plugin/pom.xml index e6046affb4bb..2ca652a0934d 100644 --- a/docs/maven-plugin/pom.xml +++ b/docs/maven-plugin/pom.xml @@ -20,7 +20,7 @@ keycloak-docs-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml diff --git a/docs/pom.xml b/docs/pom.xml index 35f5dfa64ff6..a14d3589e836 100755 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -19,7 +19,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml Keycloak Docs Parent diff --git a/examples/admin-client/pom.xml b/examples/admin-client/pom.xml index 713d1db1d74c..2256460e22f9 100755 --- a/examples/admin-client/pom.xml +++ b/examples/admin-client/pom.xml @@ -22,7 +22,7 @@ keycloak-examples-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 Keycloak Examples - Admin Client diff --git a/examples/js-console/pom.xml b/examples/js-console/pom.xml index e26e650e56f2..4c27604d0ee3 100755 --- a/examples/js-console/pom.xml +++ b/examples/js-console/pom.xml @@ -21,7 +21,7 @@ keycloak-examples-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/examples/kerberos/pom.xml b/examples/kerberos/pom.xml index 985373bde0d7..4dc282d62f12 100755 --- a/examples/kerberos/pom.xml +++ b/examples/kerberos/pom.xml @@ -22,7 +22,7 @@ keycloak-examples-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 Keycloak Examples - Kerberos Credential Delegation diff --git a/examples/ldap/pom.xml b/examples/ldap/pom.xml index 9e2bd4c20513..7eaaa466e487 100644 --- a/examples/ldap/pom.xml +++ b/examples/ldap/pom.xml @@ -22,7 +22,7 @@ keycloak-examples-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/examples/pom.xml b/examples/pom.xml index b2a2186f7b9a..66bac0afa4a6 100755 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 Keycloak Examples diff --git a/examples/providers/authenticator/pom.xml b/examples/providers/authenticator/pom.xml index dc87b6705f50..7514dabff5e8 100755 --- a/examples/providers/authenticator/pom.xml +++ b/examples/providers/authenticator/pom.xml @@ -20,7 +20,7 @@ keycloak-examples-providers-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 Authenticator Example diff --git a/examples/providers/domain-extension/pom.xml b/examples/providers/domain-extension/pom.xml index dd513889948a..83a58d35d46a 100755 --- a/examples/providers/domain-extension/pom.xml +++ b/examples/providers/domain-extension/pom.xml @@ -20,7 +20,7 @@ keycloak-examples-providers-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 Domain Extension Example diff --git a/examples/providers/pom.xml b/examples/providers/pom.xml index d5c1191f25d2..9fbfe96324c3 100755 --- a/examples/providers/pom.xml +++ b/examples/providers/pom.xml @@ -20,7 +20,7 @@ keycloak-examples-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 Provider Examples diff --git a/examples/providers/rest/pom.xml b/examples/providers/rest/pom.xml index dead2d35943f..03984118e8a7 100755 --- a/examples/providers/rest/pom.xml +++ b/examples/providers/rest/pom.xml @@ -20,7 +20,7 @@ keycloak-examples-providers-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 REST Example diff --git a/examples/saml/pom.xml b/examples/saml/pom.xml index c65cff5ac2d5..a367910a7617 100755 --- a/examples/saml/pom.xml +++ b/examples/saml/pom.xml @@ -20,7 +20,7 @@ keycloak-examples-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 SAML Examples diff --git a/examples/saml/servlet-filter/pom.xml b/examples/saml/servlet-filter/pom.xml index 3b48824ae0a6..a850735a80c1 100755 --- a/examples/saml/servlet-filter/pom.xml +++ b/examples/saml/servlet-filter/pom.xml @@ -22,7 +22,7 @@ keycloak-examples-saml-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 saml-servlet-filter diff --git a/examples/themes/pom.xml b/examples/themes/pom.xml index d4190e3ac7a9..2e101a6ea8b9 100755 --- a/examples/themes/pom.xml +++ b/examples/themes/pom.xml @@ -20,7 +20,7 @@ keycloak-examples-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 Themes Examples diff --git a/federation/kerberos/pom.xml b/federation/kerberos/pom.xml index fa11c18ca6aa..45ec6eacd119 100755 --- a/federation/kerberos/pom.xml +++ b/federation/kerberos/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../pom.xml 4.0.0 diff --git a/federation/ldap/pom.xml b/federation/ldap/pom.xml index df153ca61b5e..12f46dd85d5a 100755 --- a/federation/ldap/pom.xml +++ b/federation/ldap/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../pom.xml 4.0.0 diff --git a/federation/pom.xml b/federation/pom.xml index 314e274a73b3..3c9827c55f70 100755 --- a/federation/pom.xml +++ b/federation/pom.xml @@ -22,7 +22,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml 4.0.0 diff --git a/federation/sssd/pom.xml b/federation/sssd/pom.xml index 0999294dc505..25ae44fcb06e 100644 --- a/federation/sssd/pom.xml +++ b/federation/sssd/pom.xml @@ -4,7 +4,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../pom.xml 4.0.0 diff --git a/integration/admin-client-jee/pom.xml b/integration/admin-client-jee/pom.xml index 510faebcafb2..1eae4fa31be7 100755 --- a/integration/admin-client-jee/pom.xml +++ b/integration/admin-client-jee/pom.xml @@ -22,7 +22,7 @@ keycloak-integration-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/integration/admin-client/pom.xml b/integration/admin-client/pom.xml index b1b9a79cfa2c..3b642b77eeb8 100755 --- a/integration/admin-client/pom.xml +++ b/integration/admin-client/pom.xml @@ -22,7 +22,7 @@ keycloak-integration-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/integration/client-cli/admin-cli/pom.xml b/integration/client-cli/admin-cli/pom.xml index c0d8ef6358f2..c9b8d6ec725e 100755 --- a/integration/client-cli/admin-cli/pom.xml +++ b/integration/client-cli/admin-cli/pom.xml @@ -21,7 +21,7 @@ keycloak-client-cli-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/integration/client-cli/client-cli-dist/pom.xml b/integration/client-cli/client-cli-dist/pom.xml index 5712241f7dd8..1be36b40ce33 100755 --- a/integration/client-cli/client-cli-dist/pom.xml +++ b/integration/client-cli/client-cli-dist/pom.xml @@ -21,7 +21,7 @@ keycloak-client-cli-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 keycloak-client-cli-dist diff --git a/integration/client-cli/client-registration-cli/pom.xml b/integration/client-cli/client-registration-cli/pom.xml index 2072efa6c751..f51b761e2f84 100755 --- a/integration/client-cli/client-registration-cli/pom.xml +++ b/integration/client-cli/client-registration-cli/pom.xml @@ -21,7 +21,7 @@ keycloak-client-cli-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/integration/client-cli/pom.xml b/integration/client-cli/pom.xml index 7ebc9baa4c07..8a8b11f32ded 100644 --- a/integration/client-cli/pom.xml +++ b/integration/client-cli/pom.xml @@ -20,7 +20,7 @@ keycloak-integration-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 Keycloak Client CLI diff --git a/integration/client-registration/pom.xml b/integration/client-registration/pom.xml index 9546266379ba..3e09ba04e843 100755 --- a/integration/client-registration/pom.xml +++ b/integration/client-registration/pom.xml @@ -21,7 +21,7 @@ keycloak-integration-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/integration/pom.xml b/integration/pom.xml index dc6dde5da414..fed585b11839 100755 --- a/integration/pom.xml +++ b/integration/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml Keycloak Integration diff --git a/js/apps/account-ui/pom.xml b/js/apps/account-ui/pom.xml index 0adf4430dedf..c844125a0288 100644 --- a/js/apps/account-ui/pom.xml +++ b/js/apps/account-ui/pom.xml @@ -7,7 +7,7 @@ keycloak-js-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../pom.xml diff --git a/js/apps/admin-ui/pom.xml b/js/apps/admin-ui/pom.xml index 91c7aa5fe433..68849af8457d 100644 --- a/js/apps/admin-ui/pom.xml +++ b/js/apps/admin-ui/pom.xml @@ -7,7 +7,7 @@ keycloak-js-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../pom.xml diff --git a/js/libs/keycloak-admin-client/package.json b/js/libs/keycloak-admin-client/package.json index e55410df6c48..07ad765f9fa9 100644 --- a/js/libs/keycloak-admin-client/package.json +++ b/js/libs/keycloak-admin-client/package.json @@ -1,6 +1,6 @@ { "name": "@keycloak/keycloak-admin-client", - "version": "999.0.0-SNAPSHOT", + "version": "22.0.3", "description": "A client to interact with Keycloak's Administration API", "type": "module", "main": "lib/index.js", diff --git a/js/libs/keycloak-admin-client/pom.xml b/js/libs/keycloak-admin-client/pom.xml index d76646a12960..26fcde9f7d25 100644 --- a/js/libs/keycloak-admin-client/pom.xml +++ b/js/libs/keycloak-admin-client/pom.xml @@ -5,7 +5,7 @@ keycloak-js-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../pom.xml diff --git a/js/libs/keycloak-js/package.json b/js/libs/keycloak-js/package.json index 5fdd0e628068..719404559669 100644 --- a/js/libs/keycloak-js/package.json +++ b/js/libs/keycloak-js/package.json @@ -1,6 +1,6 @@ { "name": "keycloak-js", - "version": "999.0.0-SNAPSHOT", + "version": "22.0.3", "description": "A client-side JavaScript OpenID Connect library that can be used to secure web applications", "main": "./dist/keycloak.js", "module": "./dist/keycloak.mjs", diff --git a/js/libs/keycloak-js/pom.xml b/js/libs/keycloak-js/pom.xml index 236348cba2f0..d877a02761cf 100644 --- a/js/libs/keycloak-js/pom.xml +++ b/js/libs/keycloak-js/pom.xml @@ -5,7 +5,7 @@ keycloak-js-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../pom.xml diff --git a/js/pom.xml b/js/pom.xml index fee3981bb0a8..a08de82bcb64 100644 --- a/js/pom.xml +++ b/js/pom.xml @@ -5,7 +5,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml diff --git a/misc/keycloak-test-helper/pom.xml b/misc/keycloak-test-helper/pom.xml index aaed6d3a2de6..d714724925d4 100644 --- a/misc/keycloak-test-helper/pom.xml +++ b/misc/keycloak-test-helper/pom.xml @@ -6,7 +6,7 @@ keycloak-misc-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 org.keycloak keycloak-test-helper diff --git a/misc/pom.xml b/misc/pom.xml index 8c813fdc5074..eba806017f6a 100644 --- a/misc/pom.xml +++ b/misc/pom.xml @@ -3,7 +3,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 Keycloak Misc diff --git a/misc/spring-boot-starter/keycloak-spring-boot-starter/pom.xml b/misc/spring-boot-starter/keycloak-spring-boot-starter/pom.xml index d3a97a4168c5..2485f6c20040 100644 --- a/misc/spring-boot-starter/keycloak-spring-boot-starter/pom.xml +++ b/misc/spring-boot-starter/keycloak-spring-boot-starter/pom.xml @@ -4,7 +4,7 @@ org.keycloak keycloak-spring-boot-starter-parent - 999.0.0-SNAPSHOT + 22.0.3 keycloak-spring-boot-starter Keycloak :: Spring :: Boot :: Default :: Starter diff --git a/misc/spring-boot-starter/pom.xml b/misc/spring-boot-starter/pom.xml index b94cafa4a6d0..181658677555 100644 --- a/misc/spring-boot-starter/pom.xml +++ b/misc/spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ keycloak-misc-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 org.keycloak keycloak-spring-boot-starter-parent diff --git a/model/build-processor/pom.xml b/model/build-processor/pom.xml index ef57bdbcc58c..0a75d083a92c 100644 --- a/model/build-processor/pom.xml +++ b/model/build-processor/pom.xml @@ -3,7 +3,7 @@ keycloak-model-pom org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/model/infinispan/pom.xml b/model/infinispan/pom.xml index 3ca26d1e3c06..39b841c2ffd3 100755 --- a/model/infinispan/pom.xml +++ b/model/infinispan/pom.xml @@ -21,7 +21,7 @@ keycloak-model-pom org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/model/jpa/pom.xml b/model/jpa/pom.xml index bcb41c90dc57..68b6f490ccef 100755 --- a/model/jpa/pom.xml +++ b/model/jpa/pom.xml @@ -21,7 +21,7 @@ keycloak-model-pom org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/model/legacy-private/pom.xml b/model/legacy-private/pom.xml index e30049067ae7..38e9a81dd36c 100644 --- a/model/legacy-private/pom.xml +++ b/model/legacy-private/pom.xml @@ -3,7 +3,7 @@ keycloak-model-pom org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/model/legacy-services/pom.xml b/model/legacy-services/pom.xml index b6332e1f13d3..eb0bda42aa63 100644 --- a/model/legacy-services/pom.xml +++ b/model/legacy-services/pom.xml @@ -3,7 +3,7 @@ keycloak-model-pom org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/model/legacy/pom.xml b/model/legacy/pom.xml index 4f1c02fc16ac..43f40322b65f 100644 --- a/model/legacy/pom.xml +++ b/model/legacy/pom.xml @@ -3,7 +3,7 @@ keycloak-model-pom org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/model/map-file/pom.xml b/model/map-file/pom.xml index ea6f5b927852..e04244a73116 100644 --- a/model/map-file/pom.xml +++ b/model/map-file/pom.xml @@ -22,7 +22,7 @@ keycloak-model-pom org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/model/map-hot-rod/pom.xml b/model/map-hot-rod/pom.xml index 6ec871b29843..f928976d332d 100644 --- a/model/map-hot-rod/pom.xml +++ b/model/map-hot-rod/pom.xml @@ -5,7 +5,7 @@ keycloak-model-pom org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/model/map-jpa/pom.xml b/model/map-jpa/pom.xml index e47356b281d7..d06ea221f53a 100644 --- a/model/map-jpa/pom.xml +++ b/model/map-jpa/pom.xml @@ -21,7 +21,7 @@ keycloak-model-pom org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/model/map-ldap/pom.xml b/model/map-ldap/pom.xml index a62c95e83dcd..7878eed21bd7 100644 --- a/model/map-ldap/pom.xml +++ b/model/map-ldap/pom.xml @@ -21,7 +21,7 @@ keycloak-model-pom org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/model/map/pom.xml b/model/map/pom.xml index def50007fe46..b9292c8c129e 100644 --- a/model/map/pom.xml +++ b/model/map/pom.xml @@ -3,7 +3,7 @@ keycloak-model-pom org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/model/pom.xml b/model/pom.xml index c110ae4e5372..364ad313b110 100755 --- a/model/pom.xml +++ b/model/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml Keycloak Model Parent diff --git a/operator/pom.xml b/operator/pom.xml index 57f632bfc8e7..7704d8b14f2e 100644 --- a/operator/pom.xml +++ b/operator/pom.xml @@ -7,7 +7,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml diff --git a/pom.xml b/pom.xml index 40789bb511bd..65cfe22bcc3b 100644 --- a/pom.xml +++ b/pom.xml @@ -31,7 +31,7 @@ org.keycloak keycloak-parent - 999.0.0-SNAPSHOT + 22.0.3 pom diff --git a/quarkus/config-api/pom.xml b/quarkus/config-api/pom.xml index f4eb8eba3619..4dd1c7607bbc 100755 --- a/quarkus/config-api/pom.xml +++ b/quarkus/config-api/pom.xml @@ -21,7 +21,7 @@ keycloak-quarkus-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml 4.0.0 diff --git a/quarkus/container/Dockerfile b/quarkus/container/Dockerfile index c40db77e2d9d..58e53b2ea0ba 100644 --- a/quarkus/container/Dockerfile +++ b/quarkus/container/Dockerfile @@ -1,6 +1,6 @@ FROM registry.access.redhat.com/ubi9 AS ubi-micro-build -ENV KEYCLOAK_VERSION 999.0.0-SNAPSHOT +ENV KEYCLOAK_VERSION 22.0.3 ARG KEYCLOAK_DIST=https://github.com/keycloak/keycloak/releases/download/$KEYCLOAK_VERSION/keycloak-$KEYCLOAK_VERSION.tar.gz RUN dnf install -y tar gzip diff --git a/quarkus/deployment/pom.xml b/quarkus/deployment/pom.xml index 4ba1b0a01c91..b9dce5e18001 100644 --- a/quarkus/deployment/pom.xml +++ b/quarkus/deployment/pom.xml @@ -5,7 +5,7 @@ keycloak-quarkus-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml 4.0.0 diff --git a/quarkus/dist/pom.xml b/quarkus/dist/pom.xml index fba99bf1ab9c..c1216804ae97 100755 --- a/quarkus/dist/pom.xml +++ b/quarkus/dist/pom.xml @@ -21,7 +21,7 @@ keycloak-quarkus-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 keycloak-quarkus-dist diff --git a/quarkus/pom.xml b/quarkus/pom.xml index a3fbb926d2ac..1bf76bf4baf2 100644 --- a/quarkus/pom.xml +++ b/quarkus/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml Keycloak Quarkus Parent diff --git a/quarkus/runtime/pom.xml b/quarkus/runtime/pom.xml index ad1a9e9e8574..40f45141a3bc 100644 --- a/quarkus/runtime/pom.xml +++ b/quarkus/runtime/pom.xml @@ -5,7 +5,7 @@ keycloak-quarkus-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml 4.0.0 diff --git a/quarkus/server/pom.xml b/quarkus/server/pom.xml index 53c1253993f9..f19fc9752989 100644 --- a/quarkus/server/pom.xml +++ b/quarkus/server/pom.xml @@ -7,7 +7,7 @@ keycloak-quarkus-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml diff --git a/quarkus/tests/integration/pom.xml b/quarkus/tests/integration/pom.xml index 52f5a8484a92..9936def0d704 100644 --- a/quarkus/tests/integration/pom.xml +++ b/quarkus/tests/integration/pom.xml @@ -24,7 +24,7 @@ keycloak-quarkus-test-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml diff --git a/quarkus/tests/junit5/pom.xml b/quarkus/tests/junit5/pom.xml index 94b7d0e77557..127d409dc8b3 100644 --- a/quarkus/tests/junit5/pom.xml +++ b/quarkus/tests/junit5/pom.xml @@ -24,7 +24,7 @@ keycloak-quarkus-test-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml diff --git a/quarkus/tests/pom.xml b/quarkus/tests/pom.xml index 434605e1b9f2..584c765e8b07 100644 --- a/quarkus/tests/pom.xml +++ b/quarkus/tests/pom.xml @@ -24,7 +24,7 @@ keycloak-quarkus-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml diff --git a/rest/admin-ui-ext/pom.xml b/rest/admin-ui-ext/pom.xml index b4f0a7e714bf..058c4bcc9ac4 100644 --- a/rest/admin-ui-ext/pom.xml +++ b/rest/admin-ui-ext/pom.xml @@ -22,7 +22,7 @@ org.keycloak keycloak-rest-parent - 999.0.0-SNAPSHOT + 22.0.3 keycloak-rest-admin-ui-ext diff --git a/rest/pom.xml b/rest/pom.xml index c4d975748c08..1fd837cd40db 100644 --- a/rest/pom.xml +++ b/rest/pom.xml @@ -22,7 +22,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 Keycloak Administration UI diff --git a/saml-core-api/pom.xml b/saml-core-api/pom.xml index 392c6b495ff3..93e6ec8a3f19 100755 --- a/saml-core-api/pom.xml +++ b/saml-core-api/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml 4.0.0 diff --git a/saml-core/pom.xml b/saml-core/pom.xml index 6d1097dc5595..47652090aa3f 100755 --- a/saml-core/pom.xml +++ b/saml-core/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml 4.0.0 diff --git a/server-spi-private/pom.xml b/server-spi-private/pom.xml index 7275d1c3ee54..3802b6103b3d 100755 --- a/server-spi-private/pom.xml +++ b/server-spi-private/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml 4.0.0 diff --git a/server-spi/pom.xml b/server-spi/pom.xml index 1da428a4c892..f567552f8442 100755 --- a/server-spi/pom.xml +++ b/server-spi/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml 4.0.0 diff --git a/services/pom.xml b/services/pom.xml index 49c152048d68..26d067b4648e 100755 --- a/services/pom.xml +++ b/services/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml 4.0.0 diff --git a/testsuite/db-allocator-plugin/pom.xml b/testsuite/db-allocator-plugin/pom.xml index c82daf8462b7..5a3939105794 100644 --- a/testsuite/db-allocator-plugin/pom.xml +++ b/testsuite/db-allocator-plugin/pom.xml @@ -22,7 +22,7 @@ keycloak-testsuite-pom org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/pom.xml b/testsuite/integration-arquillian/pom.xml index e1b1ec6f793a..b893f1b76536 100644 --- a/testsuite/integration-arquillian/pom.xml +++ b/testsuite/integration-arquillian/pom.xml @@ -22,7 +22,7 @@ org.keycloak keycloak-testsuite-pom - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml diff --git a/testsuite/integration-arquillian/servers/adapter-spi/pom.xml b/testsuite/integration-arquillian/servers/adapter-spi/pom.xml index 7f641d873404..9d142c54b869 100644 --- a/testsuite/integration-arquillian/servers/adapter-spi/pom.xml +++ b/testsuite/integration-arquillian/servers/adapter-spi/pom.xml @@ -5,7 +5,7 @@ integration-arquillian-servers org.keycloak.testsuite - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 pom diff --git a/testsuite/integration-arquillian/servers/adapter-spi/undertow-adapter-jakarta/pom.xml b/testsuite/integration-arquillian/servers/adapter-spi/undertow-adapter-jakarta/pom.xml index 7539a2caf5a0..3cffa0edbd73 100644 --- a/testsuite/integration-arquillian/servers/adapter-spi/undertow-adapter-jakarta/pom.xml +++ b/testsuite/integration-arquillian/servers/adapter-spi/undertow-adapter-jakarta/pom.xml @@ -5,7 +5,7 @@ integration-arquillian-servers-adapter-spi org.keycloak.testsuite - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml 4.0.0 diff --git a/testsuite/integration-arquillian/servers/adapter-spi/undertow-adapter-saml-jakarta/pom.xml b/testsuite/integration-arquillian/servers/adapter-spi/undertow-adapter-saml-jakarta/pom.xml index 27455823417e..72b69c65e0a2 100644 --- a/testsuite/integration-arquillian/servers/adapter-spi/undertow-adapter-saml-jakarta/pom.xml +++ b/testsuite/integration-arquillian/servers/adapter-spi/undertow-adapter-saml-jakarta/pom.xml @@ -5,7 +5,7 @@ integration-arquillian-servers-adapter-spi org.keycloak.testsuite - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml 4.0.0 diff --git a/testsuite/integration-arquillian/servers/adapter-spi/undertow-adapter-spi-jakarta/pom.xml b/testsuite/integration-arquillian/servers/adapter-spi/undertow-adapter-spi-jakarta/pom.xml index 3fe965d90478..1450a5a75b8e 100644 --- a/testsuite/integration-arquillian/servers/adapter-spi/undertow-adapter-spi-jakarta/pom.xml +++ b/testsuite/integration-arquillian/servers/adapter-spi/undertow-adapter-spi-jakarta/pom.xml @@ -5,7 +5,7 @@ integration-arquillian-servers-adapter-spi org.keycloak.testsuite - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/app-server-spi/pom.xml b/testsuite/integration-arquillian/servers/app-server/app-server-spi/pom.xml index a00eae01d9ec..7e40b46e9626 100644 --- a/testsuite/integration-arquillian/servers/app-server/app-server-spi/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/app-server-spi/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/jboss/eap/pom.xml b/testsuite/integration-arquillian/servers/app-server/jboss/eap/pom.xml index 34afca828528..95005d05471e 100644 --- a/testsuite/integration-arquillian/servers/app-server/jboss/eap/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/jboss/eap/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server-jboss - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/jboss/eap6/pom.xml b/testsuite/integration-arquillian/servers/app-server/jboss/eap6/pom.xml index e5bbcdd78b13..9908fabc338f 100644 --- a/testsuite/integration-arquillian/servers/app-server/jboss/eap6/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/jboss/eap6/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server-jboss - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/jboss/pom.xml b/testsuite/integration-arquillian/servers/app-server/jboss/pom.xml index ebd028c0c2d1..ae97f702b53e 100644 --- a/testsuite/integration-arquillian/servers/app-server/jboss/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/jboss/pom.xml @@ -22,7 +22,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/jboss/wildfly/pom.xml b/testsuite/integration-arquillian/servers/app-server/jboss/wildfly/pom.xml index 4932980a169d..6c26fdca12b2 100644 --- a/testsuite/integration-arquillian/servers/app-server/jboss/wildfly/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/jboss/wildfly/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server-jboss - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/jetty/94/pom.xml b/testsuite/integration-arquillian/servers/app-server/jetty/94/pom.xml index 14fc2f005fa7..567cfeeb8b38 100644 --- a/testsuite/integration-arquillian/servers/app-server/jetty/94/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/jetty/94/pom.xml @@ -18,7 +18,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server-jetty - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/jetty/common/pom.xml b/testsuite/integration-arquillian/servers/app-server/jetty/common/pom.xml index ffb12cebace8..5e9dd28163f6 100644 --- a/testsuite/integration-arquillian/servers/app-server/jetty/common/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/jetty/common/pom.xml @@ -18,7 +18,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server-jetty - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/jetty/pom.xml b/testsuite/integration-arquillian/servers/app-server/jetty/pom.xml index 1c938b754ea7..c217069a597f 100644 --- a/testsuite/integration-arquillian/servers/app-server/jetty/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/jetty/pom.xml @@ -18,7 +18,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/karaf/fuse63/pom.xml b/testsuite/integration-arquillian/servers/app-server/karaf/fuse63/pom.xml index b57b22e505b3..31551fa87d9e 100644 --- a/testsuite/integration-arquillian/servers/app-server/karaf/fuse63/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/karaf/fuse63/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server-karaf - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/karaf/fuse7x/pom.xml b/testsuite/integration-arquillian/servers/app-server/karaf/fuse7x/pom.xml index 7b2e45786b9e..cf5f6803dcd8 100644 --- a/testsuite/integration-arquillian/servers/app-server/karaf/fuse7x/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/karaf/fuse7x/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server-karaf - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/karaf/pom.xml b/testsuite/integration-arquillian/servers/app-server/karaf/pom.xml index fc156da61e25..4e57940c2d63 100644 --- a/testsuite/integration-arquillian/servers/app-server/karaf/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/karaf/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/pom.xml b/testsuite/integration-arquillian/servers/app-server/pom.xml index 36f1eb30af79..5bb15ab19bce 100644 --- a/testsuite/integration-arquillian/servers/app-server/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/tomcat/common/pom.xml b/testsuite/integration-arquillian/servers/app-server/tomcat/common/pom.xml index a0019ea56da5..1440830d1c82 100644 --- a/testsuite/integration-arquillian/servers/app-server/tomcat/common/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/tomcat/common/pom.xml @@ -5,7 +5,7 @@ integration-arquillian-servers-app-server-tomcat org.keycloak.testsuite - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/tomcat/pom.xml b/testsuite/integration-arquillian/servers/app-server/tomcat/pom.xml index ceb6f924d843..515b85659ec0 100644 --- a/testsuite/integration-arquillian/servers/app-server/tomcat/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/tomcat/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/tomcat/tomcat8/pom.xml b/testsuite/integration-arquillian/servers/app-server/tomcat/tomcat8/pom.xml index ebf2b1cf6bef..c3d5d6eed57d 100644 --- a/testsuite/integration-arquillian/servers/app-server/tomcat/tomcat8/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/tomcat/tomcat8/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server-tomcat - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/tomcat/tomcat9/pom.xml b/testsuite/integration-arquillian/servers/app-server/tomcat/tomcat9/pom.xml index cbb1c5b0bccf..ae4323ea1e5a 100644 --- a/testsuite/integration-arquillian/servers/app-server/tomcat/tomcat9/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/tomcat/tomcat9/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server-tomcat - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/app-server/undertow/pom.xml b/testsuite/integration-arquillian/servers/app-server/undertow/pom.xml index 1f708815e3f4..39131433d512 100644 --- a/testsuite/integration-arquillian/servers/app-server/undertow/pom.xml +++ b/testsuite/integration-arquillian/servers/app-server/undertow/pom.xml @@ -18,7 +18,7 @@ org.keycloak.testsuite integration-arquillian-servers-app-server - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/auth-server/pom.xml b/testsuite/integration-arquillian/servers/auth-server/pom.xml index a5ed91a6c734..af3269307427 100644 --- a/testsuite/integration-arquillian/servers/auth-server/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/auth-server/quarkus/pom.xml b/testsuite/integration-arquillian/servers/auth-server/quarkus/pom.xml index 138d19435b18..14ac8e9e572b 100644 --- a/testsuite/integration-arquillian/servers/auth-server/quarkus/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/quarkus/pom.xml @@ -5,7 +5,7 @@ integration-arquillian-servers-auth-server org.keycloak.testsuite - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/auth-server/services/pom.xml b/testsuite/integration-arquillian/servers/auth-server/services/pom.xml index a096973d211c..b42ddfa70fe9 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/services/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-auth-server - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers-deployment/pom.xml b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers-deployment/pom.xml index 20063ee9442d..fb76fc7fd453 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers-deployment/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers-deployment/pom.xml @@ -24,7 +24,7 @@ org.keycloak.testsuite integration-arquillian-servers-auth-server-services - 999.0.0-SNAPSHOT + 22.0.3 integration-arquillian-testsuite-providers-deployment 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 7f5cba4c107e..4036d1f26553 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/pom.xml @@ -24,7 +24,7 @@ org.keycloak.testsuite integration-arquillian-servers-auth-server-services - 999.0.0-SNAPSHOT + 22.0.3 integration-arquillian-testsuite-providers diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/pom.xml b/testsuite/integration-arquillian/servers/auth-server/undertow/pom.xml index 303f3c7002e7..4069f44da4c0 100644 --- a/testsuite/integration-arquillian/servers/auth-server/undertow/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/undertow/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-auth-server - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/cache-server/infinispan/datagrid/pom.xml b/testsuite/integration-arquillian/servers/cache-server/infinispan/datagrid/pom.xml index 39ee8e345f63..98f40c1b2d25 100644 --- a/testsuite/integration-arquillian/servers/cache-server/infinispan/datagrid/pom.xml +++ b/testsuite/integration-arquillian/servers/cache-server/infinispan/datagrid/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-cache-server-infinispan - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/cache-server/infinispan/infinispan/pom.xml b/testsuite/integration-arquillian/servers/cache-server/infinispan/infinispan/pom.xml index 493512a20957..c108b1eebdce 100644 --- a/testsuite/integration-arquillian/servers/cache-server/infinispan/infinispan/pom.xml +++ b/testsuite/integration-arquillian/servers/cache-server/infinispan/infinispan/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-cache-server-infinispan - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/cache-server/infinispan/pom.xml b/testsuite/integration-arquillian/servers/cache-server/infinispan/pom.xml index 80b68a9d8278..a48d68ad96b0 100644 --- a/testsuite/integration-arquillian/servers/cache-server/infinispan/pom.xml +++ b/testsuite/integration-arquillian/servers/cache-server/infinispan/pom.xml @@ -24,7 +24,7 @@ org.keycloak.testsuite integration-arquillian-servers-cache-server - 999.0.0-SNAPSHOT + 22.0.3 pom diff --git a/testsuite/integration-arquillian/servers/cache-server/legacy/datagrid/pom.xml b/testsuite/integration-arquillian/servers/cache-server/legacy/datagrid/pom.xml index 56494399a8e5..9e61a96e6c3d 100644 --- a/testsuite/integration-arquillian/servers/cache-server/legacy/datagrid/pom.xml +++ b/testsuite/integration-arquillian/servers/cache-server/legacy/datagrid/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-cache-server-legacy - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/cache-server/legacy/infinispan/pom.xml b/testsuite/integration-arquillian/servers/cache-server/legacy/infinispan/pom.xml index cb0f8056bfb1..60f1a9917320 100644 --- a/testsuite/integration-arquillian/servers/cache-server/legacy/infinispan/pom.xml +++ b/testsuite/integration-arquillian/servers/cache-server/legacy/infinispan/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-cache-server-legacy - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/cache-server/legacy/pom.xml b/testsuite/integration-arquillian/servers/cache-server/legacy/pom.xml index d5f7c46fe695..699a0e42d55a 100644 --- a/testsuite/integration-arquillian/servers/cache-server/legacy/pom.xml +++ b/testsuite/integration-arquillian/servers/cache-server/legacy/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers-cache-server - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/cache-server/pom.xml b/testsuite/integration-arquillian/servers/cache-server/pom.xml index 22779a9af5e2..8f8576d2b1c1 100644 --- a/testsuite/integration-arquillian/servers/cache-server/pom.xml +++ b/testsuite/integration-arquillian/servers/cache-server/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/migration/pom.xml b/testsuite/integration-arquillian/servers/migration/pom.xml index 94cf6e625c65..00f37c46416f 100644 --- a/testsuite/integration-arquillian/servers/migration/pom.xml +++ b/testsuite/integration-arquillian/servers/migration/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-servers - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/servers/pom.xml b/testsuite/integration-arquillian/servers/pom.xml index 5054d727381f..568392613c6d 100644 --- a/testsuite/integration-arquillian/servers/pom.xml +++ b/testsuite/integration-arquillian/servers/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/test-apps/app-profile-jee/pom.xml b/testsuite/integration-arquillian/test-apps/app-profile-jee/pom.xml index d25c47c91ca4..ccf7b1a6490c 100644 --- a/testsuite/integration-arquillian/test-apps/app-profile-jee/pom.xml +++ b/testsuite/integration-arquillian/test-apps/app-profile-jee/pom.xml @@ -5,7 +5,7 @@ org.keycloak.testsuite integration-arquillian-test-apps - 999.0.0-SNAPSHOT + 22.0.3 keycloak-test-app-profile-jee diff --git a/testsuite/integration-arquillian/test-apps/cors/angular-product/pom.xml b/testsuite/integration-arquillian/test-apps/cors/angular-product/pom.xml index ef95d30e4f3e..0e17a5e18b59 100755 --- a/testsuite/integration-arquillian/test-apps/cors/angular-product/pom.xml +++ b/testsuite/integration-arquillian/test-apps/cors/angular-product/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-test-apps-cors-parent - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/test-apps/cors/database-service/pom.xml b/testsuite/integration-arquillian/test-apps/cors/database-service/pom.xml index 73d893fc425e..24a8ffcabdf2 100755 --- a/testsuite/integration-arquillian/test-apps/cors/database-service/pom.xml +++ b/testsuite/integration-arquillian/test-apps/cors/database-service/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-test-apps-cors-parent - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/test-apps/cors/pom.xml b/testsuite/integration-arquillian/test-apps/cors/pom.xml index ce8f9da676d2..5677e3d23352 100644 --- a/testsuite/integration-arquillian/test-apps/cors/pom.xml +++ b/testsuite/integration-arquillian/test-apps/cors/pom.xml @@ -5,7 +5,7 @@ integration-arquillian-test-apps org.keycloak.testsuite - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/test-apps/fuse/camel-fuse7-undertow/pom.xml b/testsuite/integration-arquillian/test-apps/fuse/camel-fuse7-undertow/pom.xml index 201c50a85380..50ce40e803d3 100755 --- a/testsuite/integration-arquillian/test-apps/fuse/camel-fuse7-undertow/pom.xml +++ b/testsuite/integration-arquillian/test-apps/fuse/camel-fuse7-undertow/pom.xml @@ -21,7 +21,7 @@ integration-arquillian-test-apps-fuse-parent org.keycloak.testsuite - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/test-apps/fuse/camel/pom.xml b/testsuite/integration-arquillian/test-apps/fuse/camel/pom.xml index c55d5b01db2b..e814f97480c9 100755 --- a/testsuite/integration-arquillian/test-apps/fuse/camel/pom.xml +++ b/testsuite/integration-arquillian/test-apps/fuse/camel/pom.xml @@ -21,7 +21,7 @@ integration-arquillian-test-apps-fuse-parent org.keycloak.testsuite - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/test-apps/fuse/customer-app-fuse/pom.xml b/testsuite/integration-arquillian/test-apps/fuse/customer-app-fuse/pom.xml index a7d0ad92fcb7..70917669b331 100755 --- a/testsuite/integration-arquillian/test-apps/fuse/customer-app-fuse/pom.xml +++ b/testsuite/integration-arquillian/test-apps/fuse/customer-app-fuse/pom.xml @@ -21,7 +21,7 @@ integration-arquillian-test-apps-fuse-parent org.keycloak.testsuite - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxrs-fuse7-undertow/pom.xml b/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxrs-fuse7-undertow/pom.xml index 6ae6300c2e72..faef86b06772 100755 --- a/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxrs-fuse7-undertow/pom.xml +++ b/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxrs-fuse7-undertow/pom.xml @@ -21,7 +21,7 @@ integration-arquillian-test-apps-fuse-parent org.keycloak.testsuite - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxrs/pom.xml b/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxrs/pom.xml index 2ab0bcf48be1..23c70f4a9973 100755 --- a/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxrs/pom.xml +++ b/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxrs/pom.xml @@ -21,7 +21,7 @@ integration-arquillian-test-apps-fuse-parent org.keycloak.testsuite - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxws-fuse7-undertow/pom.xml b/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxws-fuse7-undertow/pom.xml index 714b0d3f383b..20c829d41e53 100755 --- a/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxws-fuse7-undertow/pom.xml +++ b/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxws-fuse7-undertow/pom.xml @@ -21,7 +21,7 @@ integration-arquillian-test-apps-fuse-parent org.keycloak.testsuite - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxws/pom.xml b/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxws/pom.xml index 67547eb4295e..12a7b533141e 100755 --- a/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxws/pom.xml +++ b/testsuite/integration-arquillian/test-apps/fuse/cxf-jaxws/pom.xml @@ -21,7 +21,7 @@ integration-arquillian-test-apps-fuse-parent org.keycloak.testsuite - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/test-apps/fuse/external-config/pom.xml b/testsuite/integration-arquillian/test-apps/fuse/external-config/pom.xml index eb970fb64ea9..006d6c287635 100755 --- a/testsuite/integration-arquillian/test-apps/fuse/external-config/pom.xml +++ b/testsuite/integration-arquillian/test-apps/fuse/external-config/pom.xml @@ -21,7 +21,7 @@ integration-arquillian-test-apps-fuse-parent org.keycloak.testsuite - 999.0.0-SNAPSHOT + 22.0.3 Keycloak Examples - External Config diff --git a/testsuite/integration-arquillian/test-apps/fuse/features/pom.xml b/testsuite/integration-arquillian/test-apps/fuse/features/pom.xml index f5efd8bad550..6ded467dcf92 100755 --- a/testsuite/integration-arquillian/test-apps/fuse/features/pom.xml +++ b/testsuite/integration-arquillian/test-apps/fuse/features/pom.xml @@ -21,7 +21,7 @@ integration-arquillian-test-apps-fuse-parent org.keycloak.testsuite - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/test-apps/fuse/pom.xml b/testsuite/integration-arquillian/test-apps/fuse/pom.xml index 2989cc4b7980..d90a7d6630c3 100755 --- a/testsuite/integration-arquillian/test-apps/fuse/pom.xml +++ b/testsuite/integration-arquillian/test-apps/fuse/pom.xml @@ -20,7 +20,7 @@ integration-arquillian-test-apps org.keycloak.testsuite - 999.0.0-SNAPSHOT + 22.0.3 Fuse Test Applications diff --git a/testsuite/integration-arquillian/test-apps/fuse/product-app-fuse/pom.xml b/testsuite/integration-arquillian/test-apps/fuse/product-app-fuse/pom.xml index 8e85c189e528..279e7d87869e 100755 --- a/testsuite/integration-arquillian/test-apps/fuse/product-app-fuse/pom.xml +++ b/testsuite/integration-arquillian/test-apps/fuse/product-app-fuse/pom.xml @@ -21,7 +21,7 @@ integration-arquillian-test-apps-fuse-parent org.keycloak.testsuite - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/test-apps/fuse/product-app-fuse7-undertow/pom.xml b/testsuite/integration-arquillian/test-apps/fuse/product-app-fuse7-undertow/pom.xml index d35dfd83c2a5..150b07e4aa5d 100755 --- a/testsuite/integration-arquillian/test-apps/fuse/product-app-fuse7-undertow/pom.xml +++ b/testsuite/integration-arquillian/test-apps/fuse/product-app-fuse7-undertow/pom.xml @@ -21,7 +21,7 @@ integration-arquillian-test-apps-fuse-parent org.keycloak.testsuite - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/test-apps/hello-world-authz-service/pom.xml b/testsuite/integration-arquillian/test-apps/hello-world-authz-service/pom.xml index 6fe8f7897bb4..06557cc1ea6b 100755 --- a/testsuite/integration-arquillian/test-apps/hello-world-authz-service/pom.xml +++ b/testsuite/integration-arquillian/test-apps/hello-world-authz-service/pom.xml @@ -24,7 +24,7 @@ org.keycloak.testsuite integration-arquillian-test-apps - 999.0.0-SNAPSHOT + 22.0.3 hello-world-authz-service diff --git a/testsuite/integration-arquillian/test-apps/pom.xml b/testsuite/integration-arquillian/test-apps/pom.xml index 0f1a89b8033a..731490ba1a21 100644 --- a/testsuite/integration-arquillian/test-apps/pom.xml +++ b/testsuite/integration-arquillian/test-apps/pom.xml @@ -5,7 +5,7 @@ integration-arquillian org.keycloak.testsuite - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/test-apps/servlet-authz/pom.xml b/testsuite/integration-arquillian/test-apps/servlet-authz/pom.xml index a2b12820e213..e8504c9cc1f7 100755 --- a/testsuite/integration-arquillian/test-apps/servlet-authz/pom.xml +++ b/testsuite/integration-arquillian/test-apps/servlet-authz/pom.xml @@ -6,7 +6,7 @@ org.keycloak.testsuite integration-arquillian-test-apps - 999.0.0-SNAPSHOT + 22.0.3 servlet-authz-app diff --git a/testsuite/integration-arquillian/test-apps/servlet-policy-enforcer/pom.xml b/testsuite/integration-arquillian/test-apps/servlet-policy-enforcer/pom.xml index a5fb0fe51153..a549d263d134 100755 --- a/testsuite/integration-arquillian/test-apps/servlet-policy-enforcer/pom.xml +++ b/testsuite/integration-arquillian/test-apps/servlet-policy-enforcer/pom.xml @@ -24,7 +24,7 @@ org.keycloak.testsuite integration-arquillian-test-apps - 999.0.0-SNAPSHOT + 22.0.3 servlet-policy-enforcer diff --git a/testsuite/integration-arquillian/test-apps/servlets/pom.xml b/testsuite/integration-arquillian/test-apps/servlets/pom.xml index 0c028efd87ff..d4591a6eaab2 100644 --- a/testsuite/integration-arquillian/test-apps/servlets/pom.xml +++ b/testsuite/integration-arquillian/test-apps/servlets/pom.xml @@ -5,7 +5,7 @@ integration-arquillian-test-apps org.keycloak.testsuite - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/test-apps/spring-boot-adapter-app/pom.xml b/testsuite/integration-arquillian/test-apps/spring-boot-adapter-app/pom.xml index 76190b381843..78508427d45e 100644 --- a/testsuite/integration-arquillian/test-apps/spring-boot-adapter-app/pom.xml +++ b/testsuite/integration-arquillian/test-apps/spring-boot-adapter-app/pom.xml @@ -5,7 +5,7 @@ org.keycloak.testsuite integration-arquillian-test-apps - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/test-apps/test-apps-dist/pom.xml b/testsuite/integration-arquillian/test-apps/test-apps-dist/pom.xml index e937de7a6752..53c075553e68 100644 --- a/testsuite/integration-arquillian/test-apps/test-apps-dist/pom.xml +++ b/testsuite/integration-arquillian/test-apps/test-apps-dist/pom.xml @@ -5,7 +5,7 @@ integration-arquillian-test-apps org.keycloak.testsuite - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml index a85e30f5af2e..7f38d18e0724 100644 --- a/testsuite/integration-arquillian/tests/base/pom.xml +++ b/testsuite/integration-arquillian/tests/base/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian-tests - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/jboss/pom.xml index 4a6cdb684327..8373429610ec 100644 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/pom.xml +++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/pom.xml @@ -24,7 +24,7 @@ org.keycloak.testsuite integration-arquillian-tests-adapters - 999.0.0-SNAPSHOT + 22.0.3 integration-arquillian-tests-adapters-jboss diff --git a/testsuite/integration-arquillian/tests/other/adapters/karaf/fuse61/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/karaf/fuse61/pom.xml index 4f8c67e76822..ffb8975fb05b 100644 --- a/testsuite/integration-arquillian/tests/other/adapters/karaf/fuse61/pom.xml +++ b/testsuite/integration-arquillian/tests/other/adapters/karaf/fuse61/pom.xml @@ -24,7 +24,7 @@ org.keycloak.testsuite integration-arquillian-tests-adapters-karaf - 999-SNAPSHOT + 22.0.3 integration-arquillian-tests-adapters-fuse61 diff --git a/testsuite/integration-arquillian/tests/other/adapters/karaf/fuse62/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/karaf/fuse62/pom.xml index 8085daefe8d5..a2c831d70584 100644 --- a/testsuite/integration-arquillian/tests/other/adapters/karaf/fuse62/pom.xml +++ b/testsuite/integration-arquillian/tests/other/adapters/karaf/fuse62/pom.xml @@ -24,7 +24,7 @@ org.keycloak.testsuite integration-arquillian-tests-adapters-karaf - 999-SNAPSHOT + 22.0.3 integration-arquillian-tests-adapters-fuse62 diff --git a/testsuite/integration-arquillian/tests/other/adapters/karaf/karaf3/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/karaf/karaf3/pom.xml index e1bbedf3cb1a..6dfaa2be674f 100644 --- a/testsuite/integration-arquillian/tests/other/adapters/karaf/karaf3/pom.xml +++ b/testsuite/integration-arquillian/tests/other/adapters/karaf/karaf3/pom.xml @@ -24,7 +24,7 @@ org.keycloak.testsuite integration-arquillian-tests-adapters-karaf - 999-SNAPSHOT + 22.0.3 integration-arquillian-tests-adapters-karaf3 diff --git a/testsuite/integration-arquillian/tests/other/adapters/karaf/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/karaf/pom.xml index fd669a37de85..387d99ab54c2 100644 --- a/testsuite/integration-arquillian/tests/other/adapters/karaf/pom.xml +++ b/testsuite/integration-arquillian/tests/other/adapters/karaf/pom.xml @@ -24,7 +24,7 @@ org.keycloak.testsuite integration-arquillian-tests-adapters - 999-SNAPSHOT + 22.0.3 integration-arquillian-tests-adapters-karaf diff --git a/testsuite/integration-arquillian/tests/other/adapters/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/pom.xml index f64034d58652..08833e367c25 100644 --- a/testsuite/integration-arquillian/tests/other/adapters/pom.xml +++ b/testsuite/integration-arquillian/tests/other/adapters/pom.xml @@ -24,7 +24,7 @@ org.keycloak.testsuite integration-arquillian-tests-other - 999.0.0-SNAPSHOT + 22.0.3 integration-arquillian-tests-adapters diff --git a/testsuite/integration-arquillian/tests/other/adapters/was/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/was/pom.xml index 97a7a6f1f33a..af314c1578b1 100644 --- a/testsuite/integration-arquillian/tests/other/adapters/was/pom.xml +++ b/testsuite/integration-arquillian/tests/other/adapters/was/pom.xml @@ -24,7 +24,7 @@ org.keycloak.testsuite integration-arquillian-tests-adapters - 999.0.0-SNAPSHOT + 22.0.3 integration-arquillian-tests-adapters-was diff --git a/testsuite/integration-arquillian/tests/other/adapters/was/was8/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/was/was8/pom.xml index 6bb112f1325b..e19f6e031529 100644 --- a/testsuite/integration-arquillian/tests/other/adapters/was/was8/pom.xml +++ b/testsuite/integration-arquillian/tests/other/adapters/was/was8/pom.xml @@ -24,7 +24,7 @@ org.keycloak.testsuite integration-arquillian-tests-adapters-was - 999.0.0-SNAPSHOT + 22.0.3 integration-arquillian-tests-adapters-was8 diff --git a/testsuite/integration-arquillian/tests/other/adapters/wls/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/wls/pom.xml index 4cdabe88b6ea..0d7e97c33b4f 100644 --- a/testsuite/integration-arquillian/tests/other/adapters/wls/pom.xml +++ b/testsuite/integration-arquillian/tests/other/adapters/wls/pom.xml @@ -24,7 +24,7 @@ org.keycloak.testsuite integration-arquillian-tests-adapters - 999.0.0-SNAPSHOT + 22.0.3 integration-arquillian-tests-adapters-wls diff --git a/testsuite/integration-arquillian/tests/other/adapters/wls/wls12/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/wls/wls12/pom.xml index 925b66590116..de3894e63b79 100644 --- a/testsuite/integration-arquillian/tests/other/adapters/wls/wls12/pom.xml +++ b/testsuite/integration-arquillian/tests/other/adapters/wls/wls12/pom.xml @@ -24,7 +24,7 @@ org.keycloak.testsuite integration-arquillian-tests-adapters-wls - 999.0.0-SNAPSHOT + 22.0.3 integration-arquillian-tests-adapters-wls12 diff --git a/testsuite/integration-arquillian/tests/other/base-ui/pom.xml b/testsuite/integration-arquillian/tests/other/base-ui/pom.xml index 1ce0c878c4ba..38b423545eaa 100644 --- a/testsuite/integration-arquillian/tests/other/base-ui/pom.xml +++ b/testsuite/integration-arquillian/tests/other/base-ui/pom.xml @@ -22,7 +22,7 @@ integration-arquillian-tests-other org.keycloak.testsuite - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/tests/other/jpa-performance/pom.xml b/testsuite/integration-arquillian/tests/other/jpa-performance/pom.xml index a4715a5937be..3e7e690d07c7 100644 --- a/testsuite/integration-arquillian/tests/other/jpa-performance/pom.xml +++ b/testsuite/integration-arquillian/tests/other/jpa-performance/pom.xml @@ -24,7 +24,7 @@ org.keycloak.testsuite integration-arquillian-tests-other - 999.0.0-SNAPSHOT + 22.0.3 integration-arquillian-tests-jpa-performance diff --git a/testsuite/integration-arquillian/tests/other/mod_auth_mellon/pom.xml b/testsuite/integration-arquillian/tests/other/mod_auth_mellon/pom.xml index d6ce88890d05..f2040a42fd90 100644 --- a/testsuite/integration-arquillian/tests/other/mod_auth_mellon/pom.xml +++ b/testsuite/integration-arquillian/tests/other/mod_auth_mellon/pom.xml @@ -24,7 +24,7 @@ org.keycloak.testsuite integration-arquillian-tests-other - 999.0.0-SNAPSHOT + 22.0.3 integration-arquillian-tests-other-mod_auth_mellon diff --git a/testsuite/integration-arquillian/tests/other/pom.xml b/testsuite/integration-arquillian/tests/other/pom.xml index 4e8f7f9a10de..fd150f6d5d1c 100644 --- a/testsuite/integration-arquillian/tests/other/pom.xml +++ b/testsuite/integration-arquillian/tests/other/pom.xml @@ -24,7 +24,7 @@ org.keycloak.testsuite integration-arquillian-tests - 999.0.0-SNAPSHOT + 22.0.3 integration-arquillian-tests-other diff --git a/testsuite/integration-arquillian/tests/other/springboot-tests/pom.xml b/testsuite/integration-arquillian/tests/other/springboot-tests/pom.xml index ec9ae1efefbb..b5fa23e9de55 100644 --- a/testsuite/integration-arquillian/tests/other/springboot-tests/pom.xml +++ b/testsuite/integration-arquillian/tests/other/springboot-tests/pom.xml @@ -5,7 +5,7 @@ integration-arquillian-tests-other org.keycloak.testsuite - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/tests/other/sssd/pom.xml b/testsuite/integration-arquillian/tests/other/sssd/pom.xml index ff80d347fb47..7af370d4f51d 100644 --- a/testsuite/integration-arquillian/tests/other/sssd/pom.xml +++ b/testsuite/integration-arquillian/tests/other/sssd/pom.xml @@ -5,7 +5,7 @@ integration-arquillian-tests-other org.keycloak.testsuite - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/integration-arquillian/tests/other/webauthn/pom.xml b/testsuite/integration-arquillian/tests/other/webauthn/pom.xml index 4d23a83daab8..e68f2ba0cccd 100644 --- a/testsuite/integration-arquillian/tests/other/webauthn/pom.xml +++ b/testsuite/integration-arquillian/tests/other/webauthn/pom.xml @@ -5,7 +5,7 @@ org.keycloak.testsuite integration-arquillian-tests-other - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 jar diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml index 7f2d9ac54607..cdfeab15fa94 100644 --- a/testsuite/integration-arquillian/tests/pom.xml +++ b/testsuite/integration-arquillian/tests/pom.xml @@ -24,7 +24,7 @@ org.keycloak.testsuite integration-arquillian - 999.0.0-SNAPSHOT + 22.0.3 pom diff --git a/testsuite/integration-arquillian/util/pom.xml b/testsuite/integration-arquillian/util/pom.xml index 07ab5b19ef25..9efb970500ab 100644 --- a/testsuite/integration-arquillian/util/pom.xml +++ b/testsuite/integration-arquillian/util/pom.xml @@ -21,7 +21,7 @@ org.keycloak.testsuite integration-arquillian - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/testsuite/model/pom.xml b/testsuite/model/pom.xml index 483ea215abe1..9159712b9b0e 100644 --- a/testsuite/model/pom.xml +++ b/testsuite/model/pom.xml @@ -4,7 +4,7 @@ org.keycloak keycloak-testsuite-pom - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml diff --git a/testsuite/pom.xml b/testsuite/pom.xml index 5f9032a716a7..b019b70ae497 100755 --- a/testsuite/pom.xml +++ b/testsuite/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml 4.0.0 diff --git a/testsuite/utils/pom.xml b/testsuite/utils/pom.xml index f923a8af45ba..53337b6620f1 100755 --- a/testsuite/utils/pom.xml +++ b/testsuite/utils/pom.xml @@ -21,7 +21,7 @@ keycloak-testsuite-pom org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/themes/pom.xml b/themes/pom.xml index 7085e3f06d98..3e6454c78f21 100755 --- a/themes/pom.xml +++ b/themes/pom.xml @@ -3,7 +3,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 4.0.0 diff --git a/util/embedded-ldap/pom.xml b/util/embedded-ldap/pom.xml index 1e10ff4e357b..9f030da336b4 100644 --- a/util/embedded-ldap/pom.xml +++ b/util/embedded-ldap/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../../pom.xml 4.0.0 diff --git a/util/pom.xml b/util/pom.xml index 8b716dbbd2e5..eecbf4f653cf 100644 --- a/util/pom.xml +++ b/util/pom.xml @@ -20,7 +20,7 @@ keycloak-parent org.keycloak - 999.0.0-SNAPSHOT + 22.0.3 ../pom.xml