diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index abadf2aa433f..328062818163 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -5,11 +5,14 @@ body: - type: checkboxes attributes: label: Before reporting an issue - description: Please search to see if the issue is already reported, and try to reproduce the issue on the latest release. + description: | + Please search to see if the issue is already reported, and try to reproduce the issue on the latest release. + + Any reported issues must be reproducible in the [latest](https://github.com/keycloak/keycloak/releases/latest) or [nightly](https://github.com/keycloak/keycloak/releases/nightly) version of Keycloak. + + **⚠️ Failing to follow these guidelines may result in your issue being closed without action. ⚠️** options: - - label: I have searched existing issues - required: true - - label: I have reproduced the issue with the [latest nightly release](https://github.com/keycloak/keycloak/releases/tag/nightly) + - label: I have read and understood the above terms for submitting issues, and I understand that my issue may be closed without action if I do not follow them. required: true - type: dropdown id: area diff --git a/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/infinispan/InfinispanSessionCacheIdMapperUpdater.java b/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/infinispan/InfinispanSessionCacheIdMapperUpdater.java index 093a85d38dcb..a6ac4fe6cd15 100644 --- a/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/infinispan/InfinispanSessionCacheIdMapperUpdater.java +++ b/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/infinispan/InfinispanSessionCacheIdMapperUpdater.java @@ -20,7 +20,6 @@ import org.keycloak.adapters.spi.SessionIdMapper; import org.keycloak.adapters.spi.SessionIdMapperUpdater; -import java.util.*; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.servlet.ServletContext; @@ -32,6 +31,8 @@ import org.infinispan.persistence.remote.RemoteStore; import org.jboss.logging.Logger; +import java.util.Set; + /** * * @author hmlnarik diff --git a/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/infinispan/SsoSessionCacheListener.java b/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/infinispan/SsoSessionCacheListener.java index c098cdba79d5..c47b1a73f2e6 100644 --- a/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/infinispan/SsoSessionCacheListener.java +++ b/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/infinispan/SsoSessionCacheListener.java @@ -18,7 +18,7 @@ import org.keycloak.adapters.spi.SessionIdMapper; -import java.util.*; +import java.util.Queue; import java.util.concurrent.*; import org.infinispan.Cache; import org.infinispan.client.hotrod.annotation.ClientCacheEntryCreated; diff --git a/common/src/main/java/org/keycloak/common/Profile.java b/common/src/main/java/org/keycloak/common/Profile.java index 7704c171becb..4f52aa927b59 100755 --- a/common/src/main/java/org/keycloak/common/Profile.java +++ b/common/src/main/java/org/keycloak/common/Profile.java @@ -96,7 +96,7 @@ public enum Feature { DEVICE_FLOW("OAuth 2.0 Device Authorization Grant", Type.DEFAULT), - TRANSIENT_USERS("Transient users for brokering", Type.PREVIEW), + TRANSIENT_USERS("Transient users for brokering", Type.EXPERIMENTAL), ; private final Type type; diff --git a/common/src/main/java/org/keycloak/common/util/DerUtils.java b/common/src/main/java/org/keycloak/common/util/DerUtils.java index f00e547d5240..a22d4908bb38 100755 --- a/common/src/main/java/org/keycloak/common/util/DerUtils.java +++ b/common/src/main/java/org/keycloak/common/util/DerUtils.java @@ -52,14 +52,15 @@ public static PrivateKey decodePrivateKey(InputStream is) dis.readFully(keyBytes); dis.close(); - PKCS8EncodedKeySpec spec = - new PKCS8EncodedKeySpec(keyBytes); - KeyFactory kf =CryptoIntegration.getProvider().getKeyFactory("RSA"); - return kf.generatePrivate(spec); + return decodePrivateKey(keyBytes); } public static PublicKey decodePublicKey(byte[] der) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException { - return decodePublicKey(der, "RSA"); + try { + return decodePublicKey(der, "RSA"); + } catch (InvalidKeySpecException e) { + return decodePublicKey(der, "EC"); + } } public static PublicKey decodePublicKey(byte[] der, String type) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException { @@ -79,7 +80,10 @@ public static X509Certificate decodeCertificate(InputStream is) throws Exception public static PrivateKey decodePrivateKey(byte[] der) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException { PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(der); - KeyFactory kf = CryptoIntegration.getProvider().getKeyFactory("RSA"); - return kf.generatePrivate(spec); + try { + return CryptoIntegration.getProvider().getKeyFactory("RSA").generatePrivate(spec); + } catch (InvalidKeySpecException e) { + return CryptoIntegration.getProvider().getKeyFactory("EC").generatePrivate(spec); + } } } diff --git a/common/src/main/java/org/keycloak/common/util/PemUtils.java b/common/src/main/java/org/keycloak/common/util/PemUtils.java index 955f509bdf00..377d8db25684 100755 --- a/common/src/main/java/org/keycloak/common/util/PemUtils.java +++ b/common/src/main/java/org/keycloak/common/util/PemUtils.java @@ -23,6 +23,10 @@ import java.security.PublicKey; import java.security.cert.Certificate; import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; import org.keycloak.common.crypto.CryptoIntegration; @@ -53,6 +57,23 @@ public static X509Certificate decodeCertificate(String cert) { return CryptoIntegration.getProvider().getPemUtils().decodeCertificate(cert); } + /** + * Decode one or more X509 Certificates from a PEM string (certificate bundle) + * + * @param certs + * @return + * @throws Exception + */ + public static X509Certificate[] decodeCertificates(String certs) { + String[] pemBlocks = certs.split(END_CERT); + + List x509Certificates = Arrays.stream(pemBlocks) + .filter(pemBlock -> pemBlock != null && !pemBlock.trim().isEmpty()) + .map(pemBlock -> PemUtils.decodeCertificate(pemBlock + END_CERT)) + .collect(Collectors.toList()); + + return x509Certificates.toArray(new X509Certificate[x509Certificates.size()]); + } /** * Decode a Public Key from a PEM string diff --git a/common/src/test/java/org/keycloak/common/ProfileTest.java b/common/src/test/java/org/keycloak/common/ProfileTest.java index 62a750330b1f..003d6a46f982 100644 --- a/common/src/test/java/org/keycloak/common/ProfileTest.java +++ b/common/src/test/java/org/keycloak/common/ProfileTest.java @@ -93,7 +93,7 @@ public void checkDefaults() { disabledFeatures.add(Profile.Feature.KERBEROS); } assertEquals(profile.getDisabledFeatures(), disabledFeatures); - assertEquals(profile.getPreviewFeatures(), Profile.Feature.ACCOUNT3, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.DECLARATIVE_USER_PROFILE, Profile.Feature.CLIENT_SECRET_ROTATION, Profile.Feature.UPDATE_EMAIL, Profile.Feature.DPOP, Profile.Feature.TRANSIENT_USERS); + assertEquals(profile.getPreviewFeatures(), Profile.Feature.ACCOUNT3, Profile.Feature.ADMIN_FINE_GRAINED_AUTHZ, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, Profile.Feature.DECLARATIVE_USER_PROFILE, Profile.Feature.CLIENT_SECRET_ROTATION, Profile.Feature.UPDATE_EMAIL, Profile.Feature.DPOP); } @Test diff --git a/core/src/main/java/org/keycloak/TokenVerifier.java b/core/src/main/java/org/keycloak/TokenVerifier.java index 253f6f7ce613..dc2321a9b2be 100755 --- a/core/src/main/java/org/keycloak/TokenVerifier.java +++ b/core/src/main/java/org/keycloak/TokenVerifier.java @@ -33,7 +33,10 @@ import javax.crypto.SecretKey; import java.security.PublicKey; -import java.util.*; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; diff --git a/docs/documentation/release_notes/topics/23_0_0.adoc b/docs/documentation/release_notes/topics/23_0_0.adoc index d920bb4fb5e8..748f80f985ad 100644 --- a/docs/documentation/release_notes/topics/23_0_0.adoc +++ b/docs/documentation/release_notes/topics/23_0_0.adoc @@ -19,7 +19,43 @@ https://github.com/tnorimat[Takashi Norimatsu] and https://github.com/dteleguin[ Keycloak has preview support for https://fidoalliance.org/passkeys/[Passkeys]. Passkey registration and authentication are realized by the features of WebAuthn. -Therefore, users of Keycloak can do passkey registration and authentication by existing WebAuthn registraton and authentication. +Therefore, users of Keycloak can do passkey registration and authentication by existing WebAuthn registration and authentication. Both synced passkeys and device-bound passkeys can be used for both Same-Device and Cross-Device Authentication. -However, passkeys operations success depends on the user's environment. Make sure which operations can succeed in https://passkeys.dev/device-support/[the environment]. \ No newline at end of file +However, passkeys operations success depends on the user's environment. Make sure which operations can succeed in https://passkeys.dev/device-support/[the environment]. +Thanks to https://github.com/tnorimat[Takashi Norimatsu] for the contribution and thanks to https://github.com/thomasdarimont[Thomas Darimont] for the help with the +ideas and testing of this feature. + += WebAuthn improvements + +WebAuthn policy now includes a new field: `Extra Origins`. It provides better interoperability with non-Web platforms (for example, native mobile applications). +Thanks to https://github.com/akunzai[Charley Wu] for the contribution. + += RESTEasy Reactive + +Keycloak has switched to RESTEasy Reactive. Applications using `quarkus-resteasy-reactive` should still benefit from a better startup time, runtime performance, and memory footprint, even though not using reactive style/semantics. SPI's that depend directly on JAX-RS API should be compatible with this change. SPI's that depend on RESTEasy Classic including `ResteasyClientBuilder` will not be compatible and will require update, this will also be true for other implementation of the JAX-RS API like Jersey. + += More flexibility for introspection endpoint + +In previous versions, introspection endpoint automatically returned most claims, which were available in the access token. Now there is new +switch `Add to token introspection` on most of protocol mappers. This addition allows more flexibility as introspection endpoint can return different +claims than access token. This is first step towards "Lightweight access tokens" support as access tokens can omit lots of the claims, which would be still returned +by the introspection endpoint. When migrating from previous versions, the introspection endpoint should return same claims, which are returned from access token, +so the behavior should be effectively the same by default after the migration. Thanks to https://github.com/skabano[Shigeyuki Kabano] for the contribution. + += Feature flag for OAuth 2.0 device authorization grant flow + +The OAuth 2.0 device authorization grant flow now includes a feature flag, so you can easily disable this feature. This feature is still enabled by default. +Thanks to https://github.com/thomasdarimont[Thomas Darimont] for the contribution. + += Group scalability improvements + +Performance around searching of groups is improved for the use-cases with many groups and subgroups. There are improvements, which allow +paginated lookup of subgroups. Thanks to https://github.com/alice-wondered[Alice] for the contribution. + += User profile improvements + +Declarative user profile is still a preview feature in this release, but we are working hard on promoting it to a supported feature. Feedback is welcome. +If you find any issues or have any improvements in mind, you are welcome to create https://github.com/keycloak/keycloak/issues/new/choose[Github issue], +ideally with label `area/user-profile`. + diff --git a/docs/documentation/server_admin/images/cache-tab.png b/docs/documentation/server_admin/images/cache-tab.png deleted file mode 100644 index 36d0dc5830cc..000000000000 Binary files a/docs/documentation/server_admin/images/cache-tab.png and /dev/null differ diff --git a/docs/documentation/server_admin/topics/admin-console.adoc b/docs/documentation/server_admin/topics/admin-console.adoc index a28eee5c83e6..544437a7c7c9 100644 --- a/docs/documentation/server_admin/topics/admin-console.adoc +++ b/docs/documentation/server_admin/topics/admin-console.adoc @@ -7,9 +7,6 @@ include::realms/proc-using-admin-console.adoc[leveloffset=1] include::realms/master.adoc[leveloffset=2] include::realms/proc-creating-a-realm.adoc[leveloffset=2] include::realms/ssl.adoc[leveloffset=2][] -ifeval::[{project_product}==true] -include::realms/cache.adoc[leveloffset=2][] -endif::[] include::realms/email.adoc[leveloffset=2] include::realms/themes.adoc[leveloffset=2] include::realms/proc-configuring-internationalization.adoc[leveloffset=2] diff --git a/docs/documentation/server_admin/topics/events/login.adoc b/docs/documentation/server_admin/topics/events/login.adoc index 1df417920fcf..899bd797e9d7 100644 --- a/docs/documentation/server_admin/topics/events/login.adoc +++ b/docs/documentation/server_admin/topics/events/login.adoc @@ -171,17 +171,3 @@ You can exclude events by using the `--spi-events-listener-email-exclude-events` kc.[sh|bat] --spi-events-listener-email-exclude-events=UPDATE_TOTP,REMOVE_TOTP ---- -You can set a maximum length of each Event detail in the database by using the `--spi-events-store-jpa-max-detail-length` argument. This setting is useful if a detail (for example, redirect_uri) is long. For example: - -[source,bash] ----- -kc.[sh|bat] --spi-events-store-jpa-max-detail-length=1000 ----- - -Also you can set a maximum length of all Event's details by using the `--spi-events-store-jpa-max-field-length` argument. This setting is useful if you want to adhere to the underlying storage limitation. For example: - -[source,bash] ----- -kc.[sh|bat] --spi-events-store-jpa-max-field-length=2500 ----- - diff --git a/docs/documentation/server_admin/topics/identity-broker/oidc.adoc b/docs/documentation/server_admin/topics/identity-broker/oidc.adoc index 2fc28440a16a..d152e5912bad 100644 --- a/docs/documentation/server_admin/topics/identity-broker/oidc.adoc +++ b/docs/documentation/server_admin/topics/identity-broker/oidc.adoc @@ -46,6 +46,9 @@ image:images/oidc-add-identity-provider.png[Add Identity Provider] |Signature algorithm to create JWT assertion as client authentication. In the case of JWT signed with private key or Client secret as jwt, it is required. If no algorithm is specified, the following algorithm is adapted. `RS256` is adapted in the case of JWT signed with private key. `HS256` is adapted in the case of Client secret as jwt. +|Client Assertion Audience +|The audience to use for the client assertion. The default value is the IDP's token endpoint URL. + |Issuer |{project_name} validates issuer claims, in responses from the IDP, against this value. diff --git a/docs/documentation/server_admin/topics/realms/cache.adoc b/docs/documentation/server_admin/topics/realms/cache.adoc deleted file mode 100644 index 0b37cb32673e..000000000000 --- a/docs/documentation/server_admin/topics/realms/cache.adoc +++ /dev/null @@ -1,16 +0,0 @@ -[[_clear-cache]] -= Clearing server caches - -{project_name} caches everything it can in memory within the limits of your JVM and the limits you have configured. If the {project_name} database is modified by a third party, such as a DBA, outside the scope of the server's REST APIs or Admin Console, parts of the in-memory cache could be stale. You can clear the realm cache, user cache or cache of external public keys, such as Public keys of - external clients or Identity providers, which {project_name} may use to verify signatures of particular external entity. - -.Procedure - -. Click *Realm Settings* in the menu. - -. Click the *Cache* tab. - -. Click *Clear* for the cache you want to evict. -+ -.Cache tab -image:images/cache-tab.png[Cache tab] diff --git a/docs/documentation/tests/src/test/java/org/keycloak/documentation/test/utils/LinkUtils.java b/docs/documentation/tests/src/test/java/org/keycloak/documentation/test/utils/LinkUtils.java index 033b72549a1a..912fc524c04b 100644 --- a/docs/documentation/tests/src/test/java/org/keycloak/documentation/test/utils/LinkUtils.java +++ b/docs/documentation/tests/src/test/java/org/keycloak/documentation/test/utils/LinkUtils.java @@ -11,7 +11,13 @@ import java.io.FileOutputStream; import java.io.IOException; import java.net.URL; -import java.util.*; +import java.util.Map; +import java.util.List; +import java.util.Set; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.HashMap; +import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/docs/documentation/upgrading/topics/keycloak/changes-23_0_0.adoc b/docs/documentation/upgrading/topics/keycloak/changes-23_0_0.adoc index 67697e36dd71..66410d02b5f8 100644 --- a/docs/documentation/upgrading/topics/keycloak/changes-23_0_0.adoc +++ b/docs/documentation/upgrading/topics/keycloak/changes-23_0_0.adoc @@ -135,6 +135,13 @@ Stream getTopLevelGroupsStream(RealmModel realm, Endpoint `GET {keycloak server}/realms/{realm}/groups/{group_id}/children` added as a way to get subgroups of specific groups that support pagination += RESTEeasy Reactive +Relying on RESTEasy Classic is not longer an option because it is not available anymore. Migration will be needed for SPI's and code that is relying on RESTEasy Classic and related packages part of `org.jboss.resteasy.spi.*`. + = Partial export requires manage-realm permission -The endpoint `POST {keycloak server}/realms/{realm}/partial-export` and the corresponding action in the admin console now require `manage-realm` permission for execution instead of `view-realm`. This endpoint exports the realm configuration into a JSON file and the new permission is more appropriate. The parameters `exportGroupsAndRoles` and `exportClients`, which include the realm groups/roles and clients in the export respectively, continue managing the same permissions (`query-groups` and `view-clients`). \ No newline at end of file +The endpoint `POST {keycloak server}/realms/{realm}/partial-export` and the corresponding action in the admin console now require `manage-realm` permission for execution instead of `view-realm`. This endpoint exports the realm configuration into a JSON file and the new permission is more appropriate. The parameters `exportGroupsAndRoles` and `exportClients`, which include the realm groups/roles and clients in the export respectively, continue managing the same permissions (`query-groups` and `view-clients`). + += Removal of the options to trim the event's details length + +Since this release, Keycloak supports long value for `EventEntity` details column. Therefore, it no longer supports options for trimming event detail length `--spi-events-store-jpa-max-detail-length` and `--spi-events-store-jpa-max-field-length`. diff --git a/docs/guides/getting-started/templates/first-app.adoc b/docs/guides/getting-started/templates/first-app.adoc index 5fe07c69d6f2..dc13a30be718 100644 --- a/docs/guides/getting-started/templates/first-app.adoc +++ b/docs/guides/getting-started/templates/first-app.adoc @@ -9,12 +9,11 @@ To secure the first application, you start by registering the application with y . Fill in the form with the following values: ** *Client type*: `OpenID Connect` ** *Client ID*: `myclient` ++ +image::add-client-1.png[Add Client] . Click *Next* . Confirm that *Standard flow* is enabled. . Click *Next*. -+ -image::add-client-1.png[Add Client] - . Make these changes under *Login settings*. * Set *Valid redirect URIs* to `+https://www.keycloak.org/app/*+` * Set *Web origins* to `+https://www.keycloak.org+` diff --git a/docs/guides/server/caching.adoc b/docs/guides/server/caching.adoc index 4250f593ee98..f88fc00f8e2b 100644 --- a/docs/guides/server/caching.adoc +++ b/docs/guides/server/caching.adoc @@ -14,7 +14,7 @@ The current distributed cache implementation is built on top of https://infinisp == Enable distributed caching When you start Keycloak in production mode, by using the `start` command, caching is enabled and all Keycloak nodes in your network are discovered. -By default, caches are using a `UDP` transport stack so that nodes are discovered using IP multicast transport based on UDP. For most production environments, there are better discovery alternatives to UDP available. Keycloak allows you to either choose from a set of pre-defined default transport stacks, or to define your own custom stack, as you will see later in this {section}. +By default, caches are using a UDP transport stack so that nodes are discovered using IP multicast transport based on UDP. For most production environments, there are better discovery alternatives to UDP available. Keycloak allows you to either choose from a set of pre-defined default transport stacks, or to define your own custom stack, as you will see later in this {section}. To explicitly enable distributed infinispan caching, enter this command: @@ -156,7 +156,7 @@ To apply a specific cache stack, enter this command: <@kc.build parameters="--cache-stack="/> -The default stack is set to `UDP` when distributed caches are enabled. +The default stack is set to `udp` when distributed caches are enabled. === Available transport stacks @@ -181,9 +181,9 @@ The following table shows transport stacks that are available using the `--cache === Additional transport stacks The following table shows transport stacks that are supported by Keycloak, but need some extra steps to work. -Note that _none_ of these stacks are Kubernetes / OpenShift stacks, so no need exists to enable the "google" stack if you want to run Keycloak on top of the Google Kubernetes engine. +Note that _none_ of these stacks are Kubernetes / OpenShift stacks, so no need exists to enable the `google` stack if you want to run Keycloak on top of the Google Kubernetes engine. In that case, use the `kubernetes` stack. -Instead, when you have a distributed cache setup running on AWS EC2 instances, you would need to set the stack to `ec2`, because ec2 does not support a default discovery mechanism such as `UDP`. +Instead, when you have a distributed cache setup running on AWS EC2 instances, you would need to set the stack to `ec2`, because ec2 does not support a default discovery mechanism such as UDP. [%autowidth] |=== @@ -197,7 +197,7 @@ Instead, when you have a distributed cache setup running on AWS EC2 instances, y Cloud vendor specific stacks have additional dependencies for Keycloak. For more information and links to repositories with these dependencies, see the https://infinispan.org/docs/dev/titles/embedding/embedding.html#jgroups-cloud-discovery-protocols_cluster-transport[Infinispan documentation]. -To provide the dependencies to Keycloak, put the respective JAR in the `providers` directory and `build` Keycloak by entering this command: +To provide the dependencies to Keycloak, put the respective JAR in the `providers` directory and build Keycloak by entering this command: <@kc.build parameters="--cache-stack="/> diff --git a/js/apps/account-ui/playwright.config.ts b/js/apps/account-ui/playwright.config.ts index 5090beec9c40..621511ac45c6 100644 --- a/js/apps/account-ui/playwright.config.ts +++ b/js/apps/account-ui/playwright.config.ts @@ -9,7 +9,7 @@ export default defineConfig({ forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: 1, - reporter: process.env.CI ? "github" : "list", + reporter: process.env.CI ? [["github"], ["html"]] : "list", use: { baseURL: process.env.CI ? "http://localhost:8080/realms/master/account/" 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 c464d90e0114..9d07d96d3bc2 100644 --- a/js/apps/admin-ui/cypress/e2e/i18n_test.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/i18n_test.spec.ts @@ -134,7 +134,6 @@ describe("i18n tests", () => { goToUserFederationPage(); - // check key "user-federation:addProvider_other" providersPage.assertCardContainsText("ldap", "Add Ldap providers"); }); 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 cf65cc11ab8c..6f04759aa824 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 @@ -84,18 +84,25 @@ describe("OIDC identity provider test", () => { ClientAuthentication.basicAuth, ); providerBaseAdvancedSettingsPage.assertOIDCClientAuthentication( - ClientAuthentication.jwt, + ClientAuthentication.post, ); providerBaseAdvancedSettingsPage.assertOIDCClientAuthentication( - ClientAuthentication.jwtPrivKey, + ClientAuthentication.jwt, ); providerBaseAdvancedSettingsPage.assertOIDCClientAuthentication( - ClientAuthentication.post, + ClientAuthentication.jwtPrivKey, ); //Client assertion signature algorithm Object.entries(ClientAssertionSigningAlg).forEach(([, value]) => { providerBaseAdvancedSettingsPage.assertOIDCClientAuthSignAlg(value); }); + //Client assertion audience + providerBaseAdvancedSettingsPage.typeClientAssertionAudience( + "http://localhost:8180", + ); + providerBaseAdvancedSettingsPage.assertClientAssertionAudienceInputEqual( + "http://localhost:8180", + ); //OIDC Advanced Settings providerBaseAdvancedSettingsPage.assertOIDCSettingsAdvancedSwitches(); providerBaseAdvancedSettingsPage.selectPromptOption(PromptSelect.none); diff --git a/js/apps/admin-ui/cypress/e2e/realm_settings_user_profile_enabled.spec.ts b/js/apps/admin-ui/cypress/e2e/realm_settings_user_profile_enabled.spec.ts index 3caca7b27850..8db687cd3ecb 100644 --- a/js/apps/admin-ui/cypress/e2e/realm_settings_user_profile_enabled.spec.ts +++ b/js/apps/admin-ui/cypress/e2e/realm_settings_user_profile_enabled.spec.ts @@ -213,7 +213,6 @@ describe("User profile tabs", () => { cy.get(".pf-c-form__label-text") .contains("newAttribute2") .should("not.exist"); - cy.findByTestId("firstName").type("testuser9"); cy.findByTestId("email").clear(); cy.findByTestId("email").type("testuser9@gmail.com"); cy.findByTestId("save-user").click(); 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 9ee6d3309da6..fc34006615f8 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 @@ -29,10 +29,10 @@ export enum PromptSelect { } export enum ClientAuthentication { - post = "Client secret sent as basic auth", - basicAuth = "Client secret as jwt", - jwt = "JWT signed with private key", - jwtPrivKey = "Client secret sent as post", + post = "Client secret sent as post", + basicAuth = "Client secret sent as basic auth", + jwt = "JWT signed with client secret", + jwtPrivKey = "JWT signed with private key", } export enum ClientAssertionSigningAlg { @@ -84,6 +84,7 @@ export default class ProviderBaseGeneralSettingsPage extends PageObject { #pkceMethod = "#pkceMethod"; #clientAuth = "#clientAuthentication"; #clientAssertionSigningAlg = "#clientAssertionSigningAlg"; + #clientAssertionAudienceInput = "#clientAssertionAudience"; public clickSaveBtn() { cy.findByTestId(this.#saveBtn).click(); @@ -187,6 +188,11 @@ export default class ProviderBaseGeneralSettingsPage extends PageObject { return this; } + public typeClientAssertionAudience(text: string) { + cy.get(this.#clientAssertionAudienceInput).type(text).blur(); + return this; + } + public selectSyncModeOption(syncModeOption: SyncModeOption) { cy.get(this.#syncModeSelect).click(); super.clickSelectMenuItem( @@ -314,6 +320,13 @@ export default class ProviderBaseGeneralSettingsPage extends PageObject { return this; } + public assertClientAssertionAudienceInputEqual(text: string) { + cy.get(this.#clientAssertionAudienceInput) + .should("have.value", text) + .parent(); + return this; + } + public assertOIDCUrl(url: string) { cy.findByTestId("jump-link-openid-connect-settings").click(); cy.findByTestId(url + "Url") diff --git a/js/apps/admin-ui/public/locales/en/translation.json b/js/apps/admin-ui/public/locales/en/translation.json index eab519da4ff4..0db46e2e63d3 100644 --- a/js/apps/admin-ui/public/locales/en/translation.json +++ b/js/apps/admin-ui/public/locales/en/translation.json @@ -2894,9 +2894,10 @@ "clientAuthentications": { "client_secret_post": "Client secret sent as post", "client_secret_basic": "Client secret sent as basic auth", - "client_secret_jwt": "Client secret as jwt", + "client_secret_jwt": "JWT signed with client secret", "private_key_jwt": "JWT signed with private key" }, + "clientAssertionAudience": "Client assertion audience", "clientAssertionSigningAlg": "Client assertion signature algorithm", "algorithmNotSpecified": "Algorithm not specified", "acceptsPromptNone": "Accepts prompt=none forward from client", @@ -2979,7 +2980,8 @@ "attributeConsumingServiceNameHelp": "Name of the Attribute Consuming Service profile to advertise in the SP metadata.", "forwardParametersHelp": "Non OpenID Connect/OAuth standard query parameters to be forwarded to external IDP from the initial application request to Authorization Endpoint. Multiple parameters can be entered, separated by comma (,).", "clientAuthenticationHelp": "The client authentication method (cfr. https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication). In case of JWT signed with private key, the realm private key is used.", - "clientAssertionSigningAlgHelp": "Signature algorithm to create JWT assertion as client authentication. In the case of JWT signed with private key or Client secret as jwt, it is required. If no algorithm is specified, the following algorithm is adapted. RS256 is adapted in the case of JWT signed with private key. HS256 is adapted in the case of Client secret as jwt.", + "clientAssertionAudienceHelp": "The audience to use for the client assertion. The default value is the IDP's token endpoint URL.", + "clientAssertionSigningAlgHelp": "Signature algorithm to create JWT assertion as client authentication. In the case of JWT signed with private key or JWT signed with client secret, it is required. If no algorithm is specified, the following algorithm is adapted. RS256 is adapted in the case of JWT signed with private key. HS256 is adapted in the case of JWT signed with client secret.", "storeTokensHelp": "Enable/disable if tokens must be stored after authenticating users.", "storedTokensReadableHelp": "Enable/disable if new users can read any stored tokens. This assigns the broker.read-token role.", "accountLinkingOnlyHelp": "If true, users cannot log in through this provider. They can only link to this provider. This is useful if you don't want to allow login from the provider, but want to integrate with a provider", diff --git a/js/apps/admin-ui/src/authentication/AuthenticationSection.tsx b/js/apps/admin-ui/src/authentication/AuthenticationSection.tsx index b3ee06022d6d..c9c2ae9dceb1 100644 --- a/js/apps/admin-ui/src/authentication/AuthenticationSection.tsx +++ b/js/apps/admin-ui/src/authentication/AuthenticationSection.tsx @@ -1,3 +1,4 @@ +import { fetchWithError } from "@keycloak/keycloak-admin-client"; import type AuthenticationFlowRepresentation from "@keycloak/keycloak-admin-client/lib/defs/authenticationFlowRepresentation"; import RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; import { @@ -101,7 +102,7 @@ export default function AuthenticationSection() { ]); const loader = async () => { - const flowsRequest = await fetch( + const flowsRequest = await fetchWithError( `${addTrailingSlash( adminClient.baseUrl, )}admin/realms/${realmName}/ui-ext/authentication-management/flows`, diff --git a/js/apps/admin-ui/src/authentication/EmptyExecutionState.tsx b/js/apps/admin-ui/src/authentication/EmptyExecutionState.tsx index dc529f3fa33b..1bfe9c5ae963 100644 --- a/js/apps/admin-ui/src/authentication/EmptyExecutionState.tsx +++ b/js/apps/admin-ui/src/authentication/EmptyExecutionState.tsx @@ -69,7 +69,7 @@ export const EmptyExecutionState = ({ {t(`${section}Title`)} -

{t(`authentication-help:${section}`)}

+

{t(section)}

diff --git a/js/apps/admin-ui/src/authentication/policies/PolicyRow.tsx b/js/apps/admin-ui/src/authentication/policies/PolicyRow.tsx index 2b429d46fca0..fc0d31520b67 100644 --- a/js/apps/admin-ui/src/authentication/policies/PolicyRow.tsx +++ b/js/apps/admin-ui/src/authentication/policies/PolicyRow.tsx @@ -45,7 +45,7 @@ export const PolicyRow = ({ labelIcon={ } > diff --git a/js/apps/admin-ui/src/clients/add/SamlConfig.tsx b/js/apps/admin-ui/src/clients/add/SamlConfig.tsx index 415297082aa4..a1fb1c3a31dc 100644 --- a/js/apps/admin-ui/src/clients/add/SamlConfig.tsx +++ b/js/apps/admin-ui/src/clients/add/SamlConfig.tsx @@ -27,12 +27,7 @@ export const Toggle = ({ name, label }: ToggleProps) => { hasNoPaddingTop label={t(label)} fieldId={label} - labelIcon={ - - } + labelIcon={} > - } + labelIcon={} > { - } + labelIcon={} > diff --git a/js/apps/admin-ui/src/clients/credentials/Credentials.tsx b/js/apps/admin-ui/src/clients/credentials/Credentials.tsx index a77f968fa280..34f5b8c9cb70 100644 --- a/js/apps/admin-ui/src/clients/credentials/Credentials.tsx +++ b/js/apps/admin-ui/src/clients/credentials/Credentials.tsx @@ -91,7 +91,7 @@ export const Credentials = ({ client, save, refresh }: CredentialsProps) => { addAlert(t(`${message}Success`), AlertVariant.success); return data; } catch (error) { - addError(`clients:${message}Error`, error); + addError(`${message}Error`, error); } } diff --git a/js/apps/admin-ui/src/clients/import/ImportForm.tsx b/js/apps/admin-ui/src/clients/import/ImportForm.tsx index 6d555ebe2116..efad92534761 100644 --- a/js/apps/admin-ui/src/clients/import/ImportForm.tsx +++ b/js/apps/admin-ui/src/clients/import/ImportForm.tsx @@ -1,3 +1,4 @@ +import { fetchWithError } from "@keycloak/keycloak-admin-client"; import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation"; import { Language } from "@patternfly/react-code-editor"; import { @@ -61,7 +62,7 @@ export default function ImportForm() { return JSON.parse(contents); } - const response = await fetch( + const response = await fetchWithError( `${addTrailingSlash( adminClient.baseUrl, )}admin/realms/${realm}/client-description-converter`, diff --git a/js/apps/admin-ui/src/clients/keys/Certificate.tsx b/js/apps/admin-ui/src/clients/keys/Certificate.tsx index cbfd4592824d..c9a29e8cc516 100644 --- a/js/apps/admin-ui/src/clients/keys/Certificate.tsx +++ b/js/apps/admin-ui/src/clients/keys/Certificate.tsx @@ -39,12 +39,7 @@ export const Certificate = ({ keyInfo, plain = false }: CertificateProps) => { - } + labelIcon={} > diff --git a/js/apps/admin-ui/src/clients/keys/SamlKeys.tsx b/js/apps/admin-ui/src/clients/keys/SamlKeys.tsx index ee11479598b9..1244ee2fceea 100644 --- a/js/apps/admin-ui/src/clients/keys/SamlKeys.tsx +++ b/js/apps/admin-ui/src/clients/keys/SamlKeys.tsx @@ -95,10 +95,7 @@ const KeySection = ({ + } label={t(key)} fieldId={key} diff --git a/js/apps/admin-ui/src/clients/scopes/ClientScopes.tsx b/js/apps/admin-ui/src/clients/scopes/ClientScopes.tsx index 18f980632e33..b4c69a885a73 100644 --- a/js/apps/admin-ui/src/clients/scopes/ClientScopes.tsx +++ b/js/apps/admin-ui/src/clients/scopes/ClientScopes.tsx @@ -241,7 +241,7 @@ export const ClientScopes = ({ { if (selectedConfig?.mediaType === "application/zip") { - const response = await fetch( + const response = await fetchWithError( `${addTrailingSlash( adminClient.baseUrl, )}admin/realms/${realm}/clients/${id}/installation/providers/${selected}`, diff --git a/js/apps/admin-ui/src/components/group/GroupPickerDialog.tsx b/js/apps/admin-ui/src/components/group/GroupPickerDialog.tsx index 514ae40b5bc5..b46916915a16 100644 --- a/js/apps/admin-ui/src/components/group/GroupPickerDialog.tsx +++ b/js/apps/admin-ui/src/components/group/GroupPickerDialog.tsx @@ -90,7 +90,7 @@ export const GroupPickerDialog = ({ if (!navigation.map(({ id }) => id).includes(groupId)) { group = await adminClient.groups.findOne({ id: groupId }); if (!group) { - throw new Error(t("common:notFound")); + throw new Error(t("notFound")); } } if (group?.id) { diff --git a/js/apps/admin-ui/src/components/users/UserSelect.tsx b/js/apps/admin-ui/src/components/users/UserSelect.tsx index 63154fae16e4..dd03ea72a33d 100644 --- a/js/apps/admin-ui/src/components/users/UserSelect.tsx +++ b/js/apps/admin-ui/src/components/users/UserSelect.tsx @@ -81,9 +81,7 @@ export const UserSelect = ({ - } + labelIcon={} fieldId={name!} validated={errors[name!] ? "error" : "default"} helperTextInvalid={t("required")} 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 e3e3e91c639d..9b298cfcfbea 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 @@ -1,3 +1,4 @@ +import { fetchWithError } from "@keycloak/keycloak-admin-client"; import { adminClient } from "../../admin-client"; import { getAuthorizationHeaders } from "../../utils/getAuthorizationHeaders"; import { joinPath } from "../../utils/joinPath"; @@ -9,7 +10,7 @@ export async function fetchAdminUI( const accessToken = await adminClient.getAccessToken(); const baseUrl = adminClient.baseUrl; - const response = await fetch( + const response = await fetchWithError( joinPath(baseUrl, "admin/realms", adminClient.realmName, endpoint) + (query ? "?" + new URLSearchParams(query) : ""), { diff --git a/js/apps/admin-ui/src/events/EventsSection.tsx b/js/apps/admin-ui/src/events/EventsSection.tsx index ea2f19bbbd91..ac32d5a8ed62 100644 --- a/js/apps/admin-ui/src/events/EventsSection.tsx +++ b/js/apps/admin-ui/src/events/EventsSection.tsx @@ -312,7 +312,7 @@ export default function EventsSection() { ); }} > - {t(`realm-settings:eventTypes.${chip}.name`)} + {t(`eventTypes.${chip}.name`)} ))} @@ -320,7 +320,7 @@ export default function EventsSection() { > {events?.enabledEventTypes?.map((option) => ( - {t(`realm-settings:eventTypes.${option}.name`)} + {t(`eventTypes.${option}.name`)} ))} @@ -437,7 +437,7 @@ export default function EventsSection() { key={entry} onClick={() => removeFilterValue(key, entry)} > - {t(`realm-settings:eventTypes.${entry}.name`)} + {t(`eventTypes.${entry}.name`)} )) )} diff --git a/js/apps/admin-ui/src/identity-providers/add/OIDCAuthentication.tsx b/js/apps/admin-ui/src/identity-providers/add/OIDCAuthentication.tsx index ce52c3a60f4b..537552c1bd5a 100644 --- a/js/apps/admin-ui/src/identity-providers/add/OIDCAuthentication.tsx +++ b/js/apps/admin-ui/src/identity-providers/add/OIDCAuthentication.tsx @@ -12,6 +12,7 @@ import { HelpItem } from "ui-shared"; import { ClientIdSecret } from "../component/ClientIdSecret"; import { sortProviders } from "../../util"; import { useServerInfo } from "../../context/server-info/ServerInfoProvider"; +import { TextField } from "../component/TextField"; const clientAuthentications = [ "client_secret_post", @@ -123,6 +124,13 @@ export const OIDCAuthentication = ({ create = true }: { create?: boolean }) => { )} /> + {(clientAuthMethod === "private_key_jwt" || + clientAuthMethod === "client_secret_jwt") && ( + + )} ); }; 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 be0ba324dae5..ad5541f18569 100644 --- a/js/apps/admin-ui/src/identity-providers/add/SamlConnectSettings.tsx +++ b/js/apps/admin-ui/src/identity-providers/add/SamlConnectSettings.tsx @@ -1,3 +1,4 @@ +import { fetchWithError } from "@keycloak/keycloak-admin-client"; import type IdentityProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderRepresentation"; import { FormGroup, Title } from "@patternfly/react-core"; import { useFormContext } from "react-hook-form"; @@ -47,7 +48,7 @@ export const SamlConnectSettings = () => { formData.append("file", new Blob([xml])); try { - const response = await fetch( + const response = await fetchWithError( `${addTrailingSlash( adminClient.baseUrl, )}admin/realms/${realm}/identity-provider/import-config`, diff --git a/js/apps/admin-ui/src/realm-settings/DefaultGroupsTab.tsx b/js/apps/admin-ui/src/realm-settings/DefaultGroupsTab.tsx index 5abd2b6c2c82..000f1d0e5b68 100644 --- a/js/apps/admin-ui/src/realm-settings/DefaultGroupsTab.tsx +++ b/js/apps/admin-ui/src/realm-settings/DefaultGroupsTab.tsx @@ -132,7 +132,7 @@ export const DefaultsGroupsTab = () => { {enabled && ( + {" "} . diff --git a/js/apps/admin-ui/src/realm-settings/GeneralTab.tsx b/js/apps/admin-ui/src/realm-settings/GeneralTab.tsx index 05edcaf8fbd9..689a8fd10b22 100644 --- a/js/apps/admin-ui/src/realm-settings/GeneralTab.tsx +++ b/js/apps/admin-ui/src/realm-settings/GeneralTab.tsx @@ -94,7 +94,7 @@ export const RealmSettingsGeneralTab = ({ required: { value: true, message: t("required") }, pattern: { value: /^[a-zA-Z0-9-_]+$/, - message: t("realm:invalidRealmName"), + message: t("invalidRealmName"), }, }} defaultValue="" diff --git a/js/apps/admin-ui/src/realm-settings/NewClientPolicyCondition.tsx b/js/apps/admin-ui/src/realm-settings/NewClientPolicyCondition.tsx index d2dbe21d1407..78a4636b990d 100644 --- a/js/apps/admin-ui/src/realm-settings/NewClientPolicyCondition.tsx +++ b/js/apps/admin-ui/src/realm-settings/NewClientPolicyCondition.tsx @@ -229,9 +229,7 @@ export default function NewClientPolicyCondition() { } > 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 bb37e6e50c72..c146c4fa021c 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 @@ -78,7 +78,7 @@ export const EventsTab = ({ realm }: EventsTabProps) => { } addAlert(t(`${type}-events-cleared`), AlertVariant.success); } catch (error) { - addError(`realm-settings:${type}-events-cleared-error`, error); + addError(`${type}-events-cleared-error`, error); } }, }); diff --git a/js/apps/admin-ui/src/realm-settings/security-defences/Time.tsx b/js/apps/admin-ui/src/realm-settings/security-defences/Time.tsx index be9e50282da5..49b4467f6771 100644 --- a/js/apps/admin-ui/src/realm-settings/security-defences/Time.tsx +++ b/js/apps/admin-ui/src/realm-settings/security-defences/Time.tsx @@ -23,12 +23,7 @@ export const Time = ({ style={style} label={t(name)} fieldId={name} - labelIcon={ - - } + labelIcon={} validated={ errors[name] ? ValidatedOptions.error : ValidatedOptions.default } diff --git a/js/apps/admin-ui/src/realm/routes/AddRealm.tsx b/js/apps/admin-ui/src/realm/routes/AddRealm.tsx index 9655d4a1e9c6..6c3fe30c5f13 100644 --- a/js/apps/admin-ui/src/realm/routes/AddRealm.tsx +++ b/js/apps/admin-ui/src/realm/routes/AddRealm.tsx @@ -10,7 +10,7 @@ const NewRealmForm = lazy(() => import("../add/NewRealmForm")); export const AddRealmRoute: AppRouteObject = { path: "/:realm/add-realm", element: , - breadcrumb: (t) => t("realm:createRealm"), + breadcrumb: (t) => t("createRealm"), handle: { access: "view-realm", }, diff --git a/js/apps/admin-ui/src/user/UserForm.tsx b/js/apps/admin-ui/src/user/UserForm.tsx index ec53ac1437f1..5fab8b87dd0b 100644 --- a/js/apps/admin-ui/src/user/UserForm.tsx +++ b/js/apps/admin-ui/src/user/UserForm.tsx @@ -187,11 +187,40 @@ export const UserForm = ({ )} {userProfileMetadata ? ( - + <> + + } + > + ( + field.onChange(value)} + isChecked={field.value} + label={t("yes")} + labelOff={t("no")} + /> + )} + /> + + + ) : ( <> {!realm.registrationEmailAsUsername && ( diff --git a/js/libs/keycloak-admin-client/package.json b/js/libs/keycloak-admin-client/package.json index dab56cbb391f..e25a62ecbd7f 100644 --- a/js/libs/keycloak-admin-client/package.json +++ b/js/libs/keycloak-admin-client/package.json @@ -47,7 +47,7 @@ "@types/chai": "^4.3.9", "@types/lodash-es": "^4.17.10", "@types/mocha": "^10.0.3", - "@types/node": "^20.8.9", + "@types/node": "^20.8.10", "chai": "^4.3.10", "mocha": "^10.2.0", "ts-node": "^10.9.1" diff --git a/js/libs/keycloak-admin-client/src/index.ts b/js/libs/keycloak-admin-client/src/index.ts index 4829e2ded730..dae9760a23e2 100644 --- a/js/libs/keycloak-admin-client/src/index.ts +++ b/js/libs/keycloak-admin-client/src/index.ts @@ -3,5 +3,5 @@ import { RequiredActionAlias } from "./defs/requiredActionProviderRepresentation export const requiredAction = RequiredActionAlias; export default KeycloakAdminClient; -export { NetworkError } from "./utils/fetchWithError.js"; +export { NetworkError, fetchWithError } from "./utils/fetchWithError.js"; export type { NetworkErrorOptions } from "./utils/fetchWithError.js"; diff --git a/js/libs/keycloak-admin-client/src/utils/stringifyQueryParams.ts b/js/libs/keycloak-admin-client/src/utils/stringifyQueryParams.ts index 2c58d4ca9913..be526d77daa1 100644 --- a/js/libs/keycloak-admin-client/src/utils/stringifyQueryParams.ts +++ b/js/libs/keycloak-admin-client/src/utils/stringifyQueryParams.ts @@ -1,21 +1,29 @@ export function stringifyQueryParams(params: Record) { - return new URLSearchParams( - Object.entries(params).filter((param): param is [string, string] => { - const [, value] = param; + const searchParams = new URLSearchParams(); - if (typeof value === "undefined" || value === null) { - return false; - } + for (const [key, value] of Object.entries(params)) { + // Ignore undefined and null values. + if (value === undefined || value === null) { + continue; + } - if (typeof value === "string" && value.length === 0) { - return false; - } + // Ignore empty strings. + if (typeof value === "string" && value.length === 0) { + continue; + } - if (Array.isArray(value) && value.length === 0) { - return false; - } + // Ignore empty arrays. + if (Array.isArray(value) && value.length === 0) { + continue; + } - return true; - }), - ).toString(); + // Append each entry of an array as a separate parameter, or the value itself otherwise. + if (Array.isArray(value)) { + value.forEach((item) => searchParams.append(key, item.toString())); + } else { + searchParams.append(key, value.toString()); + } + } + + return searchParams.toString(); } diff --git a/js/libs/keycloak-admin-client/test/auth.spec.ts b/js/libs/keycloak-admin-client/test/auth.spec.ts index 2e3514178184..e39be8bd8d70 100644 --- a/js/libs/keycloak-admin-client/test/auth.spec.ts +++ b/js/libs/keycloak-admin-client/test/auth.spec.ts @@ -42,6 +42,6 @@ describe("Authorization", () => { "idToken", ); - expect(data.scope).to.equal("openid profile email"); + expect(data.scope).to.equal("openid email profile"); }); }); diff --git a/js/libs/keycloak-admin-client/test/authenticationManagement.spec.ts b/js/libs/keycloak-admin-client/test/authenticationManagement.spec.ts index 8e472e05cb76..470f2392af7d 100644 --- a/js/libs/keycloak-admin-client/test/authenticationManagement.spec.ts +++ b/js/libs/keycloak-admin-client/test/authenticationManagement.spec.ts @@ -176,7 +176,6 @@ describe("Authentication management", () => { "clients", "first broker login", "docker auth", - "http challenge", ]); }); diff --git a/js/libs/keycloak-admin-client/test/stringifyQueryParams.spec.ts b/js/libs/keycloak-admin-client/test/stringifyQueryParams.spec.ts index 582cf8d98c21..323698296109 100644 --- a/js/libs/keycloak-admin-client/test/stringifyQueryParams.spec.ts +++ b/js/libs/keycloak-admin-client/test/stringifyQueryParams.spec.ts @@ -23,9 +23,10 @@ describe("stringifyQueryParams", () => { numZero: 0, numNegative: -1, str: "Hello World!", + arr: ["foo", "bar"], }), ).to.equal( - "boolTrue=true&boolFalse=false&numPositive=1&numZero=0&numNegative=-1&str=Hello+World%21", + "boolTrue=true&boolFalse=false&numPositive=1&numZero=0&numNegative=-1&str=Hello+World%21&arr=foo&arr=bar", ); }); }); diff --git a/js/libs/keycloak-js/package.json b/js/libs/keycloak-js/package.json index fdc558e5271a..3faea8825bb6 100644 --- a/js/libs/keycloak-js/package.json +++ b/js/libs/keycloak-js/package.json @@ -48,7 +48,7 @@ "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^11.1.5", "es6-promise": "^4.2.8", - "rollup": "^4.1.5" + "rollup": "^4.2.0" }, "dependencies": { "base64-js": "^1.5.1", diff --git a/js/package.json b/js/package.json index a9f5b969f03e..045e172169e5 100644 --- a/js/package.json +++ b/js/package.json @@ -4,7 +4,7 @@ "prepare": "cd .. && husky install js/.husky" }, "devDependencies": { - "@types/node": "^20.8.9", + "@types/node": "^20.8.10", "@typescript-eslint/eslint-plugin": "^6.9.1", "@typescript-eslint/parser": "^6.9.1", "eslint": "^8.52.0", diff --git a/js/pnpm-lock.yaml b/js/pnpm-lock.yaml index 10af2193feb4..0eec2ec51911 100644 --- a/js/pnpm-lock.yaml +++ b/js/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: devDependencies: '@types/node': - specifier: ^20.8.9 - version: 20.8.9 + specifier: ^20.8.10 + version: 20.8.10 '@typescript-eslint/eslint-plugin': specifier: ^6.9.1 version: 6.9.1(@typescript-eslint/parser@6.9.1)(eslint@8.52.0)(typescript@5.2.2) @@ -137,7 +137,7 @@ importers: version: 1.22.0 vite: specifier: ^4.5.0 - version: 4.5.0(@types/node@20.8.9)(lightningcss@1.22.0) + version: 4.5.0(@types/node@20.8.10)(lightningcss@1.22.0) vite-plugin-checker: specifier: ^0.6.2 version: 0.6.2(eslint@8.52.0)(typescript@5.2.2)(vite@4.5.0) @@ -276,13 +276,13 @@ importers: version: 1.22.0 ts-node: specifier: ^10.9.1 - version: 10.9.1(@types/node@20.8.9)(typescript@5.2.2) + version: 10.9.1(@types/node@20.8.10)(typescript@5.2.2) uuid: specifier: ^9.0.1 version: 9.0.1 vite: specifier: ^4.5.0 - version: 4.5.0(@types/node@20.8.9)(lightningcss@1.22.0) + version: 4.5.0(@types/node@20.8.10)(lightningcss@1.22.0) vite-plugin-checker: specifier: ^0.6.2 version: 0.6.2(eslint@8.52.0)(typescript@5.2.2)(vite@4.5.0) @@ -339,8 +339,8 @@ importers: specifier: ^10.0.3 version: 10.0.3 '@types/node': - specifier: ^20.8.9 - version: 20.8.9 + specifier: ^20.8.10 + version: 20.8.10 chai: specifier: ^4.3.10 version: 4.3.10 @@ -349,7 +349,7 @@ importers: version: 10.2.0 ts-node: specifier: ^10.9.1 - version: 10.9.1(@types/node@20.8.9)(typescript@5.2.2) + version: 10.9.1(@types/node@20.8.10)(typescript@5.2.2) libs/keycloak-js: dependencies: @@ -365,25 +365,25 @@ importers: devDependencies: '@rollup/plugin-commonjs': specifier: ^25.0.7 - version: 25.0.7(rollup@4.1.5) + version: 25.0.7(rollup@4.2.0) '@rollup/plugin-inject': specifier: ^5.0.5 - version: 5.0.5(rollup@4.1.5) + version: 5.0.5(rollup@4.2.0) '@rollup/plugin-node-resolve': specifier: ^15.2.3 - version: 15.2.3(rollup@4.1.5) + version: 15.2.3(rollup@4.2.0) '@rollup/plugin-terser': specifier: ^0.4.4 - version: 0.4.4(rollup@4.1.5) + version: 0.4.4(rollup@4.2.0) '@rollup/plugin-typescript': specifier: ^11.1.5 - version: 11.1.5(rollup@4.1.5)(tslib@2.6.2)(typescript@5.2.2) + version: 11.1.5(rollup@4.2.0)(tslib@2.6.2)(typescript@5.2.2) es6-promise: specifier: ^4.2.8 version: 4.2.8 rollup: - specifier: ^4.1.5 - version: 4.1.5 + specifier: ^4.2.0 + version: 4.2.0 libs/keycloak-masthead: dependencies: @@ -414,16 +414,16 @@ importers: version: 3.4.1(vite@4.5.0) rollup-plugin-peer-deps-external: specifier: ^2.2.4 - version: 2.2.4(rollup@4.1.5) + version: 2.2.4(rollup@4.2.0) vite: specifier: ^4.5.0 - version: 4.5.0(@types/node@20.8.9)(lightningcss@1.22.0) + version: 4.5.0(@types/node@20.8.10)(lightningcss@1.22.0) vite-plugin-checker: specifier: ^0.6.2 version: 0.6.2(eslint@8.52.0)(typescript@5.2.2)(vite@4.5.0) vite-plugin-dts: specifier: ^3.6.3 - version: 3.6.3(@types/node@20.8.9)(rollup@4.1.5)(typescript@5.2.2)(vite@4.5.0) + version: 3.6.3(@types/node@20.8.10)(rollup@4.2.0)(typescript@5.2.2)(vite@4.5.0) libs/ui-shared: dependencies: @@ -454,16 +454,16 @@ importers: version: 3.4.1(vite@4.5.0) rollup-plugin-peer-deps-external: specifier: ^2.2.4 - version: 2.2.4(rollup@4.1.5) + version: 2.2.4(rollup@4.2.0) vite: specifier: ^4.5.0 - version: 4.5.0(@types/node@20.8.9)(lightningcss@1.22.0) + version: 4.5.0(@types/node@20.8.10)(lightningcss@1.22.0) vite-plugin-checker: specifier: ^0.6.2 version: 0.6.2(eslint@8.52.0)(typescript@5.2.2)(vite@4.5.0) vite-plugin-dts: specifier: ^3.6.3 - version: 3.6.3(@types/node@20.8.9)(rollup@4.1.5)(typescript@5.2.2)(vite@4.5.0) + version: 3.6.3(@types/node@20.8.10)(rollup@4.2.0)(typescript@5.2.2)(vite@4.5.0) vitest: specifier: ^0.34.6 version: 0.34.6 @@ -895,24 +895,24 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: true - /@microsoft/api-extractor-model@7.28.2(@types/node@20.8.9): + /@microsoft/api-extractor-model@7.28.2(@types/node@20.8.10): resolution: {integrity: sha512-vkojrM2fo3q4n4oPh4uUZdjJ2DxQ2+RnDQL/xhTWSRUNPF6P4QyrvY357HBxbnltKcYu+nNNolVqc6TIGQ73Ig==} dependencies: '@microsoft/tsdoc': 0.14.2 '@microsoft/tsdoc-config': 0.16.2 - '@rushstack/node-core-library': 3.61.0(@types/node@20.8.9) + '@rushstack/node-core-library': 3.61.0(@types/node@20.8.10) transitivePeerDependencies: - '@types/node' dev: true - /@microsoft/api-extractor@7.38.0(@types/node@20.8.9): + /@microsoft/api-extractor@7.38.0(@types/node@20.8.10): resolution: {integrity: sha512-e1LhZYnfw+JEebuY2bzhw0imDCl1nwjSThTrQqBXl40hrVo6xm3j/1EpUr89QyzgjqmAwek2ZkIVZbrhaR+cqg==} hasBin: true dependencies: - '@microsoft/api-extractor-model': 7.28.2(@types/node@20.8.9) + '@microsoft/api-extractor-model': 7.28.2(@types/node@20.8.10) '@microsoft/tsdoc': 0.14.2 '@microsoft/tsdoc-config': 0.16.2 - '@rushstack/node-core-library': 3.61.0(@types/node@20.8.9) + '@rushstack/node-core-library': 3.61.0(@types/node@20.8.10) '@rushstack/rig-package': 0.5.1 '@rushstack/ts-command-line': 4.16.1 colors: 1.2.5 @@ -1283,7 +1283,7 @@ packages: engines: {node: '>=14.0.0'} dev: false - /@rollup/plugin-commonjs@25.0.7(rollup@4.1.5): + /@rollup/plugin-commonjs@25.0.7(rollup@4.2.0): resolution: {integrity: sha512-nEvcR+LRjEjsaSsc4x3XZfCCvZIaSMenZu/OiwOKGN2UhQpAYI7ru7czFvyWbErlpoGjnSX3D5Ch5FcMA3kRWQ==} engines: {node: '>=14.0.0'} peerDependencies: @@ -1292,16 +1292,16 @@ packages: rollup: optional: true dependencies: - '@rollup/pluginutils': 5.0.4(rollup@4.1.5) + '@rollup/pluginutils': 5.0.4(rollup@4.2.0) commondir: 1.0.1 estree-walker: 2.0.2 glob: 8.1.0 is-reference: 1.2.1 magic-string: 0.30.3 - rollup: 4.1.5 + rollup: 4.2.0 dev: true - /@rollup/plugin-inject@5.0.5(rollup@4.1.5): + /@rollup/plugin-inject@5.0.5(rollup@4.2.0): resolution: {integrity: sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==} engines: {node: '>=14.0.0'} peerDependencies: @@ -1310,13 +1310,13 @@ packages: rollup: optional: true dependencies: - '@rollup/pluginutils': 5.0.4(rollup@4.1.5) + '@rollup/pluginutils': 5.0.4(rollup@4.2.0) estree-walker: 2.0.2 magic-string: 0.30.3 - rollup: 4.1.5 + rollup: 4.2.0 dev: true - /@rollup/plugin-node-resolve@15.2.3(rollup@4.1.5): + /@rollup/plugin-node-resolve@15.2.3(rollup@4.2.0): resolution: {integrity: sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==} engines: {node: '>=14.0.0'} peerDependencies: @@ -1325,16 +1325,16 @@ packages: rollup: optional: true dependencies: - '@rollup/pluginutils': 5.0.4(rollup@4.1.5) + '@rollup/pluginutils': 5.0.4(rollup@4.2.0) '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-builtin-module: 3.2.1 is-module: 1.0.0 resolve: 1.22.4 - rollup: 4.1.5 + rollup: 4.2.0 dev: true - /@rollup/plugin-terser@0.4.4(rollup@4.1.5): + /@rollup/plugin-terser@0.4.4(rollup@4.2.0): resolution: {integrity: sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==} engines: {node: '>=14.0.0'} peerDependencies: @@ -1343,13 +1343,13 @@ packages: rollup: optional: true dependencies: - rollup: 4.1.5 + rollup: 4.2.0 serialize-javascript: 6.0.1 smob: 1.4.0 terser: 5.19.2 dev: true - /@rollup/plugin-typescript@11.1.5(rollup@4.1.5)(tslib@2.6.2)(typescript@5.2.2): + /@rollup/plugin-typescript@11.1.5(rollup@4.2.0)(tslib@2.6.2)(typescript@5.2.2): resolution: {integrity: sha512-rnMHrGBB0IUEv69Q8/JGRD/n4/n6b3nfpufUu26axhUcboUzv/twfZU8fIBbTOphRAe0v8EyxzeDpKXqGHfyDA==} engines: {node: '>=14.0.0'} peerDependencies: @@ -1362,14 +1362,14 @@ packages: tslib: optional: true dependencies: - '@rollup/pluginutils': 5.0.4(rollup@4.1.5) + '@rollup/pluginutils': 5.0.4(rollup@4.2.0) resolve: 1.22.4 - rollup: 4.1.5 + rollup: 4.2.0 tslib: 2.6.2 typescript: 5.2.2 dev: true - /@rollup/pluginutils@5.0.4(rollup@4.1.5): + /@rollup/pluginutils@5.0.4(rollup@4.2.0): resolution: {integrity: sha512-0KJnIoRI8A+a1dqOYLxH8vBf8bphDmty5QvIm2hqm7oFCFYKCAZWWd2hXgMibaPsNDhI0AtpYfQZJG47pt/k4g==} engines: {node: '>=14.0.0'} peerDependencies: @@ -1381,10 +1381,10 @@ packages: '@types/estree': 1.0.1 estree-walker: 2.0.2 picomatch: 2.3.1 - rollup: 4.1.5 + rollup: 4.2.0 dev: true - /@rollup/pluginutils@5.0.5(rollup@4.1.5): + /@rollup/pluginutils@5.0.5(rollup@4.2.0): resolution: {integrity: sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==} engines: {node: '>=14.0.0'} peerDependencies: @@ -1396,106 +1396,106 @@ packages: '@types/estree': 1.0.1 estree-walker: 2.0.2 picomatch: 2.3.1 - rollup: 4.1.5 + rollup: 4.2.0 dev: true - /@rollup/rollup-android-arm-eabi@4.1.5: - resolution: {integrity: sha512-/fwx6GS8cIbM2rTNyLMxjSCOegHywOdXO+kN9yFy018iCULcKZCyA3xvzw4bxyKbYfdSxQgdhbsl0egNcxerQw==} + /@rollup/rollup-android-arm-eabi@4.2.0: + resolution: {integrity: sha512-8PlggAxGxavr+pkCNeV1TM2wTb2o+cUWDg9M1cm9nR27Dsn287uZtSLYXoQqQcmq+sYfF7lHfd3sWJJinH9GmA==} cpu: [arm] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-android-arm64@4.1.5: - resolution: {integrity: sha512-tmXh7dyEt+JEz/NgDJlB1UeL/1gFV0v8qYzUAU42WZH4lmUJ5rp6/HkR2qUNC5jCgYEwd8/EfbHKtGIEfS4CUg==} + /@rollup/rollup-android-arm64@4.2.0: + resolution: {integrity: sha512-+71T85hbMFrJI+zKQULNmSYBeIhru55PYoF/u75MyeN2FcxE4HSPw20319b+FcZ4lWx2Nx/Ql9tN+hoaD3GH/A==} cpu: [arm64] os: [android] requiresBuild: true dev: true optional: true - /@rollup/rollup-darwin-arm64@4.1.5: - resolution: {integrity: sha512-lTDmLxdEVhzI3KCesZUrNbl3icBvPrDv/85JasY5gh4P2eAuDFmM4uj9HC5DdH0anLC0fwJ+1Uzasr4qOXcjRQ==} + /@rollup/rollup-darwin-arm64@4.2.0: + resolution: {integrity: sha512-IIIQLuG43QIElT1JZqUP/zqIdiJl4t9U/boa0GZnQTw9m1X0k3mlBuysbgYXeloLT1RozdL7bgw4lpSaI8GOXw==} cpu: [arm64] os: [darwin] requiresBuild: true dev: true optional: true - /@rollup/rollup-darwin-x64@4.1.5: - resolution: {integrity: sha512-v6qEHZyjWnIgcc4oiy8AIeFsUJAx+Kg0sLj+RE7ICwv3u7YC/+bSClxAiBASRjMzqsq0Z+I/pfxj+OD8mjBYxg==} + /@rollup/rollup-darwin-x64@4.2.0: + resolution: {integrity: sha512-BXcXvnLaea1Xz900omrGJhxHFJfH9jZ0CpJuVsbjjhpniJ6qiLXz3xA8Lekaa4MuhFcJd4f0r+Ky1G4VFbYhWw==} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm-gnueabihf@4.1.5: - resolution: {integrity: sha512-WngCfwPEDUNbZR1FNO2TCROYUwJvRlbvPi3AS85bDUkkoRDBcjUIz42cuB1j4PKilmnZascL5xTMF/yU8YFayA==} + /@rollup/rollup-linux-arm-gnueabihf@4.2.0: + resolution: {integrity: sha512-f4K3MKw9Y4AKi4ANGnmPIglr+S+8tO858YrGVuqAHXxJdVghBmz9CPU9kDpOnGvT4g4vg5uNyIFpOOFvffXyMA==} cpu: [arm] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-gnu@4.1.5: - resolution: {integrity: sha512-Q2A/PEP/UTPTOBwgar3mmCaApahoezai/8e/7f4GCLV6XWCpnU4YwkQQtla7d7nUnc792Ps7g1G0WMovzIknrA==} + /@rollup/rollup-linux-arm64-gnu@4.2.0: + resolution: {integrity: sha512-bNsTYQBgp4H7w6cT7FZhesxpcUPahsSIy4NgdZjH1ZwEoZHxi4XKglj+CsSEkhsKi+x6toVvMylhjRKhEMYfnA==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-arm64-musl@4.1.5: - resolution: {integrity: sha512-84aBKNAVzTU/eG3tb2+kR4NGRAtm2YVW/KHwkGGDR4z1k4hyrDbuImsfs/6J74t6y0YLOe9HOSu7ejRjzUBGVQ==} + /@rollup/rollup-linux-arm64-musl@4.2.0: + resolution: {integrity: sha512-Jp1NxBJpGLuxRU2ihrQk4IZ+ia5nffobG6sOFUPW5PMYkF0kQtxEbeDuCa69Xif211vUOcxlOnf5IOEIpTEySA==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-gnu@4.1.5: - resolution: {integrity: sha512-mldtP9UEBurIq2+GYMdNeiqCLW1fdgf4KdkMR/QegAeXk4jFHkKQl7p0NITrKFVyVqzISGXH5gR6GSTBH4wszw==} + /@rollup/rollup-linux-x64-gnu@4.2.0: + resolution: {integrity: sha512-3p3iRtQmv2aXw+vtKNyZMLOQ+LSRsqArXjKAh2Oj9cqwfIRe7OXvdkOzWfZOIp1F/x5KJzVAxGxnniF4cMbnsQ==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-linux-x64-musl@4.1.5: - resolution: {integrity: sha512-36p+nMcSxjAEzfU47+by102HolUtf/EfgBAidocTKAofJMTqG5QD50qzaFLk4QO+z7Qvg4qd0wr99jGAwnKOig==} + /@rollup/rollup-linux-x64-musl@4.2.0: + resolution: {integrity: sha512-atih7IF/reUZe4LBLC5Izd44hth2tfDIG8LaPp4/cQXdHh9jabcZEvIeRPrpDq0i/Uu487Qu5gl5KwyAnWajnw==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-arm64-msvc@4.1.5: - resolution: {integrity: sha512-5oxhubo0A3J8aF/tG+6jHBg785HF8/88kl1YnfbDKmnqMxz/EFiAQDH9cq6lbnxofjn8tlq5KiTf0crJGOGThg==} + /@rollup/rollup-win32-arm64-msvc@4.2.0: + resolution: {integrity: sha512-vYxF3tKJeUE4ceYzpNe2p84RXk/fGK30I8frpRfv/MyPStej/mRlojztkN7Jtd1014HHVeq/tYaMBz/3IxkxZw==} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-ia32-msvc@4.1.5: - resolution: {integrity: sha512-uVQyBREKX9ErofL8KAZ4iVlqzSZOXSIG+BOLYuz5FD+Cg6jh1eLIeUa3Q4SgX0QaTRFeeAgSNqCC+8kZrZBpSw==} + /@rollup/rollup-win32-ia32-msvc@4.2.0: + resolution: {integrity: sha512-1LZJ6zpl93SaPQvas618bMFarVwufWTaczH4ESAbFcwiC4OtznA6Ym+hFPyIGaJaGEB8uMWWac0uXGPXOg5FGA==} cpu: [ia32] os: [win32] requiresBuild: true dev: true optional: true - /@rollup/rollup-win32-x64-msvc@4.1.5: - resolution: {integrity: sha512-FQ5qYqRJ2vUBSom3Fos8o/6UvAMOvlus4+HGCAifH1TagbbwVnVVe0o01J1V52EWnQ8kmfpJDJ0FMrfM5yzcSA==} + /@rollup/rollup-win32-x64-msvc@4.2.0: + resolution: {integrity: sha512-dgQfFdHCNg08nM5zBmqxqc9vrm0DVzhWotpavbPa0j4//MAOKZEB75yGAfzQE9fUJ+4pvM1239Y4IhL8f6sSog==} cpu: [x64] os: [win32] requiresBuild: true dev: true optional: true - /@rushstack/node-core-library@3.61.0(@types/node@20.8.9): + /@rushstack/node-core-library@3.61.0(@types/node@20.8.10): resolution: {integrity: sha512-tdOjdErme+/YOu4gPed3sFS72GhtWCgNV9oDsHDnoLY5oDfwjKUc9Z+JOZZ37uAxcm/OCahDHfuu2ugqrfWAVQ==} peerDependencies: '@types/node': '*' @@ -1503,7 +1503,7 @@ packages: '@types/node': optional: true dependencies: - '@types/node': 20.8.9 + '@types/node': 20.8.10 colors: 1.2.5 fs-extra: 7.0.1 import-lazy: 4.0.0 @@ -1965,7 +1965,7 @@ packages: /@types/gunzip-maybe@1.4.1: resolution: {integrity: sha512-FAxzVI4EbR3bsvsvtNsJ6kqaxQftN1wD/F6A+51m85oo1oRoZn15rQwbedJY2GmzyUKl6EjG9wKkgShwcTQgTA==} dependencies: - '@types/node': 20.8.8 + '@types/node': 20.8.9 dev: false /@types/json-schema@7.0.12: @@ -1994,10 +1994,10 @@ packages: resolution: {integrity: sha512-2yrWpBk32tvV/JAd3HNHWuZn/VDN1P+72hWirHnvsvTGSqbANi+kSeuQR9yAHnbvaBvHDsoTdXV0Fe+iRtHLKA==} dev: true - /@types/node@20.8.8: - resolution: {integrity: sha512-YRsdVxq6OaLfmR9Hy816IMp33xOBjfyOgUd77ehqg96CFywxAPbDbXvAsuN2KVg2HOT8Eh6uAfU+l4WffwPVrQ==} + /@types/node@20.8.10: + resolution: {integrity: sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==} dependencies: - undici-types: 5.25.3 + undici-types: 5.26.5 /@types/node@20.8.9: resolution: {integrity: sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==} @@ -2042,14 +2042,14 @@ packages: /@types/tar-fs@2.0.3: resolution: {integrity: sha512-ADS99HAnztB8MD+LSOdzDrDLcSe5oBIg+SUQwwsgnsOgZobWoSqYmg9ZJWdvLppoKV8R8kZinX6Om+LlsNuIlQ==} dependencies: - '@types/node': 20.8.8 + '@types/node': 20.8.9 '@types/tar-stream': 2.2.2 dev: false /@types/tar-stream@2.2.2: resolution: {integrity: sha512-1AX+Yt3icFuU6kxwmPakaiGrJUwG44MpuiqPg4dSolRFk6jmvs4b3IbUol9wKDLIgU76gevn3EwE8y/DkSJCZQ==} dependencies: - '@types/node': 20.8.9 + '@types/node': 20.8.10 dev: false /@types/uuid@9.0.6: @@ -2060,7 +2060,7 @@ packages: resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==} requiresBuild: true dependencies: - '@types/node': 20.8.9 + '@types/node': 20.8.10 dev: true optional: true @@ -2205,7 +2205,7 @@ packages: vite: ^4 dependencies: '@swc/core': 1.3.95 - vite: 4.5.0(@types/node@20.8.9)(lightningcss@1.22.0) + vite: 4.5.0(@types/node@20.8.10)(lightningcss@1.22.0) transitivePeerDependencies: - '@swc/helpers' dev: true @@ -6212,12 +6212,12 @@ packages: glob: 7.2.3 dev: true - /rollup-plugin-peer-deps-external@2.2.4(rollup@4.1.5): + /rollup-plugin-peer-deps-external@2.2.4(rollup@4.2.0): resolution: {integrity: sha512-AWdukIM1+k5JDdAqV/Cxd+nejvno2FVLVeZ74NKggm3Q5s9cbbcOgUPGdbxPi4BXu7xGaZ8HG12F+thImYu/0g==} peerDependencies: rollup: '*' dependencies: - rollup: 4.1.5 + rollup: 4.2.0 dev: true /rollup@3.29.4: @@ -6228,23 +6228,23 @@ packages: fsevents: 2.3.3 dev: true - /rollup@4.1.5: - resolution: {integrity: sha512-AEw14/q4NHYQkQlngoSae2yi7hDBeT9w84aEzdgCr39+2RL+iTG84lGTkgC1Wp5igtquN64cNzuzZKVz+U6jOg==} + /rollup@4.2.0: + resolution: {integrity: sha512-deaMa9Z+jPVeBD2dKXv+h7EbdKte9++V2potc/ADqvVgEr6DEJ3ia9u0joarjC2lX/ubaCRYz3QVx0TzuVqAJA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.1.5 - '@rollup/rollup-android-arm64': 4.1.5 - '@rollup/rollup-darwin-arm64': 4.1.5 - '@rollup/rollup-darwin-x64': 4.1.5 - '@rollup/rollup-linux-arm-gnueabihf': 4.1.5 - '@rollup/rollup-linux-arm64-gnu': 4.1.5 - '@rollup/rollup-linux-arm64-musl': 4.1.5 - '@rollup/rollup-linux-x64-gnu': 4.1.5 - '@rollup/rollup-linux-x64-musl': 4.1.5 - '@rollup/rollup-win32-arm64-msvc': 4.1.5 - '@rollup/rollup-win32-ia32-msvc': 4.1.5 - '@rollup/rollup-win32-x64-msvc': 4.1.5 + '@rollup/rollup-android-arm-eabi': 4.2.0 + '@rollup/rollup-android-arm64': 4.2.0 + '@rollup/rollup-darwin-arm64': 4.2.0 + '@rollup/rollup-darwin-x64': 4.2.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.2.0 + '@rollup/rollup-linux-arm64-gnu': 4.2.0 + '@rollup/rollup-linux-arm64-musl': 4.2.0 + '@rollup/rollup-linux-x64-gnu': 4.2.0 + '@rollup/rollup-linux-x64-musl': 4.2.0 + '@rollup/rollup-win32-arm64-msvc': 4.2.0 + '@rollup/rollup-win32-ia32-msvc': 4.2.0 + '@rollup/rollup-win32-x64-msvc': 4.2.0 fsevents: 2.3.3 dev: true @@ -6754,7 +6754,7 @@ packages: typescript: 5.2.2 dev: true - /ts-node@10.9.1(@types/node@20.8.9)(typescript@5.2.2): + /ts-node@10.9.1(@types/node@20.8.10)(typescript@5.2.2): resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true peerDependencies: @@ -6773,7 +6773,7 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.8.9 + '@types/node': 20.8.10 acorn: 8.10.0 acorn-walk: 8.2.0 arg: 4.1.3 @@ -6897,9 +6897,6 @@ packages: which-boxed-primitive: 1.0.2 dev: true - /undici-types@5.25.3: - resolution: {integrity: sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==} - /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} @@ -7016,7 +7013,7 @@ packages: extsprintf: 1.4.1 dev: true - /vite-node@0.34.6(@types/node@20.8.8)(lightningcss@1.22.0): + /vite-node@0.34.6(@types/node@20.8.10): resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==} engines: {node: '>=v14.18.0'} hasBin: true @@ -7026,7 +7023,7 @@ packages: mlly: 1.4.1 pathe: 1.1.1 picocolors: 1.0.0 - vite: 4.5.0(@types/node@20.8.8)(lightningcss@1.22.0) + vite: 4.5.0(@types/node@20.8.10)(lightningcss@1.22.0) transitivePeerDependencies: - '@types/node' - less @@ -7038,7 +7035,7 @@ packages: - terser dev: true - /vite-node@0.34.6(@types/node@20.8.9): + /vite-node@0.34.6(@types/node@20.8.9)(lightningcss@1.22.0): resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==} engines: {node: '>=v14.18.0'} hasBin: true @@ -7106,14 +7103,14 @@ packages: strip-ansi: 6.0.1 tiny-invariant: 1.3.1 typescript: 5.2.2 - vite: 4.5.0(@types/node@20.8.9)(lightningcss@1.22.0) + vite: 4.5.0(@types/node@20.8.10)(lightningcss@1.22.0) vscode-languageclient: 7.0.0 vscode-languageserver: 7.0.0 vscode-languageserver-textdocument: 1.0.8 vscode-uri: 3.0.7 dev: true - /vite-plugin-dts@3.6.3(@types/node@20.8.9)(rollup@4.1.5)(typescript@5.2.2)(vite@4.5.0): + /vite-plugin-dts@3.6.3(@types/node@20.8.10)(rollup@4.2.0)(typescript@5.2.2)(vite@4.5.0): resolution: {integrity: sha512-NyRvgobl15rYj65coi/gH7UAEH+CpSjh539DbGb40DfOTZSvDLNYTzc8CK4460W+LqXuMK7+U3JAxRB3ksrNPw==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -7123,13 +7120,13 @@ packages: vite: optional: true dependencies: - '@microsoft/api-extractor': 7.38.0(@types/node@20.8.9) - '@rollup/pluginutils': 5.0.5(rollup@4.1.5) + '@microsoft/api-extractor': 7.38.0(@types/node@20.8.10) + '@rollup/pluginutils': 5.0.5(rollup@4.2.0) '@vue/language-core': 1.8.20(typescript@5.2.2) debug: 4.3.4(supports-color@8.1.1) kolorist: 1.8.0 typescript: 5.2.2 - vite: 4.5.0(@types/node@20.8.9)(lightningcss@1.22.0) + vite: 4.5.0(@types/node@20.8.10)(lightningcss@1.22.0) vue-tsc: 1.8.20(typescript@5.2.2) transitivePeerDependencies: - '@types/node' @@ -7137,7 +7134,7 @@ packages: - supports-color dev: true - /vite@4.5.0(@types/node@20.8.8)(lightningcss@1.22.0): + /vite@4.5.0(@types/node@20.8.10)(lightningcss@1.22.0): resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true @@ -7165,7 +7162,7 @@ packages: terser: optional: true dependencies: - '@types/node': 20.8.8 + '@types/node': 20.8.10 esbuild: 0.18.20 lightningcss: 1.22.0 postcss: 8.4.28 @@ -7244,7 +7241,7 @@ packages: dependencies: '@types/chai': 4.3.9 '@types/chai-subset': 1.3.3 - '@types/node': 20.8.9 + '@types/node': 20.8.10 '@vitest/expect': 0.34.6 '@vitest/runner': 0.34.6 '@vitest/snapshot': 0.34.6 @@ -7263,8 +7260,8 @@ packages: strip-literal: 1.3.0 tinybench: 2.5.0 tinypool: 0.7.0 - vite: 4.5.0(@types/node@20.8.9)(lightningcss@1.22.0) - vite-node: 0.34.6(@types/node@20.8.9) + vite: 4.5.0(@types/node@20.8.10)(lightningcss@1.22.0) + vite-node: 0.34.6(@types/node@20.8.10) why-is-node-running: 2.2.2 transitivePeerDependencies: - less @@ -7309,7 +7306,7 @@ packages: dependencies: '@types/chai': 4.3.8 '@types/chai-subset': 1.3.3 - '@types/node': 20.8.8 + '@types/node': 20.8.9 '@vitest/expect': 0.34.6 '@vitest/runner': 0.34.6 '@vitest/snapshot': 0.34.6 @@ -7329,8 +7326,8 @@ packages: strip-literal: 1.3.0 tinybench: 2.5.0 tinypool: 0.7.0 - vite: 4.5.0(@types/node@20.8.8)(lightningcss@1.22.0) - vite-node: 0.34.6(@types/node@20.8.8)(lightningcss@1.22.0) + vite: 4.5.0(@types/node@20.8.9)(lightningcss@1.22.0) + vite-node: 0.34.6(@types/node@20.8.9)(lightningcss@1.22.0) why-is-node-running: 2.2.2 transitivePeerDependencies: - less diff --git a/model/jpa/src/main/java/org/keycloak/events/jpa/AdminEventEntity.java b/model/jpa/src/main/java/org/keycloak/events/jpa/AdminEventEntity.java index f5be1d5d029f..76655a624658 100644 --- a/model/jpa/src/main/java/org/keycloak/events/jpa/AdminEventEntity.java +++ b/model/jpa/src/main/java/org/keycloak/events/jpa/AdminEventEntity.java @@ -60,7 +60,7 @@ public class AdminEventEntity { @Column(name="RESOURCE_PATH") private String resourcePath; - @Column(name="REPRESENTATION", length = 25500) + @Column(name="REPRESENTATION") private String representation; @Column(name="ERROR") diff --git a/model/jpa/src/main/java/org/keycloak/events/jpa/EventEntity.java b/model/jpa/src/main/java/org/keycloak/events/jpa/EventEntity.java index 1e26a486dac3..38cab319a097 100644 --- a/model/jpa/src/main/java/org/keycloak/events/jpa/EventEntity.java +++ b/model/jpa/src/main/java/org/keycloak/events/jpa/EventEntity.java @@ -57,9 +57,13 @@ public class EventEntity { @Column(name="ERROR") private String error; + // This is the legacy field which is kept here to be able to read old events without the need to migrate them @Column(name="DETAILS_JSON", length = 2550) private String detailsJson; + @Column(name="DETAILS_JSON_LONG_VALUE") + private String detailsJsonLongValue; + public String getId() { return id; } @@ -133,11 +137,11 @@ public void setError(String error) { } public String getDetailsJson() { - return detailsJson; + return detailsJsonLongValue != null ? detailsJsonLongValue : detailsJson; } public void setDetailsJson(String detailsJson) { - this.detailsJson = detailsJson; + this.detailsJsonLongValue = detailsJson; } } diff --git a/model/jpa/src/main/java/org/keycloak/events/jpa/JpaEventStoreProvider.java b/model/jpa/src/main/java/org/keycloak/events/jpa/JpaEventStoreProvider.java index e1a1690f7ddf..28e3ff6af906 100755 --- a/model/jpa/src/main/java/org/keycloak/events/jpa/JpaEventStoreProvider.java +++ b/model/jpa/src/main/java/org/keycloak/events/jpa/JpaEventStoreProvider.java @@ -39,7 +39,6 @@ import jakarta.persistence.EntityManager; import jakarta.persistence.TypedQuery; import java.io.IOException; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; @@ -57,14 +56,10 @@ public class JpaEventStoreProvider implements EventStoreProvider { private final KeycloakSession session; private final EntityManager em; - private final int maxDetailLength; - private final int maxFieldLength; - public JpaEventStoreProvider(KeycloakSession session, EntityManager em, int maxDetailLength, int maxFieldLength) { + public JpaEventStoreProvider(KeycloakSession session, EntityManager em) { this.session = session; this.em = em; - this.maxDetailLength = maxDetailLength; - this.maxFieldLength = maxFieldLength; } @Override @@ -165,40 +160,13 @@ private EventEntity convertEvent(Event event) { eventEntity.setIpAddress(event.getIpAddress()); eventEntity.setError(event.getError()); try { - if (maxDetailLength > 0 && event.getDetails() != null) { - Map result = new HashMap<>(event.getDetails()); - result.entrySet().forEach(t -> t.setValue(trimToMaxDetailLength(t.getValue()))); - - eventEntity.setDetailsJson(trimToMaxFieldLength(mapper.writeValueAsString(result))); - } else { - eventEntity.setDetailsJson(mapper.writeValueAsString(event.getDetails())); - } + eventEntity.setDetailsJson(mapper.writeValueAsString(event.getDetails())); } catch (IOException ex) { logger.error("Failed to write log details", ex); } return eventEntity; } - private String trimToMaxDetailLength(String detail) { - if (detail != null && detail.length() > maxDetailLength) { - logger.warnf("Detail '%s' will be truncated.", detail); - // (maxDetailLength - 3) takes "..." into account - return detail.substring(0, maxDetailLength - 3).concat("..."); - } else { - return detail; - } - } - - private String trimToMaxFieldLength(String field) { - if (maxFieldLength > 0 && field != null && field.length() > maxFieldLength) { - logger.warnf("Field '%s' will be truncated.", field); - // (maxFieldLength - 3) takes "..." into account - return field.substring(0, maxFieldLength - 3).concat("..."); - } else { - return field; - } - } - static Event convertEvent(EventEntity eventEntity) { Event event = new Event(); event.setId(eventEntity.getId() == null ? UUID.randomUUID().toString() : eventEntity.getId()); @@ -234,8 +202,8 @@ private AdminEventEntity convertAdminEvent(AdminEvent adminEvent, boolean includ adminEventEntity.setResourcePath(adminEvent.getResourcePath()); adminEventEntity.setError(adminEvent.getError()); - if(includeRepresentation) { - adminEventEntity.setRepresentation(trimToMaxFieldLength(adminEvent.getRepresentation())); + if (includeRepresentation) { + adminEventEntity.setRepresentation(adminEvent.getRepresentation()); } return adminEventEntity; } @@ -255,7 +223,7 @@ static AdminEvent convertAdminEvent(AdminEventEntity adminEventEntity) { adminEvent.setResourcePath(adminEventEntity.getResourcePath()); adminEvent.setError(adminEventEntity.getError()); - if(adminEventEntity.getRepresentation() != null) { + if (adminEventEntity.getRepresentation() != null) { adminEvent.setRepresentation(adminEventEntity.getRepresentation()); } return adminEvent; diff --git a/model/jpa/src/main/java/org/keycloak/events/jpa/JpaEventStoreProviderFactory.java b/model/jpa/src/main/java/org/keycloak/events/jpa/JpaEventStoreProviderFactory.java index 49506c7b00e3..24868b3037c4 100755 --- a/model/jpa/src/main/java/org/keycloak/events/jpa/JpaEventStoreProviderFactory.java +++ b/model/jpa/src/main/java/org/keycloak/events/jpa/JpaEventStoreProviderFactory.java @@ -32,21 +32,15 @@ public class JpaEventStoreProviderFactory implements EventStoreProviderFactory, InvalidationHandler { public static final String ID = "jpa"; - private int maxDetailLength; - private int maxFieldLength; @Override public EventStoreProvider create(KeycloakSession session) { JpaConnectionProvider connection = session.getProvider(JpaConnectionProvider.class); - return new JpaEventStoreProvider(session, connection.getEntityManager(), maxDetailLength, maxFieldLength); + return new JpaEventStoreProvider(session, connection.getEntityManager()); } @Override public void init(Config.Scope config) { - maxDetailLength = config.getInt("max-detail-length", -1); - maxFieldLength = config.getInt("max-field-length", -1); - if (maxDetailLength != -1 && maxDetailLength < 3) throw new IllegalArgumentException("max-detail-length cannot be less that 3."); - if (maxFieldLength != -1 && maxFieldLength < 3) throw new IllegalArgumentException("max-field-length cannot be less that 3."); } @Override diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java index cff752cce093..174290a40505 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java @@ -35,7 +35,18 @@ import jakarta.persistence.EntityManager; import jakarta.persistence.LockModeType; import jakarta.persistence.TypedQuery; -import java.util.*; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.Collections; +import java.util.Collection; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Arrays; +import java.util.Optional; import java.util.function.Predicate; import java.util.stream.Stream; diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-23.0.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-23.0.0.xml index 4f35cc440dc6..4c499231a40f 100644 --- a/model/jpa/src/main/resources/META-INF/jpa-changelog-23.0.0.xml +++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-23.0.0.xml @@ -30,4 +30,11 @@ + + + + + + + diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/integration/KeycloakDeploymentTest.java b/operator/src/test/java/org/keycloak/operator/testsuite/integration/KeycloakDeploymentTest.java index 23ba52cb33a0..3c11de21afb3 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/integration/KeycloakDeploymentTest.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/integration/KeycloakDeploymentTest.java @@ -107,7 +107,7 @@ public void testKeycloakDeploymentBeforeSecret() { Resource stsResource = k8sclient.resources(StatefulSet.class).withName(deploymentName); Resource keycloakResource = k8sclient.resources(Keycloak.class).withName(deploymentName); // expect no errors and not ready, which means we'll keep reconciling - Awaitility.await().untilAsserted(() -> { + Awaitility.await().ignoreExceptions().untilAsserted(() -> { assertThat(stsResource.get()).isNotNull(); Keycloak keycloak = keycloakResource.get(); CRAssert.assertKeycloakStatusCondition(keycloak, KeycloakStatusCondition.HAS_ERRORS, false); diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/FeaturesDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/FeaturesDistTest.java index f3108366fcd4..5ab946220a43 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/FeaturesDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/FeaturesDistTest.java @@ -28,7 +28,7 @@ @LegacyStore public class FeaturesDistTest { - private static final String PREVIEW_FEATURES_EXPECTED_LOG = "Preview features enabled: account3, admin-fine-grained-authz, client-secret-rotation, declarative-user-profile, dpop, recovery-codes, scripts, token-exchange, transient-users, update-email"; + private static final String PREVIEW_FEATURES_EXPECTED_LOG = "Preview features enabled: account3, admin-fine-grained-authz, client-secret-rotation, declarative-user-profile, dpop, recovery-codes, scripts, token-exchange, update-email"; @Test public void testEnableOnBuild(KeycloakDistribution dist) { diff --git a/server-spi-private/src/main/java/org/keycloak/keystore/KeyStoreProvider.java b/server-spi-private/src/main/java/org/keycloak/keystore/KeyStoreProvider.java new file mode 100644 index 000000000000..c18a1125824e --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/keystore/KeyStoreProvider.java @@ -0,0 +1,50 @@ +/* + * Copyright 2022 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.keystore; + +import java.security.KeyStore; + +import org.keycloak.provider.Provider; + +/** + * KeyStore provider provides credentials for clients and servers. + */ +public interface KeyStoreProvider extends Provider { + + public static final String LDAP_CLIENT_KEYSTORE = "ldap-client-keystore"; + public static final String HTTPS_SERVER_KEYSTORE = "https-server-keystore"; + + /** + * Loads KeyStore of given identifier. + * + * @param keyStoreIdentifier Identifier of the wanted KeyStore, such as LDAP_CLIENT_KEYSTORE. + * @return KeyStore. + */ + KeyStore loadKeyStore(String keyStoreIdentifier); + + /** + * Loads KeyStore of given identifier and returns a KeyStore.Builder. + * Builder encapsulates both KeyStore and KeyEntry password(s). + * + * @param keyStoreIdentifier Identifier of the wanted KeyStore, such as LDAP_CLIENT_KEYSTORE. + * @return Builder for KeyStore. + */ + + KeyStore.Builder loadKeyStoreBuilder(String keyStoreIdentifier); + +} diff --git a/server-spi-private/src/main/java/org/keycloak/keystore/KeyStoreProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/keystore/KeyStoreProviderFactory.java new file mode 100644 index 000000000000..bb96eb83ae19 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/keystore/KeyStoreProviderFactory.java @@ -0,0 +1,23 @@ +/* + * Copyright 2022 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.keystore; + +import org.keycloak.provider.ProviderFactory; + +public interface KeyStoreProviderFactory extends ProviderFactory { +} diff --git a/server-spi-private/src/main/java/org/keycloak/keystore/KeyStoreSpi.java b/server-spi-private/src/main/java/org/keycloak/keystore/KeyStoreSpi.java new file mode 100644 index 000000000000..c89f8cb0728c --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/keystore/KeyStoreSpi.java @@ -0,0 +1,46 @@ +/* + * Copyright 2022 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.keystore; + +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.provider.Spi; + +public class KeyStoreSpi implements Spi { + + @Override + public boolean isInternal() { + return true; + } + + @Override + public String getName() { + return "keystore"; + } + + @Override + public Class getProviderClass() { + return KeyStoreProvider.class; + } + + @Override + public Class getProviderFactoryClass() { + return KeyStoreProviderFactory.class; + } + +} diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java b/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java index 5477514a5a29..27bb1d16de16 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java @@ -24,7 +24,12 @@ import org.keycloak.models.RequiredCredentialModel; import org.keycloak.representations.idm.IdentityProviderRepresentation; -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.LinkedList; + /** * @author Bill Burke diff --git a/server-spi-private/src/main/java/org/keycloak/validate/BuiltinValidators.java b/server-spi-private/src/main/java/org/keycloak/validate/BuiltinValidators.java new file mode 100644 index 000000000000..7b80aa100370 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/validate/BuiltinValidators.java @@ -0,0 +1,82 @@ +/* + * 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.validate; + +import org.keycloak.validate.validators.DoubleValidator; +import org.keycloak.validate.validators.EmailValidator; +import org.keycloak.validate.validators.IntegerValidator; +import org.keycloak.validate.validators.LengthValidator; +import org.keycloak.validate.validators.LocalDateValidator; +import org.keycloak.validate.validators.NotBlankValidator; +import org.keycloak.validate.validators.NotEmptyValidator; +import org.keycloak.validate.validators.OptionsValidator; +import org.keycloak.validate.validators.PatternValidator; +import org.keycloak.validate.validators.UriValidator; +import org.keycloak.validate.validators.ValidatorConfigValidator; + +/** + * @author Marek Posolda + */ +public class BuiltinValidators { + + public static NotBlankValidator notBlankValidator() { + return NotBlankValidator.INSTANCE; + } + + public static NotEmptyValidator notEmptyValidator() { + return NotEmptyValidator.INSTANCE; + } + + public static LengthValidator lengthValidator() { + return LengthValidator.INSTANCE; + } + + public static UriValidator uriValidator() { + return UriValidator.INSTANCE; + } + + public static EmailValidator emailValidator() { + return EmailValidator.INSTANCE; + } + + public static PatternValidator patternValidator() { + return PatternValidator.INSTANCE; + } + + public static DoubleValidator doubleValidator() { + return DoubleValidator.INSTANCE; + } + + public static IntegerValidator integerValidator() { + return IntegerValidator.INSTANCE; + } + + public static LocalDateValidator dateValidator() { + return LocalDateValidator.INSTANCE; + } + + public static OptionsValidator optionsValidator() { + return OptionsValidator.INSTANCE; + } + + public static ValidatorConfigValidator validatorConfigValidator() { + return ValidatorConfigValidator.INSTANCE; + } +} diff --git a/server-spi-private/src/main/java/org/keycloak/validate/Validators.java b/server-spi-private/src/main/java/org/keycloak/validate/Validators.java deleted file mode 100644 index 2311a779807c..000000000000 --- a/server-spi-private/src/main/java/org/keycloak/validate/Validators.java +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Copyright 2021 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.keycloak.validate; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.validate.validators.LocalDateValidator; -import org.keycloak.validate.validators.EmailValidator; -import org.keycloak.validate.validators.IntegerValidator; -import org.keycloak.validate.validators.LengthValidator; -import org.keycloak.validate.validators.NotBlankValidator; -import org.keycloak.validate.validators.NotEmptyValidator; -import org.keycloak.validate.validators.OptionsValidator; -import org.keycloak.validate.validators.DoubleValidator; -import org.keycloak.validate.validators.PatternValidator; -import org.keycloak.validate.validators.UriValidator; -import org.keycloak.validate.validators.ValidatorConfigValidator; - -/** - * Facade for Validation functions with support for {@link Validator} implementation lookup by id. - */ -public class Validators { - - /** - * Holds a mapping of internal {@link SimpleValidator} to allow look-up via provider id. - */ - private static final Map INTERNAL_VALIDATORS; - - static { - List list = Arrays.asList( - LengthValidator.INSTANCE, - NotEmptyValidator.INSTANCE, - UriValidator.INSTANCE, - EmailValidator.INSTANCE, - NotBlankValidator.INSTANCE, - PatternValidator.INSTANCE, - DoubleValidator.INSTANCE, - IntegerValidator.INSTANCE, - ValidatorConfigValidator.INSTANCE, - OptionsValidator.INSTANCE - ); - - INTERNAL_VALIDATORS = list.stream().collect(Collectors.toMap(SimpleValidator::getId, v -> v)); - } - - /** - * Holds the {@link KeycloakSession}. - */ - private final KeycloakSession session; - - /** - * Creates a new {@link Validators} instance with the given {@link KeycloakSession}. - * - * @param session - */ - public Validators(KeycloakSession session) { - this.session = session; - } - - /** - * Look-up for a built-in or registered {@link Validator} with the given provider {@code id}. - * - * @param id - * @return - * @see #validator(KeycloakSession, String) - */ - public Validator validator(String id) { - return validator(session, id); - } - - /** - * Look-up for a built-in or registered {@link ValidatorFactory} with the given provider {@code id}. - * - * @param id - * @return - * @see #validatorFactory(KeycloakSession, String) - */ - public ValidatorFactory validatorFactory(String id) { - return validatorFactory(session, id); - } - - /** - * Validates the {@link ValidatorConfig} of {@link Validator} referenced by the given provider {@code id}. - * - * @param id - * @param config - * @return - * @see #validateConfig(KeycloakSession, String, ValidatorConfig) - */ - public ValidationResult validateConfig(String id, ValidatorConfig config) { - return validateConfig(session, id, config); - } - - /* static import friendly accessor methods for built-in validators */ - - public static Validator getInternalValidatorById(String id) { - return INTERNAL_VALIDATORS.get(id); - } - - public static ValidatorFactory getInternalValidatorFactoryById(String id) { - return INTERNAL_VALIDATORS.get(id); - } - - public static Map getInternalValidators() { - return Collections.unmodifiableMap(INTERNAL_VALIDATORS); - } - - public static NotBlankValidator notBlankValidator() { - return NotBlankValidator.INSTANCE; - } - - public static NotEmptyValidator notEmptyValidator() { - return NotEmptyValidator.INSTANCE; - } - - public static LengthValidator lengthValidator() { - return LengthValidator.INSTANCE; - } - - public static UriValidator uriValidator() { - return UriValidator.INSTANCE; - } - - public static EmailValidator emailValidator() { - return EmailValidator.INSTANCE; - } - - public static PatternValidator patternValidator() { - return PatternValidator.INSTANCE; - } - - public static DoubleValidator doubleValidator() { - return DoubleValidator.INSTANCE; - } - - public static IntegerValidator integerValidator() { - return IntegerValidator.INSTANCE; - } - - public static LocalDateValidator dateValidator() { - return LocalDateValidator.INSTANCE; - } - - public static OptionsValidator optionsValidator() { - return OptionsValidator.INSTANCE; - } - - public static ValidatorConfigValidator validatorConfigValidator() { - return ValidatorConfigValidator.INSTANCE; - } - - /** - * Look-up up for a built-in or registered {@link Validator} with the given validatorId. - * - * @param session the {@link KeycloakSession} - * @param id the id of the validator - * @return the {@link Validator} or {@literal null} - */ - public static Validator validator(KeycloakSession session, String id) { - - // Fast-path for internal Validators - Validator validator = getInternalValidatorById(id); - if (validator != null) { - return validator; - } - - if (session == null) { - return null; - } - - // Lookup validator in registry - return session.getProvider(Validator.class, id); - } - - /** - * Look-up for a built-in or registered {@link ValidatorFactory} with the given validatorId. - *

- * This is intended for users who want to dynamically create new {@link Validator} instances, validate - * {@link ValidatorConfig} configurations or create default configurations for a {@link Validator}. - * - * @param session the {@link KeycloakSession} - * @param id the id of the validator - * @return the {@link Validator} or {@literal null} - */ - public static ValidatorFactory validatorFactory(KeycloakSession session, String id) { - - // Fast-path for internal Validators - ValidatorFactory factory = getInternalValidatorFactoryById(id); - if (factory != null) { - return factory; - } - - if (session == null) { - return null; - } - - // Lookup factory in registry - KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory(); - return (ValidatorFactory) sessionFactory.getProviderFactory(Validator.class, id); - } - - /** - * Validates the {@link ValidatorConfig} of {@link Validator} referenced by the given provider {@code id}. - * - * @param session - * @param id of the validator - * @param config to be validated - * @return - */ - public static ValidationResult validateConfig(KeycloakSession session, String id, ValidatorConfig config) { - - ValidatorFactory validatorFactory = validatorFactory(session, id); - if (validatorFactory != null) { - return validatorFactory.validateConfig(session, config); - } - - // We could not find a ValidationFactory to validate that config, so we assume the config is valid. - return ValidationResult.OK; - } -} diff --git a/server-spi-private/src/main/java/org/keycloak/validate/validators/ValidatorConfigValidator.java b/server-spi-private/src/main/java/org/keycloak/validate/validators/ValidatorConfigValidator.java index 1eb3144b6b48..a49631834719 100644 --- a/server-spi-private/src/main/java/org/keycloak/validate/validators/ValidatorConfigValidator.java +++ b/server-spi-private/src/main/java/org/keycloak/validate/validators/ValidatorConfigValidator.java @@ -57,7 +57,7 @@ public class ValidatorConfigValidator implements SimpleValidator { public static final ValidatorConfigValidator INSTANCE = new ValidatorConfigValidator(); - private ValidatorConfigValidator() { + public ValidatorConfigValidator() { } @Override 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 6c8d5d862e5e..79b2e597d4c4 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 @@ -52,6 +52,7 @@ org.keycloak.email.EmailSenderSpi org.keycloak.email.EmailTemplateSpi org.keycloak.executors.ExecutorsSpi org.keycloak.theme.ThemeSpi +org.keycloak.keystore.KeyStoreSpi org.keycloak.truststore.TruststoreSpi org.keycloak.connections.httpclient.HttpClientSpi org.keycloak.authentication.AuthenticatorSpi diff --git a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.validate.ValidatorFactory b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.validate.ValidatorFactory index 5694637d6871..48378941f57d 100644 --- a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.validate.ValidatorFactory +++ b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.validate.ValidatorFactory @@ -7,4 +7,4 @@ org.keycloak.validate.validators.PatternValidator org.keycloak.validate.validators.DoubleValidator org.keycloak.validate.validators.IntegerValidator org.keycloak.validate.validators.LocalDateValidator -org.keycloak.validate.validators.OptionsValidator \ No newline at end of file +org.keycloak.validate.validators.OptionsValidator diff --git a/server-spi-private/src/main/java/org/keycloak/userprofile/AttributeContext.java b/server-spi/src/main/java/org/keycloak/userprofile/AttributeContext.java similarity index 100% rename from server-spi-private/src/main/java/org/keycloak/userprofile/AttributeContext.java rename to server-spi/src/main/java/org/keycloak/userprofile/AttributeContext.java diff --git a/server-spi-private/src/main/java/org/keycloak/userprofile/AttributeGroupMetadata.java b/server-spi/src/main/java/org/keycloak/userprofile/AttributeGroupMetadata.java similarity index 100% rename from server-spi-private/src/main/java/org/keycloak/userprofile/AttributeGroupMetadata.java rename to server-spi/src/main/java/org/keycloak/userprofile/AttributeGroupMetadata.java diff --git a/server-spi-private/src/main/java/org/keycloak/userprofile/AttributeMetadata.java b/server-spi/src/main/java/org/keycloak/userprofile/AttributeMetadata.java similarity index 100% rename from server-spi-private/src/main/java/org/keycloak/userprofile/AttributeMetadata.java rename to server-spi/src/main/java/org/keycloak/userprofile/AttributeMetadata.java diff --git a/server-spi-private/src/main/java/org/keycloak/userprofile/AttributeValidatorMetadata.java b/server-spi/src/main/java/org/keycloak/userprofile/AttributeValidatorMetadata.java similarity index 100% rename from server-spi-private/src/main/java/org/keycloak/userprofile/AttributeValidatorMetadata.java rename to server-spi/src/main/java/org/keycloak/userprofile/AttributeValidatorMetadata.java diff --git a/server-spi-private/src/main/java/org/keycloak/userprofile/Attributes.java b/server-spi/src/main/java/org/keycloak/userprofile/Attributes.java similarity index 100% rename from server-spi-private/src/main/java/org/keycloak/userprofile/Attributes.java rename to server-spi/src/main/java/org/keycloak/userprofile/Attributes.java diff --git a/server-spi-private/src/main/java/org/keycloak/userprofile/UserProfileAttributeValidationContext.java b/server-spi/src/main/java/org/keycloak/userprofile/UserProfileAttributeValidationContext.java similarity index 100% rename from server-spi-private/src/main/java/org/keycloak/userprofile/UserProfileAttributeValidationContext.java rename to server-spi/src/main/java/org/keycloak/userprofile/UserProfileAttributeValidationContext.java diff --git a/server-spi-private/src/main/java/org/keycloak/userprofile/UserProfileContext.java b/server-spi/src/main/java/org/keycloak/userprofile/UserProfileContext.java similarity index 100% rename from server-spi-private/src/main/java/org/keycloak/userprofile/UserProfileContext.java rename to server-spi/src/main/java/org/keycloak/userprofile/UserProfileContext.java diff --git a/server-spi-private/src/main/java/org/keycloak/userprofile/UserProfileMetadata.java b/server-spi/src/main/java/org/keycloak/userprofile/UserProfileMetadata.java similarity index 100% rename from server-spi-private/src/main/java/org/keycloak/userprofile/UserProfileMetadata.java rename to server-spi/src/main/java/org/keycloak/userprofile/UserProfileMetadata.java diff --git a/server-spi-private/src/main/java/org/keycloak/validate/AbstractSimpleValidator.java b/server-spi/src/main/java/org/keycloak/validate/AbstractSimpleValidator.java similarity index 100% rename from server-spi-private/src/main/java/org/keycloak/validate/AbstractSimpleValidator.java rename to server-spi/src/main/java/org/keycloak/validate/AbstractSimpleValidator.java diff --git a/server-spi-private/src/main/java/org/keycloak/validate/AbstractStringValidator.java b/server-spi/src/main/java/org/keycloak/validate/AbstractStringValidator.java similarity index 100% rename from server-spi-private/src/main/java/org/keycloak/validate/AbstractStringValidator.java rename to server-spi/src/main/java/org/keycloak/validate/AbstractStringValidator.java diff --git a/server-spi-private/src/main/java/org/keycloak/validate/SimpleValidator.java b/server-spi/src/main/java/org/keycloak/validate/SimpleValidator.java similarity index 100% rename from server-spi-private/src/main/java/org/keycloak/validate/SimpleValidator.java rename to server-spi/src/main/java/org/keycloak/validate/SimpleValidator.java diff --git a/server-spi-private/src/main/java/org/keycloak/validate/ValidationContext.java b/server-spi/src/main/java/org/keycloak/validate/ValidationContext.java similarity index 100% rename from server-spi-private/src/main/java/org/keycloak/validate/ValidationContext.java rename to server-spi/src/main/java/org/keycloak/validate/ValidationContext.java diff --git a/server-spi-private/src/main/java/org/keycloak/validate/ValidationError.java b/server-spi/src/main/java/org/keycloak/validate/ValidationError.java similarity index 100% rename from server-spi-private/src/main/java/org/keycloak/validate/ValidationError.java rename to server-spi/src/main/java/org/keycloak/validate/ValidationError.java diff --git a/server-spi-private/src/main/java/org/keycloak/validate/ValidationResult.java b/server-spi/src/main/java/org/keycloak/validate/ValidationResult.java similarity index 100% rename from server-spi-private/src/main/java/org/keycloak/validate/ValidationResult.java rename to server-spi/src/main/java/org/keycloak/validate/ValidationResult.java diff --git a/server-spi-private/src/main/java/org/keycloak/validate/Validator.java b/server-spi/src/main/java/org/keycloak/validate/Validator.java similarity index 75% rename from server-spi-private/src/main/java/org/keycloak/validate/Validator.java rename to server-spi/src/main/java/org/keycloak/validate/Validator.java index dfd08a76525f..41e3042605ca 100644 --- a/server-spi-private/src/main/java/org/keycloak/validate/Validator.java +++ b/server-spi/src/main/java/org/keycloak/validate/Validator.java @@ -1,18 +1,20 @@ /* - * Copyright 2021 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. + * 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 + * 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 + * 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. * - * 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.validate; @@ -40,17 +42,6 @@ default ValidationContext validate(Object input) { return validate(input, "input", new ValidationContext(), ValidatorConfig.EMPTY); } - /** - * Validates the given {@code input} with an additional {@code config}. - * - * @param input the value to validate - * @param config parameterization for the current validation - * @return the validation context with the outcome of the validation - */ - default ValidationContext validate(Object input, ValidatorConfig config) { - return validate(input, "input", new ValidationContext(), config); - } - /** * Validates the given {@code input}. * diff --git a/server-spi-private/src/main/java/org/keycloak/validate/ValidatorConfig.java b/server-spi/src/main/java/org/keycloak/validate/ValidatorConfig.java similarity index 100% rename from server-spi-private/src/main/java/org/keycloak/validate/ValidatorConfig.java rename to server-spi/src/main/java/org/keycloak/validate/ValidatorConfig.java diff --git a/server-spi-private/src/main/java/org/keycloak/validate/ValidatorFactory.java b/server-spi/src/main/java/org/keycloak/validate/ValidatorFactory.java similarity index 100% rename from server-spi-private/src/main/java/org/keycloak/validate/ValidatorFactory.java rename to server-spi/src/main/java/org/keycloak/validate/ValidatorFactory.java diff --git a/server-spi-private/src/main/java/org/keycloak/validate/ValidatorSPI.java b/server-spi/src/main/java/org/keycloak/validate/ValidatorSPI.java similarity index 100% rename from server-spi-private/src/main/java/org/keycloak/validate/ValidatorSPI.java rename to server-spi/src/main/java/org/keycloak/validate/ValidatorSPI.java diff --git a/server-spi/src/main/java/org/keycloak/validate/Validators.java b/server-spi/src/main/java/org/keycloak/validate/Validators.java new file mode 100644 index 000000000000..07d62d81fd19 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/validate/Validators.java @@ -0,0 +1,131 @@ +/* + * 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.validate; + +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; + +/** + * Facade for Validation functions with support for {@link Validator} implementation lookup by id. + */ +public class Validators { + + /** + * Holds the {@link KeycloakSession}. + */ + private final KeycloakSession session; + + /** + * Creates a new {@link Validators} instance with the given {@link KeycloakSession}. + * + * @param session + */ + public Validators(KeycloakSession session) { + this.session = session; + } + + /** + * Look-up for a built-in or registered {@link Validator} with the given provider {@code id}. + * + * @param id + * @return + * @see #validator(KeycloakSession, String) + */ + public Validator validator(String id) { + return validator(session, id); + } + + /** + * Look-up for a built-in or registered {@link ValidatorFactory} with the given provider {@code id}. + * + * @param id + * @return + * @see #validatorFactory(KeycloakSession, String) + */ + public ValidatorFactory validatorFactory(String id) { + return validatorFactory(session, id); + } + + /** + * Validates the {@link ValidatorConfig} of {@link Validator} referenced by the given provider {@code id}. + * + * @param id + * @param config + * @return + * @see #validateConfig(KeycloakSession, String, ValidatorConfig) + */ + public ValidationResult validateConfig(String id, ValidatorConfig config) { + return validateConfig(session, id, config); + } + + /** + * Look-up up for a built-in or registered {@link Validator} with the given validatorId. + * + * @param session the {@link KeycloakSession} + * @param id the id of the validator + * @return the {@link Validator} or {@literal null} + */ + public static Validator validator(KeycloakSession session, String id) { + if (session == null) { + throw new IllegalArgumentException("KeycloakSession must be not null"); + } + + // Lookup validator in registry + return session.getProvider(Validator.class, id); + } + + /** + * Look-up for a built-in or registered {@link ValidatorFactory} with the given validatorId. + *

+ * This is intended for users who want to dynamically create new {@link Validator} instances, validate + * {@link ValidatorConfig} configurations or create default configurations for a {@link Validator}. + * + * @param session the {@link KeycloakSession} + * @param id the id of the validator + * @return the {@link Validator} or {@literal null} + */ + public static ValidatorFactory validatorFactory(KeycloakSession session, String id) { + if (session == null) { + throw new IllegalArgumentException("KeycloakSession must be not null"); + } + + // Lookup factory in registry + KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory(); + return (ValidatorFactory) sessionFactory.getProviderFactory(Validator.class, id); + } + + /** + * Validates the {@link ValidatorConfig} of {@link Validator} referenced by the given provider {@code id}. + * + * @param session + * @param id of the validator + * @param config to be validated + * @return + */ + public static ValidationResult validateConfig(KeycloakSession session, String id, ValidatorConfig config) { + + ValidatorFactory validatorFactory = validatorFactory(session, id); + if (validatorFactory != null) { + return validatorFactory.validateConfig(session, config); + } + + // We could not find a ValidationFactory to validate that config, so we assume the config is valid. + return ValidationResult.OK; + } +} diff --git a/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java b/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java index 72242391d481..4ad7be4c0d9d 100755 --- a/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java +++ b/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java @@ -62,6 +62,7 @@ import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.messages.Messages; import org.keycloak.sessions.AuthenticationSessionModel; +import org.keycloak.utils.StringUtil; import org.keycloak.vault.VaultStringSecret; import javax.crypto.SecretKey; @@ -427,7 +428,11 @@ protected JsonWebToken generateToken() { jwt.type(OAuth2Constants.JWT); jwt.issuer(getConfig().getClientId()); jwt.subject(getConfig().getClientId()); - jwt.audience(getConfig().getTokenUrl()); + String audience = getConfig().getClientAssertionAudience(); + if (StringUtil.isBlank(audience)) { + audience = getConfig().getTokenUrl(); + } + jwt.audience(audience); int expirationDelay = session.getContext().getRealm().getAccessCodeLifespan(); jwt.expiration(Time.currentTime() + expirationDelay); jwt.issuedNow(); diff --git a/services/src/main/java/org/keycloak/broker/oidc/OAuth2IdentityProviderConfig.java b/services/src/main/java/org/keycloak/broker/oidc/OAuth2IdentityProviderConfig.java index 020548ce0d1c..f74eaaaf49a6 100644 --- a/services/src/main/java/org/keycloak/broker/oidc/OAuth2IdentityProviderConfig.java +++ b/services/src/main/java/org/keycloak/broker/oidc/OAuth2IdentityProviderConfig.java @@ -154,7 +154,15 @@ public String getClientAssertionSigningAlg() { public void setClientAssertionSigningAlg(String signingAlg) { getConfig().put("clientAssertionSigningAlg", signingAlg); } - + + public String getClientAssertionAudience() { + return getConfig().get("clientAssertionAudience"); + } + + public void setClientAssertionAudience(String audience) { + getConfig().put("clientAssertionAudience", audience); + } + @Override public void validate(RealmModel realm) { SslRequired sslRequired = realm.getSslRequired(); diff --git a/services/src/main/java/org/keycloak/keystore/DefaultKeyStoreProvider.java b/services/src/main/java/org/keycloak/keystore/DefaultKeyStoreProvider.java new file mode 100644 index 000000000000..207ed6b054d9 --- /dev/null +++ b/services/src/main/java/org/keycloak/keystore/DefaultKeyStoreProvider.java @@ -0,0 +1,159 @@ +/* + * Copyright 2022 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.keystore; + +import java.io.IOException; +import java.nio.file.Paths; +import java.security.KeyStore; +import java.security.KeyStore.Builder; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.cert.CertificateException; +import java.security.spec.InvalidKeySpecException; +import java.time.Duration; + +import org.jboss.logging.Logger; +import org.keycloak.Config.Scope; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; + +public class DefaultKeyStoreProvider implements KeyStoreProvider, KeyStoreProviderFactory { + + private static final Logger log = Logger.getLogger(DefaultKeyStoreProvider.class); + + private KeyStore.Builder ldapKeyStoreBuilder; + private KeyStore.Builder httpsKeyStoreBuilder; + + @Override + public void close() { + // Nothing to do here. + } + + /** + * Returns named keystore. + * + * @param keyStoreIdentifier The identifier of requested keystore. + * @return Reference to a keystore. + * @throws KeyStoreException + */ + @Override + public KeyStore loadKeyStore(String keyStoreIdentifier) { + try { + return loadKeyStoreBuilder(keyStoreIdentifier).getKeyStore(); + } catch (KeyStoreException e) { + log.errorv("Cannot load KeyStore {0}", keyStoreIdentifier); + throw new RuntimeException("Cannot load KeyStore " + keyStoreIdentifier + ":" + e.getMessage()); + } + } + + @Override + public Builder loadKeyStoreBuilder(String keyStoreIdentifier) { + log.infov("loadKeyStoreBuilder was called with keystore identifier {0}", keyStoreIdentifier); + if (keyStoreIdentifier.equals(LDAP_CLIENT_KEYSTORE)) { + return ldapKeyStoreBuilder; + } + if (keyStoreIdentifier.equals(HTTPS_SERVER_KEYSTORE)) { + return httpsKeyStoreBuilder; + } + + log.errorv("loadKeyStoreBuilder was called with invalid keystore identifier {0}", keyStoreIdentifier); + throw new IllegalArgumentException("invalid keystore requested, keyStoreIdentifier:" + keyStoreIdentifier); + } + + @Override + public KeyStoreProvider create(KeycloakSession session) { + return this; + } + + @Override + public void init(Scope config) { + // Allow changing the default duration that defines how frequently at most the backing file(s) will be checked + // for modification. The value is parsed as ISO8601 time duration (e.g. "1s", "2m30s", "1h"). + String cacheTtl = config.get("keyStoreCacheTtl"); + if (cacheTtl != null) { + log.infov("Setting reloading keyStore cache TTL to {0}", cacheTtl); + ReloadingKeyStore.setDefaultKeyStoreCacheTtl(Duration.parse("PT" + cacheTtl)); + } + + ldapKeyStoreBuilder = getKeyStore(config, "ldap"); + httpsKeyStoreBuilder = getKeyStore(config, "https"); + } + + private KeyStore.Builder getKeyStore(Scope config, String prefix) { + KeyStore.Builder keyStoreBuilder = null; + + // Check if credentials are given as PEM files. + log.debugv("Checking for PEM files for {0}", prefix); + String certificateFile = config.get(prefix + "CertificateFile"); + String certificateKeyFile = config.get(prefix + "CertificateKeyFile"); + if (certificateFile != null && certificateKeyFile != null) { + log.infov("Loading credentials: {0}, {1}", certificateFile, certificateKeyFile); + try { + keyStoreBuilder = ReloadingKeyStore.Builder.fromPem(Paths.get(certificateFile), + Paths.get(certificateKeyFile)); + } catch (NoSuchAlgorithmException | CertificateException | IllegalArgumentException | KeyStoreException + | InvalidKeySpecException | IOException e) { + throw new RuntimeException("Failed to initialize " + prefix + " keystore: " + e.toString()); + } + } + + // Check if credentials are given as KeyStore file. + String keyStoreFile = config.get(prefix + "KeystoreFile"); + String keyStorePassword = config.get(prefix + "KeystorePassword"); + String keyStoreType = config.get(prefix + "KeystoreType", "JKS"); + + // Check if both PEM files and KeyStore is configured. + if (keyStoreBuilder != null && keyStoreFile != null) { + log.errorv("Both PEM files and KeyStore was configured for {0}. Choose only one.", prefix); + throw new IllegalArgumentException("Both PEM files and KeyStore was configured for " + prefix + ". Choose only one."); + } + + // Check if keyStore file is configured without password. + if (keyStoreFile != null && keyStorePassword == null) { + log.errorv("Password not given for {0} keystore {1}", prefix, keyStoreFile); + throw new IllegalArgumentException("Password not given for " + prefix + " keystore: " + keyStoreFile); + } + + log.debugv("Checking for keystore file for {0}", prefix); + if (keyStoreFile != null) { + try { + log.infov("Loading credentials for {0}: {1}", prefix, keyStoreFile); + keyStoreBuilder = ReloadingKeyStore.Builder + .fromKeyStoreFile(keyStoreType, Paths.get(keyStoreFile), keyStorePassword); + } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException | NoSuchProviderException e) { + throw new RuntimeException("Failed to initialize " + prefix + " keystore: " + e.toString()); + } + } + + return keyStoreBuilder; + } + + @Override + public String getId() { + return "default"; + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + // Nothing to do here. + } + + + +} diff --git a/services/src/main/java/org/keycloak/keystore/DelegatingKeyStoreSpi.java b/services/src/main/java/org/keycloak/keystore/DelegatingKeyStoreSpi.java new file mode 100644 index 000000000000..469296c9acfa --- /dev/null +++ b/services/src/main/java/org/keycloak/keystore/DelegatingKeyStoreSpi.java @@ -0,0 +1,284 @@ +/* + * Copyright 2022 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.keystore; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.Key; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.KeyStoreSpi; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.spec.InvalidKeySpecException; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.jboss.logging.Logger; + +/** + * Implementation of {@code KeyStoreSpi} that delegates calls to an instance of {@code KeyStore}. + * The delegate keystore can be replaced on demand when the underlying certificate(s) and key(s) change. + */ +public abstract class DelegatingKeyStoreSpi extends KeyStoreSpi { + + private static final Logger log = Logger.getLogger(DelegatingKeyStoreSpi.class); + + // Use Clock instance instead of Instant.now(). This allows mocked clock to be injected from test cases(s). + static Clock now = Clock.systemUTC(); + + // Defines how often the delegate keystore should be checked for updates. + static Duration cacheTtl = Duration.of(1, ChronoUnit.SECONDS); + + private AtomicReference delegate = new AtomicReference<>(); + + // Defines the next time when to check updates. + private Instant cacheExpiredTime = Instant.MIN; + + /** + * Reloads the delegate KeyStore if the underlying files have changed on disk. + */ + abstract void refresh() throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException, + InvalidKeySpecException, NoSuchProviderException; + + /** + * Calls {@link #refresh()} to refresh the cached KeyStore and if more than + * {@link #cacheTtl} has passed since last + * refresh. + */ + private void refreshCachedKeyStore() { + // Return if not enough time has passed for the delegate KeyStore to be refreshed. + if (now.instant().isBefore(cacheExpiredTime)) { + return; + } + + // Set the time when refresh should be checked next. + cacheExpiredTime = now.instant().plus(cacheTtl); + + try { + refresh(); + } catch (Exception e) { + log.debug("Failed to refresh:", e); + e.printStackTrace(); + } + } + + /** + * Replace the {@code KeyStore} delegate, + * + * @param delegate KeyStore instance that becomes the delegate. + */ + void setKeyStoreDelegate(KeyStore delegate) { + log.debug("Setting new KeyStore delegate"); + this.delegate.set(new Delegate(delegate)); + } + + @Override + public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, UnrecoverableKeyException { + refreshCachedKeyStore(); + + try { + return delegate.get().keyStore.getKey(alias, password); + } catch (KeyStoreException e) { + log.error("getKey() failed", e); + return null; + } catch (UnrecoverableKeyException e) { + // UnrecoverableKeyException is thrown when given password for keystore entry was incorrect. + // JSSE X509KeyManagerImpl.getEntry() unfortunately hides the error by catching and ignoring the exception. + // To help troubleshooting, we catch the exception here as well and print the error. + log.error("getKey() failed", e); + e.printStackTrace(); + throw e; + } + } + + @Override + public Certificate[] engineGetCertificateChain(String alias) { + refreshCachedKeyStore(); + + try { + return delegate.get().keyStore.getCertificateChain(alias); + } catch (KeyStoreException e) { + log.error("getCertificateChain() failed:", e); + return new Certificate[0]; + } + } + + @Override + public Certificate engineGetCertificate(String alias) { + refreshCachedKeyStore(); + + try { + return delegate.get().keyStore.getCertificate(alias); + } catch (KeyStoreException e) { + log.error("getCertificate() failed:", e); + return null; + } + } + + @Override + public Date engineGetCreationDate(String alias) { + refreshCachedKeyStore(); + + try { + return delegate.get().keyStore.getCreationDate(alias); + } catch (KeyStoreException e) { + log.error("getCreationDate() failed:", e); + return null; + } + } + + /** + * Return aliases in sorted order. + * This is different than the order used by underlying KeyStore. + * Sorting aliases enables user to have expected fallback behavior when KeyManager selects server certificate. + * This can be useful in cases when client does not set TLS SNI or unknown SNI servername is requested. + */ + @Override + public Enumeration engineAliases() { + refreshCachedKeyStore(); + return Collections.enumeration(new ArrayList<>(delegate.get().sortedAliases)); + } + + @Override + public boolean engineContainsAlias(String alias) { + refreshCachedKeyStore(); + + try { + return delegate.get().keyStore.containsAlias(alias); + } catch (KeyStoreException e) { + log.error("containsAlias() failed:", e); + return false; + } + } + + @Override + public int engineSize() { + refreshCachedKeyStore(); + + try { + return delegate.get().keyStore.size(); + } catch (KeyStoreException e) { + log.error("size() failed:", e); + return 0; + } + } + + @Override + public boolean engineIsKeyEntry(String alias) { + refreshCachedKeyStore(); + + try { + return delegate.get().keyStore.isKeyEntry(alias); + } catch (KeyStoreException e) { + log.error("isKeyEntry() failed:", e); + return false; + } + } + + @Override + public boolean engineIsCertificateEntry(String alias) { + refreshCachedKeyStore(); + + try { + return delegate.get().keyStore.isCertificateEntry(alias); + } catch (KeyStoreException e) { + log.error("isCertificateEntry() failed;", e); + return false; + } + } + + @Override + public String engineGetCertificateAlias(Certificate cert) { + refreshCachedKeyStore(); + + try { + return delegate.get().keyStore.getCertificateAlias(cert); + } catch (KeyStoreException e) { + log.error("getCertificateAlias() failed:", e); + return null; + } + } + + @Override + public void engineLoad(InputStream stream, char[] password) + throws IOException, NoSuchAlgorithmException, CertificateException { + // Nothing to do here since implementations of this class have their own means to load certificates and keys. + log.debug("engineLoad()"); + } + + private static final String IMMUTABLE_KEYSTORE_ERR = "Modifying keystore is not supported"; + + @Override + public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain) + throws KeyStoreException { + throw new UnsupportedOperationException(IMMUTABLE_KEYSTORE_ERR); + } + + @Override + public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) throws KeyStoreException { + throw new UnsupportedOperationException(IMMUTABLE_KEYSTORE_ERR); + } + + @Override + public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException { + throw new UnsupportedOperationException(IMMUTABLE_KEYSTORE_ERR); + } + + @Override + public void engineDeleteEntry(String alias) throws KeyStoreException { + throw new UnsupportedOperationException(IMMUTABLE_KEYSTORE_ERR); + } + + @Override + public void engineStore(OutputStream stream, char[] password) + throws IOException, NoSuchAlgorithmException, CertificateException { + throw new UnsupportedOperationException(IMMUTABLE_KEYSTORE_ERR); + } + + class Delegate { + KeyStore keyStore; + List sortedAliases; + + Delegate(KeyStore ks) { + this.keyStore = ks; + + try { + // Keep aliases sorted to entries returned. + sortedAliases = Collections.list(ks.aliases()); + Collections.sort(sortedAliases); + } catch (KeyStoreException e) { + // Ignore exception. + log.error("Failed getting aliases:", e); + } + } + } + +} diff --git a/services/src/main/java/org/keycloak/keystore/ReloadingKeyStore.java b/services/src/main/java/org/keycloak/keystore/ReloadingKeyStore.java new file mode 100644 index 000000000000..e17f298cc851 --- /dev/null +++ b/services/src/main/java/org/keycloak/keystore/ReloadingKeyStore.java @@ -0,0 +1,223 @@ +/* + * Copyright 2022 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.keystore; + +import java.io.IOException; +import java.nio.file.Path; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.KeyStoreSpi; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.cert.CertificateException; +import java.security.spec.InvalidKeySpecException; +import java.time.Duration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * KeyStore that can reload the wrapped delegate {@code KeyStore} when the backing files have been changed on disk. + */ +public class ReloadingKeyStore extends KeyStore { + + protected ReloadingKeyStore(KeyStoreSpi keyStoreSpi) + throws NoSuchAlgorithmException, CertificateException, IOException { + + super(keyStoreSpi, null, "ReloadingKeyStore"); + + // Calling load(), even with null arguments, will initialize the KeyStore to expected state. + load(null, null); + } + + /** + * KeyStore.Builder creates reloading keystores for various types of credential files. + */ + public static class Builder extends KeyStore.Builder { + + private final KeyStore keyStore; + private final ProtectionParameter protection; + + // Map + private Map aliasProtection; + + private Builder(KeyStore keyStore, char[] password) { + this.keyStore = keyStore; + this.protection = new PasswordProtection(password); + } + + private Builder(KeyStore keyStore, char[] password, Map aliasPasswords) { + this.keyStore = keyStore; + this.protection = new PasswordProtection(password); + this.aliasProtection = new HashMap<>(); + for (Map.Entry entry : aliasPasswords.entrySet()) { + aliasProtection.put(entry.getKey(), new PasswordProtection(entry.getValue())); + } + } + + /** + * Returns the KeyStore described by this object. + * + * @return Keystore described by this object. + */ + @Override + public KeyStore getKeyStore() { + return keyStore; + } + + /** + * Returns the ProtectionParameters (password) that should be used to obtain the Entry with the given alias. + * + * @param alias Alias for key entry. + * @return ProtectionParameters (password) for the key entry. + */ + @Override + public ProtectionParameter getProtectionParameter(String alias) { + // Use keystore password, if individual alias passwords are not defined. + if (aliasProtection == null) { + return protection; + } + + // Certain Java versions pass alias in a format to getProtectionParameter(), which was meant to be internal + // to NewSunX509 X509KeyManagerImpl. This was fixed in JDK17. + // + // X509KeyManagerImpl calls getProtectionParameter with incorrect alias + // https://bugs.openjdk.org/browse/JDK-8264554 + // https://github.com/openjdk/jdk/pull/3326 + // + // For compatibility also with older versions, parse the plain alias from NewSunX509 KeyManager internal + // (prefixed) alias. + // https://github.com/openjdk/jdk/blob/6e55a72f25f7273e3a8a19e0b9a97669b84808e9/src/java.base/share/classes/sun/security/ssl/X509KeyManagerImpl.java#L237-L265 + Objects.requireNonNull(alias); + int firstDot = alias.indexOf('.'); + int secondDot = alias.indexOf('.', firstDot + 1); + if ((firstDot == -1) || (secondDot == firstDot)) { + return aliasProtection.getOrDefault(alias, protection); + } + String requestedAlias = alias.substring(secondDot + 1); + return aliasProtection.getOrDefault(requestedAlias, protection); + } + + /** + * Creates KeyStore builder for given PKCS#12 or JKS file. + * + * @param type KeyStore type such as {@code "PKCS12"} or {@code "JKS"}. + * @param path Path to the keystore file. + * @param password Password used to decrypt the KeyStore. + * @return The KeyStore builder. + */ + public static KeyStore.Builder fromKeyStoreFile(String type, Path path, String password) + throws NoSuchAlgorithmException, CertificateException, KeyStoreException, + IOException, NoSuchProviderException { + return new Builder(new ReloadingKeyStore(new ReloadingKeyStoreFileSpi(type, path, password)), + password.toCharArray()); + } + + /** + * Creates KeyStore builder for given PKCS#12 or JKS file. + * + * @param type KeyStore type such as {@code "PKCS12"} or {@code "JKS"}. + * @param path Path to the keystore file. + * @param password Password used to decrypt the KeyStore. + * @param aliasPasswords Passwords used to decrypt keystore entries. Map of: alias (key), password (value). + * @return The KeyStore builder. + */ + public static KeyStore.Builder fromKeyStoreFile(String type, Path path, String password, + Map aliasPasswords) + throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException, NoSuchProviderException { + return new Builder(new ReloadingKeyStore(new ReloadingKeyStoreFileSpi(type, path, password)), + password.toCharArray(), aliasPasswords); + } + + /** + * Creates KeyStore builder for given certificate and private key files. + * Certificate in position {@code certs[n]} must match the private key in position {@code keys[n]}. + * + * @param certs List of paths to certificates. + * @param keys List of keys to private keys. + * @return The KeyStore builder. + */ + public static KeyStore.Builder fromPem(List certs, List keys) + throws NoSuchAlgorithmException, CertificateException, IllegalArgumentException, KeyStoreException, + InvalidKeySpecException, IOException { + + if (keys.size() < certs.size()) { + throw new IllegalArgumentException("Missing private key"); + } else if (keys.size() > certs.size()) { + throw new IllegalArgumentException("Missing X.509 certificate"); + } else if (keys.isEmpty()) { + throw new IllegalArgumentException("No credentials configured"); + } + + ReloadingPemFileKeyStoreSpi spi = new ReloadingPemFileKeyStoreSpi(); + + Iterator cpi = certs.iterator(); + Iterator kpi = keys.iterator(); + while (cpi.hasNext() && kpi.hasNext()) { + spi.addKeyEntry(cpi.next(), kpi.next()); + } + + return new Builder(new ReloadingKeyStore(spi), ReloadingPemFileKeyStoreSpi.IN_MEMORY_KEYSTORE_PASSWORD); + } + + /** + * Creates KeyStore builder for given certificate and private key file. + * + * @param cert Path to certificate. + * @param key Path to private key. + * @return The KeyStore builder. + */ + public static KeyStore.Builder fromPem(Path cert, Path key) + throws NoSuchAlgorithmException, CertificateException, IllegalArgumentException, KeyStoreException, + InvalidKeySpecException, IOException { + + ReloadingPemFileKeyStoreSpi spi = new ReloadingPemFileKeyStoreSpi(); + spi.addKeyEntry(cert, key); + return new Builder(new ReloadingKeyStore(spi), ReloadingPemFileKeyStoreSpi.IN_MEMORY_KEYSTORE_PASSWORD); + } + + /** + * Creates KeyStore builder for certificate path(s). + * + * @param cert Path to certificate. + * @return The KeyStore builder. + */ + public static KeyStore.Builder fromPem(Path... cert) throws KeyStoreException, InvalidKeySpecException, + NoSuchAlgorithmException, CertificateException, IOException { + + ReloadingPemFileKeyStoreSpi spi = new ReloadingPemFileKeyStoreSpi(); + for (Path c : cert) { + spi.addCertificateEntry(c); + } + return new Builder(new ReloadingKeyStore(spi), ReloadingPemFileKeyStoreSpi.IN_MEMORY_KEYSTORE_PASSWORD); + } + } + + /** + * Set how frequently the KeyStore(s) will check if the underlying files have changed and reload is required. + * The check still happens only when credentials are used. TTL of one second means that the file modification + * will be checked at most once per second, depending when the KeyStore is used next. + * + * @param ttl Minimum time-to-live for in-memory delegate {@code KeyStore}. + */ + public static void setDefaultKeyStoreCacheTtl(Duration ttl) { + DelegatingKeyStoreSpi.cacheTtl = ttl; + } +} diff --git a/services/src/main/java/org/keycloak/keystore/ReloadingKeyStoreFileSpi.java b/services/src/main/java/org/keycloak/keystore/ReloadingKeyStoreFileSpi.java new file mode 100644 index 000000000000..62f356cbbd66 --- /dev/null +++ b/services/src/main/java/org/keycloak/keystore/ReloadingKeyStoreFileSpi.java @@ -0,0 +1,81 @@ +/* + * Copyright 2022 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.keystore; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.FileTime; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.cert.CertificateException; + +import org.jboss.logging.Logger; +import org.keycloak.common.crypto.CryptoIntegration; +import org.keycloak.common.util.KeystoreUtil.KeystoreFormat; + +/** + * {@code KeyStoreSpi} implementation that hot-reloads {@code KeyStore} when the backing file changes. + */ +public class ReloadingKeyStoreFileSpi extends DelegatingKeyStoreSpi { + + private static final Logger log = Logger.getLogger(ReloadingKeyStoreFileSpi.class); + + private final String type; + private final Path path; + private final char[] password; + private FileTime lastModified; + + public ReloadingKeyStoreFileSpi(String type, Path path, String password) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, NoSuchProviderException { + if (password == null) { + throw new IllegalArgumentException("Password must not be null"); + } + + this.type = type; + this.path = path; + this.password = password.toCharArray(); + + refresh(); + } + + /** + * Reload keystore if it has been modified on disk since is was last loaded. + * @throws IOException + * @throws KeyStoreException + * @throws CertificateException + * @throws NoSuchAlgorithmException + * @throws NoSuchProviderException + */ + void refresh() throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException { + // If keystore has been previously loaded, check the modification timestamp to decide if reload is needed. + if ((lastModified != null) && (lastModified.compareTo(Files.getLastModifiedTime(path)) >= 0)) { + // File was not modified since last reload: do nothing. + return; + } + + // Load keystore from disk. + log.debugv("Reloading keystore {0}", path); + KeyStore ks = CryptoIntegration.getProvider().getKeyStore(KeystoreFormat.valueOf(type)); + ks.load(Files.newInputStream(path), password); + setKeyStoreDelegate(ks); + this.lastModified = Files.getLastModifiedTime(path); + } + +} diff --git a/services/src/main/java/org/keycloak/keystore/ReloadingPemFileKeyStoreSpi.java b/services/src/main/java/org/keycloak/keystore/ReloadingPemFileKeyStoreSpi.java new file mode 100644 index 000000000000..24c6141a4cef --- /dev/null +++ b/services/src/main/java/org/keycloak/keystore/ReloadingPemFileKeyStoreSpi.java @@ -0,0 +1,199 @@ +/* + * Copyright 2022 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.keystore; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.FileTime; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.spec.InvalidKeySpecException; +import java.util.ArrayList; +import java.util.List; + +import org.jboss.logging.Logger; +import org.keycloak.common.util.PemUtils; + +/** + * {@code KeyStoreSpi} implementation that hot-reloads certificates and private keys from PEM files when the backing files change. + */ +public class ReloadingPemFileKeyStoreSpi extends DelegatingKeyStoreSpi { + + // Empty password used for the in-memory KeyStore that holds the credentials loaded from PEM files. + protected static final char[] IN_MEMORY_KEYSTORE_PASSWORD = "".toCharArray(); + + private static final Logger log = Logger.getLogger(ReloadingPemFileKeyStoreSpi.class); + + // List of objects holding the path of certificates and private keys and their last known modification timestamps. + private final List keyFileEntries = new ArrayList<>(); + + // List of objects holding the path of the certificates and their last known modification timestamps. + private final List certificateFileEntries = new ArrayList<>(); + + public ReloadingPemFileKeyStoreSpi() { + // Empty. + } + + /** + * Adds new key entry (certificate and private key) and recreates the {@code KeyStore}. + * + * @param cert Path to certificate file in PEM format. + * @param key Path to private key file in PEM format. + * @throws IOException + * @throws CertificateException + * @throws InvalidKeySpecException + * @throws KeyStoreException + * @throws NoSuchAlgorithmException + */ + public void addKeyEntry(Path cert, Path key) throws IOException, KeyStoreException, InvalidKeySpecException, NoSuchAlgorithmException, CertificateException { + keyFileEntries.add(new KeyFileEntry(cert, key)); + setKeyStoreDelegate(createKeyStore()); + } + + /** + * Adds new certificate entry and recreates the {@code KeyStore}. + * + * @param cert Path to certificate file in PEM format. + * @throws IOException + * @throws CertificateException + * @throws NoSuchAlgorithmException + * @throws InvalidKeySpecException + * @throws KeyStoreException + */ + public void addCertificateEntry(Path cert) throws IOException, KeyStoreException, InvalidKeySpecException, NoSuchAlgorithmException, CertificateException { + certificateFileEntries.add(new CertificateFileEntry(cert)); + setKeyStoreDelegate(createKeyStore()); + } + + /** + * Reload certificate and key files if they have been modified on disk since they were last loaded. + * @throws IOException + * @throws CertificateException + * @throws NoSuchAlgorithmException + * @throws InvalidKeySpecException + * @throws KeyStoreException + */ + void refresh() throws KeyStoreException, InvalidKeySpecException, NoSuchAlgorithmException, CertificateException, + IOException { + // Check if any of the files has been updated. + // If yes, update the last modification timestamp for the file(s) and recreate delegate KeyStore with new content. + boolean wasReloaded = false; + int i = 0; + for (KeyFileEntry e : keyFileEntries) { + if (e.needsReload()) { + keyFileEntries.set(i, new KeyFileEntry(e.certPath, e.keyPath)); + wasReloaded = true; + } + i++; + } + i = 0; + for (CertificateFileEntry e : certificateFileEntries) { + if (e.needsReload()) { + certificateFileEntries.set(i, new CertificateFileEntry(e.certPath)); + wasReloaded = true; + } + i++; + } + // Re-generate KeyStore. + if (wasReloaded) { + log.debug("Refreshing KeyStore"); + setKeyStoreDelegate(createKeyStore()); + } + } + + /** + * Create KeyStore containing given certificates and private keys. + * @throws KeyStoreException + * @throws IOException + * @throws CertificateException + * @throws NoSuchAlgorithmException + * @throws InvalidKeySpecException + */ + private KeyStore createKeyStore() throws KeyStoreException, InvalidKeySpecException, NoSuchAlgorithmException, + CertificateException, IOException { + log.debug("Creating new in-memory PKCS12 KeyStore."); + KeyStore ks = KeyStore.getInstance("PKCS12"); + + // Calling load(), even with null arguments, will initialize the KeyStore to expected state. + ks.load(null, null); + + int i = 0; + + // Load certificates + private keys. + for (KeyFileEntry e : keyFileEntries) { + String alias = String.format("%04d", i++); + log.debugv("Adding key entry with alias {0}: {1}, {2}", alias, e.keyPath, e.certPath); + ks.setKeyEntry(alias, PemUtils.decodePrivateKey(new String(Files.readAllBytes(e.keyPath))), IN_MEMORY_KEYSTORE_PASSWORD, + PemUtils.decodeCertificates(new String(Files.readAllBytes(e.certPath)))); + } + // Load trusted certificates. + for (CertificateFileEntry e : certificateFileEntries) { + String alias = String.format("%04d", i++); + log.debugv("Adding certificate entry with alias {0}: {1}", alias, e.certPath); + for (Certificate c : PemUtils.decodeCertificates(new String(Files.readAllBytes(e.certPath)))) { + ks.setCertificateEntry(alias, c); + } + } + + return ks; + } + + /** + * Holds the path of certificate and private key and their last known modification timestamp. + */ + class KeyFileEntry { + private final Path certPath; + private final Path keyPath; + private final FileTime certLastModified; + private final FileTime keyLastModified; + + KeyFileEntry(Path certPath, Path keyPath) throws IOException { + this.certPath = certPath; + this.keyPath = keyPath; + this.certLastModified = Files.getLastModifiedTime(certPath); + this.keyLastModified = Files.getLastModifiedTime(keyPath); + } + + boolean needsReload() throws IOException { + return (certLastModified.compareTo(Files.getLastModifiedTime(certPath)) < 0) || + (keyLastModified.compareTo(Files.getLastModifiedTime(keyPath)) < 0); + } + } + + /** + * Holds the path of a certificate and its last known modification timestamp. + */ + class CertificateFileEntry { + private final Path certPath; + private final FileTime certLastModified; + + CertificateFileEntry(Path certPath) throws IOException { + this.certPath = certPath; + this.certLastModified = Files.getLastModifiedTime(certPath); + } + + boolean needsReload() throws IOException { + return certLastModified.compareTo(Files.getLastModifiedTime(certPath)) < 0; + } + } + +} diff --git a/services/src/main/java/org/keycloak/partialimport/UsersPartialImport.java b/services/src/main/java/org/keycloak/partialimport/UsersPartialImport.java index 7820462cd1e2..d7d3eb1abf24 100755 --- a/services/src/main/java/org/keycloak/partialimport/UsersPartialImport.java +++ b/services/src/main/java/org/keycloak/partialimport/UsersPartialImport.java @@ -110,7 +110,9 @@ public void remove(RealmModel realm, KeycloakSession session, UserRepresentation @Override public void create(RealmModel realm, KeycloakSession session, UserRepresentation user) { - user.setId(KeycloakModelUtils.generateId()); + if (user.getId() == null) { + user.setId(KeycloakModelUtils.generateId()); + } UserModel userModel = RepresentationToModel.createUser(session, realm, user); if (userModel == null) throw new RuntimeException("Unable to create user " + getName(user)); createdIds.put(getName(user), userModel.getId()); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java index 2f371513917e..dbd61ed4428c 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java @@ -75,10 +75,7 @@ protected static void setClaim(IDToken token, ProtocolMapperModel mappingModel, } - private static final Pattern CLIENT_ID_PATTERN = Pattern.compile("\\$\\{client_id\\}"); - - private static final Pattern DOT_PATTERN = Pattern.compile("\\."); - private static final String DOT_REPLACEMENT = "\\\\\\\\."; + private static final Pattern CLIENT_ID_PATTERN = Pattern.compile(Pattern.quote("${client_id}")); private static void mapClaim(IDToken token, ProtocolMapperModel mappingModel, Object attributeValue, String clientId) { attributeValue = OIDCAttributeMapperHelper.mapAttributeValue(mappingModel, attributeValue); @@ -90,11 +87,10 @@ private static void mapClaim(IDToken token, ProtocolMapperModel mappingModel, Ob } if (clientId != null) { - // case when clientId contains dots - clientId = DOT_PATTERN.matcher(clientId).replaceAll(DOT_REPLACEMENT); Matcher matcher = CLIENT_ID_PATTERN.matcher(protocolClaim); if (matcher.find()) { - protocolClaim = matcher.replaceAll(clientId); + // dots and backslashes in clientId should be escaped first for the claim + protocolClaim = matcher.replaceAll(Matcher.quoteReplacement(clientId.replace("\\", "\\\\").replace(".", "\\."))); } } diff --git a/services/src/main/java/org/keycloak/protocol/saml/mappers/ScriptBasedMapper.java b/services/src/main/java/org/keycloak/protocol/saml/mappers/ScriptBasedMapper.java index 5e0b00685742..a6cf95ad455e 100644 --- a/services/src/main/java/org/keycloak/protocol/saml/mappers/ScriptBasedMapper.java +++ b/services/src/main/java/org/keycloak/protocol/saml/mappers/ScriptBasedMapper.java @@ -12,7 +12,11 @@ import org.keycloak.scripting.ScriptCompilationException; import org.keycloak.scripting.ScriptingProvider; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + /** * This class provides a mapper that uses javascript to attach a value to an attribute for SAML tokens. diff --git a/services/src/main/java/org/keycloak/userprofile/DeclarativeUserProfileProvider.java b/services/src/main/java/org/keycloak/userprofile/DeclarativeUserProfileProvider.java index 211c6d8972ad..8d17a2267e1b 100644 --- a/services/src/main/java/org/keycloak/userprofile/DeclarativeUserProfileProvider.java +++ b/services/src/main/java/org/keycloak/userprofile/DeclarativeUserProfileProvider.java @@ -27,6 +27,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; @@ -448,6 +449,10 @@ private boolean isBuiltInAttribute(String attributeName) { return UserModel.USERNAME.equals(attributeName) || UserModel.EMAIL.equals(attributeName); } + private boolean isOptionalBuiltInAttribute(String attributeName) { + return UserModel.FIRST_NAME.equals(attributeName) || UserModel.LAST_NAME.equals(attributeName); + } + private Predicate createViewAllowedPredicate(Predicate canEdit, Set viewRoles) { return ac -> UPConfigUtils.isRoleForContext(ac.getContext(), viewRoles) || canEdit.test(ac); @@ -521,7 +526,11 @@ private Function createUserDefinedProfi throw new RuntimeException("UserProfile configuration for realm '" + session.getContext().getRealm().getName() + "' is invalid: " + errors.toString()); } - for (AttributeMetadata metadata : decoratedMetadata.getAttributes()) { + Iterator attributes = decoratedMetadata.getAttributes().iterator(); + + while (attributes.hasNext()) { + AttributeMetadata metadata = attributes.next(); + String attributeName = metadata.getName(); if (isBuiltInAttribute(attributeName)) { @@ -534,6 +543,10 @@ private Function createUserDefinedProfi // user-defined configuration will add its own validators validators.removeIf(m -> m.getValidatorId().equals(id)); } + } else if (isOptionalBuiltInAttribute(attributeName)) { + // removes optional default attributes in favor of user-defined configuration + // make sure any attribute other than username and email are removed from the metadata + attributes.remove(); } } diff --git a/services/src/main/java/org/keycloak/userprofile/validator/ImmutableAttributeValidator.java b/services/src/main/java/org/keycloak/userprofile/validator/ImmutableAttributeValidator.java index 3b588adbff1d..d76d5575eb9a 100644 --- a/services/src/main/java/org/keycloak/userprofile/validator/ImmutableAttributeValidator.java +++ b/services/src/main/java/org/keycloak/userprofile/validator/ImmutableAttributeValidator.java @@ -17,23 +17,20 @@ package org.keycloak.userprofile.validator; import static org.keycloak.common.util.CollectionUtil.collectionEquals; -import static org.keycloak.validate.Validators.notBlankValidator; +import static org.keycloak.validate.BuiltinValidators.notBlankValidator; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; -import org.keycloak.common.util.CollectionUtil; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.userprofile.AttributeContext; -import org.keycloak.userprofile.AttributeValidatorMetadata; import org.keycloak.userprofile.UserProfileAttributeValidationContext; import org.keycloak.validate.SimpleValidator; import org.keycloak.validate.ValidationContext; import org.keycloak.validate.ValidationError; import org.keycloak.validate.ValidatorConfig; -import org.keycloak.validate.Validators; /** * A validator that fails when the attribute is marked as read only and its value has changed. diff --git a/services/src/main/resources/META-INF/services/org.keycloak.keystore.KeyStoreProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.keystore.KeyStoreProviderFactory new file mode 100644 index 000000000000..b23a1477845e --- /dev/null +++ b/services/src/main/resources/META-INF/services/org.keycloak.keystore.KeyStoreProviderFactory @@ -0,0 +1,18 @@ +# +# Copyright 2022 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. +# + +org.keycloak.keystore.DefaultKeyStoreProvider diff --git a/services/src/test/java/org/keycloak/keystore/TestReloadingKeystore.java b/services/src/test/java/org/keycloak/keystore/TestReloadingKeystore.java new file mode 100644 index 000000000000..30ee12be2d0c --- /dev/null +++ b/services/src/test/java/org/keycloak/keystore/TestReloadingKeystore.java @@ -0,0 +1,100 @@ +/* + * 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.keystore; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.cert.X509Certificate; +import java.security.KeyStore; +import java.util.Arrays; +import java.util.Collections; + +import org.junit.ClassRule; +import org.junit.Test; +import org.keycloak.common.util.PemUtils; +import org.keycloak.rule.CryptoInitRule; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class TestReloadingKeystore { + + @ClassRule + public static CryptoInitRule cryptoInitRule = new CryptoInitRule(); + + @Test + public void testLoadJks() throws Exception { + Path ksPath = Paths.get(TestReloadingKeystore.class.getResource("keycloak.jks").getFile()); + KeyStore gotKs = ReloadingKeyStore.Builder.fromKeyStoreFile("JKS", ksPath, "secret").getKeyStore(); + assertNotNull(gotKs); + assertEquals(Arrays.asList("localhost"), Collections.list(gotKs.aliases())); + } + + @Test + public void testLoadP12() throws Exception { + Path ksPath = Paths.get(TestReloadingKeystore.class.getResource("keycloak.p12").getFile()); + KeyStore gotKs = ReloadingKeyStore.Builder.fromKeyStoreFile("JKS", ksPath, "secret").getKeyStore(); + assertNotNull(gotKs); + assertEquals(Arrays.asList("localhost"), Collections.list(gotKs.aliases())); + } + + @Test + public void testLoadPemWithRsaKey() throws Exception { + Path certPath = Paths.get(TestReloadingKeystore.class.getResource("rsa-leaf.pem").getFile()); + Path keyPath = Paths.get(TestReloadingKeystore.class.getResource("rsa-leaf-key.pem").getFile()); + Path caPath = Paths.get(TestReloadingKeystore.class.getResource("root-ca.pem").getFile()); + + KeyStore gotKs = ReloadingKeyStore.Builder.fromPem(certPath, keyPath).getKeyStore(); + assertNotNull(gotKs); + assertEquals("CN=rsa-leaf", ((X509Certificate) gotKs.getCertificate("0000")).getSubjectX500Principal().getName()); + + KeyStore gotTs = ReloadingKeyStore.Builder.fromPem(caPath).getKeyStore(); + assertNotNull(gotTs); + assertEquals("CN=root-ca", ((X509Certificate) gotTs.getCertificate("0000")).getSubjectX500Principal().getName()); + } + + @Test + public void testLoadPemWithEcKey() throws Exception { + Path certPath = Paths.get(TestReloadingKeystore.class.getResource("ec-leaf.pem").getFile()); + Path keyPath = Paths.get(TestReloadingKeystore.class.getResource("ec-leaf-key.pem").getFile()); + Path caPath = Paths.get(TestReloadingKeystore.class.getResource("root-ca.pem").getFile()); + + KeyStore gotKs = ReloadingKeyStore.Builder.fromPem(certPath, keyPath).getKeyStore(); + assertNotNull(gotKs); + assertEquals("CN=ec-leaf", + ((X509Certificate) gotKs.getCertificate("0000")).getSubjectX500Principal().getName()); + + KeyStore gotTs = ReloadingKeyStore.Builder.fromPem(caPath).getKeyStore(); + assertNotNull(gotTs); + assertEquals("CN=root-ca", + ((X509Certificate) gotTs.getCertificate("0000")).getSubjectX500Principal().getName()); + } + + @Test + public void testLoadPemBundle() throws Exception { + Path certPath = Paths.get(TestReloadingKeystore.class.getResource("rsa-leaf.pem").getFile()); + String bundle = new String(Files.readAllBytes(certPath)); + + X509Certificate[] certs = PemUtils.decodeCertificates(bundle); + + assertEquals(2, certs.length); + assertEquals("CN=rsa-leaf", certs[0].getSubjectX500Principal().getName()); + assertEquals("CN=intermediate-ca", certs[1].getSubjectX500Principal().getName()); + } + +} diff --git a/services/src/test/resources/org/keycloak/keystore/ec-leaf-key.pem b/services/src/test/resources/org/keycloak/keystore/ec-leaf-key.pem new file mode 100644 index 000000000000..45f32cac97a5 --- /dev/null +++ b/services/src/test/resources/org/keycloak/keystore/ec-leaf-key.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgeC/hX7bKYrZGl0ps +qcbtL8vB28yMAGwJKsJTXq3/vKWhRANCAARZnS7b7xChLWBKgyGS6da919363Maa +ILybziGjvRuTdMM+HOwRbVaQ4asIz6fFbBb04QL/B6n6egMipbd2LQSo +-----END PRIVATE KEY----- diff --git a/services/src/test/resources/org/keycloak/keystore/ec-leaf.pem b/services/src/test/resources/org/keycloak/keystore/ec-leaf.pem new file mode 100644 index 000000000000..43a931489dae --- /dev/null +++ b/services/src/test/resources/org/keycloak/keystore/ec-leaf.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIBMzCB2qADAgECAggXjHM80So4djAKBggqhkjOPQQDAjAaMRgwFgYDVQQDEw9p +bnRlcm1lZGlhdGUtY2EwHhcNMjMxMDA5MTMyNDI0WhcNMjQxMDA4MTMyNDI0WjAS +MRAwDgYDVQQDEwdlYy1sZWFmMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWZ0u +2+8QoS1gSoMhkunWvdfd+tzGmiC8m84ho70bk3TDPhzsEW1WkOGrCM+nxWwW9OEC +/wep+noDIqW3di0EqKMSMBAwDgYDVR0PAQH/BAQDAgWgMAoGCCqGSM49BAMCA0gA +MEUCIQDqbhNKetPJID/Q3nKXOTMgCAmt+gszcO1zsUi0ckX+lQIgBwLWlDnIRLkO +oTTWImQAf06Xtg8TV3/JBAS4mmVBX5Q= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBVDCB+6ADAgECAggXjHM8wGbICTAKBggqhkjOPQQDAjASMRAwDgYDVQQDEwdy +b290LWNhMB4XDTIzMTAwOTEzMjQyNFoXDTI0MTAwODEzMjQyNFowGjEYMBYGA1UE +AxMPaW50ZXJtZWRpYXRlLWNhMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtGRD +/KJfkyN2e8ljU4yJ2DCm3lfKIEG7XxyyYfTk4AV61mZ0LHZkHL5VmpBS05ubzPMH +fC29fFk3AHR5/G9if6MzMDEwDgYDVR0PAQH/BAQDAgWgMB8GA1UdIwQYMBaAFOqV +hVTxdWOGZhcicVoHmskuEzCxMAoGCCqGSM49BAMCA0gAMEUCIE2cHFGfUhPpD5nA +Y9cN4fyu2W9PTn1wyhfsF0EV24TIAiEAypFQGe13SElvcWCm4K8jLfiW/LIYM8Ys +u3M3qCYH/ho= +-----END CERTIFICATE----- diff --git a/services/src/test/resources/org/keycloak/keystore/intermediate-ca-key.pem b/services/src/test/resources/org/keycloak/keystore/intermediate-ca-key.pem new file mode 100644 index 000000000000..cab6fed2c670 --- /dev/null +++ b/services/src/test/resources/org/keycloak/keystore/intermediate-ca-key.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgpEHQGjZDRTreis5b +vtp5XAiFjGQo7tdIllW/tShkhWWhRANCAAS0ZEP8ol+TI3Z7yWNTjInYMKbeV8og +QbtfHLJh9OTgBXrWZnQsdmQcvlWakFLTm5vM8wd8Lb18WTcAdHn8b2J/ +-----END PRIVATE KEY----- diff --git a/services/src/test/resources/org/keycloak/keystore/intermediate-ca.pem b/services/src/test/resources/org/keycloak/keystore/intermediate-ca.pem new file mode 100644 index 000000000000..280c94a4bdc8 --- /dev/null +++ b/services/src/test/resources/org/keycloak/keystore/intermediate-ca.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBVDCB+6ADAgECAggXjHM8wGbICTAKBggqhkjOPQQDAjASMRAwDgYDVQQDEwdy +b290LWNhMB4XDTIzMTAwOTEzMjQyNFoXDTI0MTAwODEzMjQyNFowGjEYMBYGA1UE +AxMPaW50ZXJtZWRpYXRlLWNhMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtGRD +/KJfkyN2e8ljU4yJ2DCm3lfKIEG7XxyyYfTk4AV61mZ0LHZkHL5VmpBS05ubzPMH +fC29fFk3AHR5/G9if6MzMDEwDgYDVR0PAQH/BAQDAgWgMB8GA1UdIwQYMBaAFOqV +hVTxdWOGZhcicVoHmskuEzCxMAoGCCqGSM49BAMCA0gAMEUCIE2cHFGfUhPpD5nA +Y9cN4fyu2W9PTn1wyhfsF0EV24TIAiEAypFQGe13SElvcWCm4K8jLfiW/LIYM8Ys +u3M3qCYH/ho= +-----END CERTIFICATE----- diff --git a/services/src/test/resources/org/keycloak/keystore/keycloak.jks b/services/src/test/resources/org/keycloak/keystore/keycloak.jks new file mode 100644 index 000000000000..81570ab52922 Binary files /dev/null and b/services/src/test/resources/org/keycloak/keystore/keycloak.jks differ diff --git a/services/src/test/resources/org/keycloak/keystore/keycloak.p12 b/services/src/test/resources/org/keycloak/keystore/keycloak.p12 new file mode 100644 index 000000000000..1b33a6aa4a43 Binary files /dev/null and b/services/src/test/resources/org/keycloak/keystore/keycloak.p12 differ diff --git a/services/src/test/resources/org/keycloak/keystore/root-ca-key.pem b/services/src/test/resources/org/keycloak/keystore/root-ca-key.pem new file mode 100644 index 000000000000..240b782f1f8e --- /dev/null +++ b/services/src/test/resources/org/keycloak/keystore/root-ca-key.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgTd6NT/y8PEMmit25 +E7tzQPXcYsXm2pyicwFGPjdN/QmhRANCAAQ0C5N5+u1TF06nG5ZnHkShGiOjYDx9 +ON9VoBOOnuJabRTCqt7/r+ZFMKmeo6fYkC4q+yXXXRxKyNt26+CILXL2 +-----END PRIVATE KEY----- diff --git a/services/src/test/resources/org/keycloak/keystore/root-ca.pem b/services/src/test/resources/org/keycloak/keystore/root-ca.pem new file mode 100644 index 000000000000..7515b289913e --- /dev/null +++ b/services/src/test/resources/org/keycloak/keystore/root-ca.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBWzCCAQKgAwIBAgIIF4xzPMBcK/QwCgYIKoZIzj0EAwIwEjEQMA4GA1UEAxMH +cm9vdC1jYTAeFw0yMzEwMDkxMzI0MjRaFw0yNDEwMDgxMzI0MjRaMBIxEDAOBgNV +BAMTB3Jvb3QtY2EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQ0C5N5+u1TF06n +G5ZnHkShGiOjYDx9ON9VoBOOnuJabRTCqt7/r+ZFMKmeo6fYkC4q+yXXXRxKyNt2 +6+CILXL2o0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQU6pWFVPF1Y4ZmFyJxWgeayS4TMLEwCgYIKoZIzj0EAwIDRwAwRAIgXpD+ +QcwwDMvwLUWNovwQZ78/ljXdfCmlFHIIYVKzegYCIE1Z9ImCnT6zXZGM/L98w+TC +PM/gSqk3ATsDewVggI69 +-----END CERTIFICATE----- diff --git a/services/src/test/resources/org/keycloak/keystore/rsa-leaf-key.pem b/services/src/test/resources/org/keycloak/keystore/rsa-leaf-key.pem new file mode 100644 index 000000000000..1b8cc883ac69 --- /dev/null +++ b/services/src/test/resources/org/keycloak/keystore/rsa-leaf-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC2OK/1oUFE4WbR +q0GB9STxvnmvathWHfY2UxHRA3ku1keZNi2a3Yl6kphWxEAaIF62rtZ4sl+yFbV7 +3VsX+5M+v66udtY//DbHQBfQzENtM/iGLfIB4cVUwi6QjKazlp4N5UmAUjhQNt+s +GojrOV2sgK/g9GZdJKVOs24CVvmYgt94zVzkJBp09oxtUKRx1/OajXhHx39sAv2/ +Wsdai+bIy1xCkee5dUcGvVVH1+AwBoduq60RzjjrwhIXaVstIMp/i9hfthaLUaN1 +jJ65e6xyBq/ELNKiApIpFSEwql37E4GCqOZ3gXxkoI/ZearwB0tpM4Iw3slyJ4c3 +m4jeOXNzAgMBAAECggEAcvq4BOzYa2cy4rAGKMOPqT6AN/DaSxSizEqEXnermqM5 +EHMAOcz6N66Sk4VH0XSFbw5JZGVhPtS17E+TcEGc+qYmyg1QSZWS1w0ZOfX5wcle +UwRgxVE5m67fhPRLJ9ytJKV+SIsY33yQjF897/cFpZiZ4f81LCa4bD2J9837toAl +QIsYhPouZsl44OtIO+zvzVkTN+g1bo5TzS+34GhzsCJwCIFAmiC8u6BqGywrcAcs +bQEB+GdcHOVY4MLvNe+FMXSvaJxB/XyiBznaiv71MCpnXd6yJRKsSoCCkUT1rcET +6a6Zd8GjL4raxRQDXHzVde0ZOZ9dmLq2IcnQt/KP4QKBgQDhmGS2VjYdu+2oWJUV +ouJj9PX9hNFQmV3m0R7MVEbg66EWYdQ12dMqUvhYrzVewmj2UBiewHK9KahIqmsP +7czVSuhFWnXXmpJ5cAboKW+O2mzLzgNSkjc6oKqQ0wGjiQjh/jsmn0Rhd/16Hb6k +ecxC9zZ2U5GfFqxBmdkHN0TtIwKBgQDOx8lfSBOkbSnaDlSgMLuIN2MqWhiswn0o +cM2jjOzy179g9+Rff8HdVTzxlFr5v11kbBP+dHZUMUZhDsYFgtNlk90SzhttN300 +znUnmqFSA3GniiK9cWV/gSfqXCn1HAGJkSDveqrFpc9jnIzCmOvpRVg1TltUBP80 ++Qph5VYNcQKBgQCw8wTEFJk792Uxb9H4d5vVxZWRdjVsX+/MlWjv4ob58ziWQsPe +PW4pu4y6mytkmV8VHLxDATa+c0dhP/LcOq04/Bl2FL9sDv0nLZztS1sKlP9TWNM4 +WCHj5ZQEJs4ZROpnwS8KW1CalzCGMYCQqEx0lk3swB3vyCvzBMa7pzl0OwKBgQC/ +NNzPf32ySMMvgJreOdHfRqArFH7GmV1UXOK6fmDIuZmdF/yUFjX07ZHaLX3mfP7m +Ek52t8iHV70lmJyhkfTac900O2P/HRVpFBQfGAxeIxMggkpXdRkOyyZWvvyJS2lc +hNe4umxZMVbRXHyIoRFcdiP3vsoSqkA3AqaIGaH18QKBgQCmltt/oH9ccUPDTSZi +42AExmcfcdLLwLu2vcvF7xKbeexqEre/Tb3fd30a+LNe4h22CKuFpyKOcCf4yjbr +w3h2Ovtj4UyTWfMSDKygu8TpGRqBlr5tY7u9HLkXlGCLqjhUKCbkahvM//mtNGQr +EuU9uzXBBEwHNud+QXwyWaP6rA== +-----END PRIVATE KEY----- diff --git a/services/src/test/resources/org/keycloak/keystore/rsa-leaf.pem b/services/src/test/resources/org/keycloak/keystore/rsa-leaf.pem new file mode 100644 index 000000000000..2ac4f3caa395 --- /dev/null +++ b/services/src/test/resources/org/keycloak/keystore/rsa-leaf.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIICADCCAaagAwIBAgIIF4xzPMBu2CMwCgYIKoZIzj0EAwIwGjEYMBYGA1UEAxMP +aW50ZXJtZWRpYXRlLWNhMB4XDTIzMTAwOTEzMjQyNFoXDTI0MTAwODEzMjQyNFow +EzERMA8GA1UEAxMIcnNhLWxlYWYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC2OK/1oUFE4WbRq0GB9STxvnmvathWHfY2UxHRA3ku1keZNi2a3Yl6kphW +xEAaIF62rtZ4sl+yFbV73VsX+5M+v66udtY//DbHQBfQzENtM/iGLfIB4cVUwi6Q +jKazlp4N5UmAUjhQNt+sGojrOV2sgK/g9GZdJKVOs24CVvmYgt94zVzkJBp09oxt +UKRx1/OajXhHx39sAv2/Wsdai+bIy1xCkee5dUcGvVVH1+AwBoduq60RzjjrwhIX +aVstIMp/i9hfthaLUaN1jJ65e6xyBq/ELNKiApIpFSEwql37E4GCqOZ3gXxkoI/Z +earwB0tpM4Iw3slyJ4c3m4jeOXNzAgMBAAGjEjAQMA4GA1UdDwEB/wQEAwIFoDAK +BggqhkjOPQQDAgNIADBFAiEA6vjLmUbFW7FJxBdjcbIai3TmgbR+OnZ5NnMUs3eD +KskCIAhBg//xN7Q7Obh3YBDYqRrHPSOyNS5eRJmsgiUzZYza +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBVDCB+6ADAgECAggXjHM8wGbICTAKBggqhkjOPQQDAjASMRAwDgYDVQQDEwdy +b290LWNhMB4XDTIzMTAwOTEzMjQyNFoXDTI0MTAwODEzMjQyNFowGjEYMBYGA1UE +AxMPaW50ZXJtZWRpYXRlLWNhMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtGRD +/KJfkyN2e8ljU4yJ2DCm3lfKIEG7XxyyYfTk4AV61mZ0LHZkHL5VmpBS05ubzPMH +fC29fFk3AHR5/G9if6MzMDEwDgYDVR0PAQH/BAQDAgWgMB8GA1UdIwQYMBaAFOqV +hVTxdWOGZhcicVoHmskuEzCxMAoGCCqGSM49BAMCA0gAMEUCIE2cHFGfUhPpD5nA +Y9cN4fyu2W9PTn1wyhfsF0EV24TIAiEAypFQGe13SElvcWCm4K8jLfiW/LIYM8Ys +u3M3qCYH/ho= +-----END CERTIFICATE----- diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java index c99534bb9934..f6f25c043c6e 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java @@ -556,8 +556,9 @@ public String createUser(String realm, String username, String password, String return ApiUtil.createUserWithAdminClient(adminClient.realm(realm), homer); } - public static UserRepresentation createUserRepresentation(String username, String email, String firstName, String lastName, List groups, boolean enabled) { + public static UserRepresentation createUserRepresentation(String id, String username, String email, String firstName, String lastName, List groups, boolean enabled) { UserRepresentation user = new UserRepresentation(); + user.setId(id); user.setUsername(username); user.setEmail(email); user.setFirstName(firstName); @@ -567,6 +568,10 @@ public static UserRepresentation createUserRepresentation(String username, Strin return user; } + public static UserRepresentation createUserRepresentation(String username, String email, String firstName, String lastName, List groups, boolean enabled) { + return createUserRepresentation(null, username, email, firstName, lastName, groups, enabled); + } + public static UserRepresentation createUserRepresentation(String username, String email, String firstName, String lastName, boolean enabled) { return createUserRepresentation(username, email, firstName, lastName, null, enabled); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/event/AdminEventTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/event/AdminEventTest.java index b99518cca88d..23d08e05d83f 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/event/AdminEventTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/event/AdminEventTest.java @@ -17,6 +17,7 @@ package org.keycloak.testsuite.admin.event; +import org.apache.commons.lang3.RandomStringUtils; import org.junit.Before; import org.junit.Test; import org.keycloak.admin.client.resource.RealmResource; @@ -45,6 +46,7 @@ import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; import static org.junit.Assert.assertNull; import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER; @@ -167,6 +169,23 @@ public void defaultMaxResults() { assertThat(realm.getAdminEvents(null, null, null, null, null, null, null, null, 0, 1000).size(), is(greaterThanOrEqualTo(110))); } + @Test + public void adminEventRepresentationLenght() { + RealmResource realm = adminClient.realms().realm("test"); + AdminEventRepresentation event = new AdminEventRepresentation(); + event.setOperationType(OperationType.CREATE.toString()); + event.setAuthDetails(new AuthDetailsRepresentation()); + event.setRealmId(realm.toRepresentation().getId()); + String longValue = RandomStringUtils.random(30000, true, true); + event.setRepresentation(longValue); + + testingClient.testing("test").onAdminEvent(event, true); + List adminEvents = realm.getAdminEvents(null, null, null, null, null, null, null, null, null, null); + + assertThat(adminEvents, hasSize(1)); + assertThat(adminEvents.get(0).getRepresentation(), equalTo(longValue)); + } + @Test public void orderResultsTest() { RealmResource realm = adminClient.realms().realm("test"); 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 e5cd2344ea05..b1c6503d25de 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 @@ -57,6 +57,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.CoreMatchers.startsWith; @@ -247,6 +248,17 @@ private void addUsers() { piRep.setUsers(users); } + private void addUsersWithIds() { + List users = new ArrayList<>(); + + for (int i = 0; i < NUM_ENTITIES; i++) { + UserRepresentation user = createUserRepresentation(UUID.randomUUID().toString(), USER_PREFIX + i, USER_PREFIX + i + "@foo.com", "foo", "bar", null, true); + users.add(user); + } + + piRep.setUsers(users); + } + private void addUsersWithTermsAndConditions() { List users = new ArrayList<>(); List requiredActions = new ArrayList<>(); @@ -418,6 +430,46 @@ public void testAddUsers() { } } + @Test + public void testAddUsersWithIds() { + assertAdminEvents.clear(); + + setFail(); + addUsersWithIds(); + + Set userRepIds = new HashSet<>(); + for (UserRepresentation userRep : piRep.getUsers()) { + userRepIds.add(userRep.getId()); + } + + PartialImportResults results = doImport(); + assertEquals(NUM_ENTITIES, results.getAdded()); + + // Need to do this way as admin events from partial import are unsorted + Set userIds = new HashSet<>(); + for (int i=0 ; i createProviderClients() { + List clientsRepList = super.createProviderClients(); + log.info("Update provider clients to accept JWT authentication"); + KeyMetadataRepresentation keyRep = KeyUtils.findActiveSigningKey(adminClient.realm(consumerRealmName()), Algorithm.RS256); + for (ClientRepresentation client: clientsRepList) { + client.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID); + if (client.getAttributes() == null) { + client.setAttributes(new HashMap()); + } + client.getAttributes().put(JWTClientAuthenticator.CERTIFICATE_ATTR, keyRep.getCertificate()); + } + return clientsRepList; + } + + @Override + public IdentityProviderRepresentation setUpIdentityProvider(IdentityProviderSyncMode syncMode) { + IdentityProviderRepresentation idp = createIdentityProvider(IDP_OIDC_ALIAS, IDP_OIDC_PROVIDER_ID); + Map config = idp.getConfig(); + applyDefaultConfiguration(config, syncMode); + config.put("clientSecret", null); + config.put("clientAuthMethod", OIDCLoginProtocol.PRIVATE_KEY_JWT); + config.put("clientAssertionAudience", "https://localhost:8543/auth/realms/provider"); + return idp; + } + + } + +} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java index 0fc416d6a2db..2ce9fd6a1a98 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java @@ -235,8 +235,9 @@ public void testSuccess_postMethod_charset_body() throws Exception { @Test public void testSuccess_dotsInClientId() throws Exception { // Create client with dot in the name + final String clientId = "my.foo.$\\client\\$"; ClientRepresentation clientRep = org.keycloak.testsuite.util.ClientBuilder.create() - .clientId("my.foo.client") + .clientId(clientId) .addRedirectUri("http://foo.host") .secret("password") .directAccessGrants() @@ -258,11 +259,11 @@ public void testSuccess_dotsInClientId() throws Exception { userResource.roles().clientLevel(clientUUID).add(Collections.singletonList(fooRole)); // Login to the new client - OAuthClient.AccessTokenResponse accessTokenResponse = oauth.clientId("my.foo.client") + OAuthClient.AccessTokenResponse accessTokenResponse = oauth.clientId(clientId) .doGrantAccessTokenRequest("password", "test-user@localhost", "password"); AccessToken accessToken = oauth.verifyToken(accessTokenResponse.getAccessToken()); - Assert.assertNames(accessToken.getResourceAccess("my.foo.client").getRoles(), "my.foo.role"); + Assert.assertNames(accessToken.getResourceAccess(clientId).getRoles(), "my.foo.role"); events.clear(); @@ -271,7 +272,7 @@ public void testSuccess_dotsInClientId() throws Exception { try { Response response = UserInfoClientUtil.executeUserInfoRequest_getMethod(client, accessTokenResponse.getAccessToken()); - testSuccessfulUserInfoResponse(response, "my.foo.client"); + testSuccessfulUserInfoResponse(response, clientId); } finally { client.close(); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/user/profile/UserProfileTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/user/profile/UserProfileTest.java index c8f474492502..e1df4317faa2 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/user/profile/UserProfileTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/user/profile/UserProfileTest.java @@ -1665,4 +1665,44 @@ private static void assertRemoveEmptyRootAttribute(Map> att assertNull(user.getEmail()); assertEquals(upAttributes.getFirstValue(UserModel.FIRST_NAME), attributes.get(UserModel.FIRST_NAME).get(0)); } + + @Test + public void testRemoveOptionalAttributesFromDefaultConfigIfNotSet() { + getTestingClient().server(TEST_REALM_NAME).run((RunOnServer) UserProfileTest::testRemoveOptionalAttributesFromDefaultConfigIfNotSet); + } + + private static void testRemoveOptionalAttributesFromDefaultConfigIfNotSet(KeycloakSession session) throws IOException { + UPConfig config = new UPConfig(); + UPAttribute attribute = new UPAttribute(); + + attribute.setName("foo"); + + config.addAttribute(attribute); + + UserProfileProvider provider = getUserProfileProvider(session); + provider.setConfiguration(JsonSerialization.writeValueAsString(config)); + + Map attributes = new HashMap<>(); + + attributes.put(UserModel.USERNAME, org.keycloak.models.utils.KeycloakModelUtils.generateId() + "@keycloak.org"); + attributes.put(UserModel.EMAIL, org.keycloak.models.utils.KeycloakModelUtils.generateId() + "@keycloak.org"); + attributes.put("foo", "foo"); + + UserProfile profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes); + UserModel user = profile.create(); + + assertFalse(profile.getAttributes().contains(UserModel.FIRST_NAME)); + assertFalse(profile.getAttributes().contains(UserModel.LAST_NAME)); + + UPAttribute firstName = new UPAttribute(); + firstName.setName(UserModel.FIRST_NAME); + config.addAttribute(firstName); + UPAttribute lastName = new UPAttribute(); + lastName.setName(UserModel.LAST_NAME); + config.addAttribute(lastName); + provider.setConfiguration(JsonSerialization.writeValueAsString(config)); + profile = provider.create(UserProfileContext.UPDATE_PROFILE, attributes, user); + assertTrue(profile.getAttributes().contains(UserModel.FIRST_NAME)); + assertTrue(profile.getAttributes().contains(UserModel.LAST_NAME)); + } } diff --git a/server-spi-private/src/test/java/org/keycloak/validate/BuiltinValidatorsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/validation/BuiltinValidatorsTest.java similarity index 81% rename from server-spi-private/src/test/java/org/keycloak/validate/BuiltinValidatorsTest.java rename to testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/validation/BuiltinValidatorsTest.java index 7bbacd4fc393..5834f8953cdd 100644 --- a/server-spi-private/src/test/java/org/keycloak/validate/BuiltinValidatorsTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/validation/BuiltinValidatorsTest.java @@ -1,4 +1,23 @@ -package org.keycloak.validate; +/* + * 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.validation; import static org.keycloak.validate.ValidatorConfig.configFromMap; @@ -11,24 +30,39 @@ import org.junit.Assert; import org.junit.Test; +import org.keycloak.models.KeycloakSession; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.arquillian.annotation.ModelTest; +import org.keycloak.validate.AbstractSimpleValidator; +import org.keycloak.validate.ValidationContext; +import org.keycloak.validate.ValidationError; +import org.keycloak.validate.ValidationResult; +import org.keycloak.validate.Validator; +import org.keycloak.validate.ValidatorConfig; import org.keycloak.validate.validators.DoubleValidator; import org.keycloak.validate.validators.EmailValidator; import org.keycloak.validate.validators.IntegerValidator; import org.keycloak.validate.validators.LengthValidator; import org.keycloak.validate.validators.OptionsValidator; import org.keycloak.validate.validators.PatternValidator; +import org.keycloak.validate.BuiltinValidators; import org.keycloak.validate.validators.UriValidator; import com.google.common.collect.ImmutableMap; -public class BuiltinValidatorsTest { +public class BuiltinValidatorsTest extends AbstractKeycloakTest { private static final ValidatorConfig valConfigIgnoreEmptyValues = ValidatorConfig.builder().config(AbstractSimpleValidator.IGNORE_EMPTY_VALUE, true).build(); + @Override + public void addTestRealms(List testRealms) { + } + @Test public void testLengthValidator() { - Validator validator = Validators.lengthValidator(); + Validator validator = BuiltinValidators.lengthValidator(); // null and empty values handling Assert.assertFalse(validator.validate(null, "name", configFromMap(ImmutableMap.of(LengthValidator.KEY_MIN, 1))).isValid()); @@ -84,12 +118,13 @@ public void testLengthValidator() { } @Test - public void testLengthValidator_ConfigValidation() { + @ModelTest + public void testLengthValidator_ConfigValidation(KeycloakSession session) { // invalid min and max config values ValidatorConfig config = new ValidatorConfig(ImmutableMap.of(LengthValidator.KEY_MIN, new Object(), LengthValidator.KEY_MAX, "invalid")); - ValidationResult result = Validators.validatorConfigValidator().validate(config, LengthValidator.ID).toResult(); + ValidationResult result = BuiltinValidators.validatorConfigValidator().validate(config, LengthValidator.ID, new ValidationContext(session)).toResult(); Assert.assertFalse(result.isValid()); ValidationError[] errors = result.getErrors().toArray(new ValidationError[0]); @@ -105,25 +140,25 @@ public void testLengthValidator_ConfigValidation() { Assert.assertEquals(LengthValidator.KEY_MAX, error1.getInputHint()); // empty config - result = Validators.validatorConfigValidator().validate(null, LengthValidator.ID).toResult(); + result = BuiltinValidators.validatorConfigValidator().validate(null, LengthValidator.ID, new ValidationContext(session)).toResult(); Assert.assertEquals(2, result.getErrors().size()); - result = Validators.validatorConfigValidator().validate(ValidatorConfig.EMPTY, LengthValidator.ID).toResult(); + result = BuiltinValidators.validatorConfigValidator().validate(ValidatorConfig.EMPTY, LengthValidator.ID, new ValidationContext(session)).toResult(); Assert.assertEquals(2, result.getErrors().size()); // correct config - Assert.assertTrue(Validators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(LengthValidator.KEY_MIN, "10")), LengthValidator.ID).toResult().isValid()); - Assert.assertTrue(Validators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(LengthValidator.KEY_MAX, "10")), LengthValidator.ID).toResult().isValid()); - Assert.assertTrue(Validators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(LengthValidator.KEY_MIN, "10", LengthValidator.KEY_MAX, "10")), LengthValidator.ID).toResult().isValid()); + Assert.assertTrue(BuiltinValidators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(LengthValidator.KEY_MIN, "10")), LengthValidator.ID, new ValidationContext(session)).toResult().isValid()); + Assert.assertTrue(BuiltinValidators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(LengthValidator.KEY_MAX, "10")), LengthValidator.ID, new ValidationContext(session)).toResult().isValid()); + Assert.assertTrue(BuiltinValidators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(LengthValidator.KEY_MIN, "10", LengthValidator.KEY_MAX, "10")), LengthValidator.ID, new ValidationContext(session)).toResult().isValid()); // max is smaller than min - Assert.assertFalse(Validators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(LengthValidator.KEY_MIN, "10", LengthValidator.KEY_MAX, "9")), LengthValidator.ID).toResult().isValid()); + Assert.assertFalse(BuiltinValidators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(LengthValidator.KEY_MIN, "10", LengthValidator.KEY_MAX, "9")), LengthValidator.ID, new ValidationContext(session)).toResult().isValid()); } @Test public void testEmailValidator() { // this also validates StringFormatValidatorBase for simple values - Validator validator = Validators.emailValidator(); + Validator validator = BuiltinValidators.emailValidator(); Assert.assertFalse(validator.validate(null, "email").isValid()); Assert.assertFalse(validator.validate("", "email").isValid()); @@ -156,7 +191,7 @@ public void testEmailValidator() { @Test public void testAbstractSimpleValidatorSupportForCollections() { - Validator validator = Validators.emailValidator(); + Validator validator = BuiltinValidators.emailValidator(); List valuesCollection = new ArrayList<>(); @@ -180,7 +215,7 @@ public void testAbstractSimpleValidatorSupportForCollections() { @Test public void testNotBlankValidator() { - Validator validator = Validators.notBlankValidator(); + Validator validator = BuiltinValidators.notBlankValidator(); // simple String value Assert.assertTrue(validator.validate("tester", "username").isValid()); @@ -203,7 +238,7 @@ public void testNotBlankValidator() { @Test public void testNotEmptyValidator() { - Validator validator = Validators.notEmptyValidator(); + Validator validator = BuiltinValidators.notEmptyValidator(); Assert.assertTrue(validator.validate("tester", "username").isValid()); Assert.assertTrue(validator.validate(" ", "username").isValid()); @@ -221,7 +256,7 @@ public void testNotEmptyValidator() { @Test public void testDoubleValidator() { - Validator validator = Validators.doubleValidator(); + Validator validator = BuiltinValidators.doubleValidator(); // null value and empty String Assert.assertFalse(validator.validate(null, "null").isValid()); @@ -285,12 +320,13 @@ public void testDoubleValidator() { } @Test - public void testDoubleValidator_ConfigValidation() { + @ModelTest + public void testDoubleValidator_ConfigValidation(KeycloakSession session) { // invalid min and max config values ValidatorConfig config = new ValidatorConfig(ImmutableMap.of(DoubleValidator.KEY_MIN, new Object(), DoubleValidator.KEY_MAX, "invalid")); - ValidationResult result = Validators.validatorConfigValidator().validate(config, DoubleValidator.ID).toResult(); + ValidationResult result = BuiltinValidators.validatorConfigValidator().validate(config, DoubleValidator.ID, new ValidationContext(session)).toResult(); Assert.assertFalse(result.isValid()); ValidationError[] errors = result.getErrors().toArray(new ValidationError[0]); @@ -306,23 +342,23 @@ public void testDoubleValidator_ConfigValidation() { Assert.assertEquals(DoubleValidator.KEY_MAX, error1.getInputHint()); // empty config - result = Validators.validatorConfigValidator().validate(null, DoubleValidator.ID).toResult(); + result = BuiltinValidators.validatorConfigValidator().validate(null, DoubleValidator.ID, new ValidationContext(session)).toResult(); Assert.assertEquals(0, result.getErrors().size()); - result = Validators.validatorConfigValidator().validate(ValidatorConfig.EMPTY, DoubleValidator.ID).toResult(); + result = BuiltinValidators.validatorConfigValidator().validate(ValidatorConfig.EMPTY, DoubleValidator.ID, new ValidationContext(session)).toResult(); Assert.assertEquals(0, result.getErrors().size()); // correct config - Assert.assertTrue(Validators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(DoubleValidator.KEY_MIN, "10.1")), DoubleValidator.ID).toResult().isValid()); - Assert.assertTrue(Validators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(DoubleValidator.KEY_MAX, "10.1")), DoubleValidator.ID).toResult().isValid()); - Assert.assertTrue(Validators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(DoubleValidator.KEY_MIN, "10.1", DoubleValidator.KEY_MAX, "11")), DoubleValidator.ID).toResult().isValid()); + Assert.assertTrue(BuiltinValidators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(DoubleValidator.KEY_MIN, "10.1")), DoubleValidator.ID, new ValidationContext(session)).toResult().isValid()); + Assert.assertTrue(BuiltinValidators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(DoubleValidator.KEY_MAX, "10.1")), DoubleValidator.ID, new ValidationContext(session)).toResult().isValid()); + Assert.assertTrue(BuiltinValidators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(DoubleValidator.KEY_MIN, "10.1", DoubleValidator.KEY_MAX, "11")), DoubleValidator.ID, new ValidationContext(session)).toResult().isValid()); // max is smaller than min - Assert.assertFalse(Validators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(DoubleValidator.KEY_MIN, "10.1", DoubleValidator.KEY_MAX, "10.1")), DoubleValidator.ID).toResult().isValid()); + Assert.assertFalse(BuiltinValidators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(DoubleValidator.KEY_MIN, "10.1", DoubleValidator.KEY_MAX, "10.1")), DoubleValidator.ID, new ValidationContext(session)).toResult().isValid()); } @Test public void testIntegerValidator() { - Validator validator = Validators.integerValidator(); + Validator validator = BuiltinValidators.integerValidator(); // null value and empty String Assert.assertFalse(validator.validate(null, "null").isValid()); @@ -387,12 +423,13 @@ public void testIntegerValidator() { } @Test - public void testIntegerValidator_ConfigValidation() { + @ModelTest + public void testIntegerValidator_ConfigValidation(KeycloakSession session) { // invalid min and max config values ValidatorConfig config = new ValidatorConfig(ImmutableMap.of(IntegerValidator.KEY_MIN, new Object(), IntegerValidator.KEY_MAX, "invalid")); - ValidationResult result = Validators.validatorConfigValidator().validate(config, IntegerValidator.ID).toResult(); + ValidationResult result = BuiltinValidators.validatorConfigValidator().validate(config, IntegerValidator.ID, new ValidationContext(session)).toResult(); Assert.assertFalse(result.isValid()); ValidationError[] errors = result.getErrors().toArray(new ValidationError[0]); @@ -408,24 +445,24 @@ public void testIntegerValidator_ConfigValidation() { Assert.assertEquals(IntegerValidator.KEY_MAX, error1.getInputHint()); // empty config - result = Validators.validatorConfigValidator().validate(null, IntegerValidator.ID).toResult(); + result = BuiltinValidators.validatorConfigValidator().validate(null, IntegerValidator.ID, new ValidationContext(session)).toResult(); Assert.assertEquals(0, result.getErrors().size()); - result = Validators.validatorConfigValidator().validate(ValidatorConfig.EMPTY, IntegerValidator.ID).toResult(); + result = BuiltinValidators.validatorConfigValidator().validate(ValidatorConfig.EMPTY, IntegerValidator.ID, new ValidationContext(session)).toResult(); Assert.assertEquals(0, result.getErrors().size()); // correct config - Assert.assertTrue(Validators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(IntegerValidator.KEY_MIN, "10")), IntegerValidator.ID).toResult().isValid()); - Assert.assertTrue(Validators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(IntegerValidator.KEY_MAX, "10")), IntegerValidator.ID).toResult().isValid()); - Assert.assertTrue(Validators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(IntegerValidator.KEY_MIN, "10", IntegerValidator.KEY_MAX, "11")), IntegerValidator.ID).toResult().isValid()); + Assert.assertTrue(BuiltinValidators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(IntegerValidator.KEY_MIN, "10")), IntegerValidator.ID, new ValidationContext(session)).toResult().isValid()); + Assert.assertTrue(BuiltinValidators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(IntegerValidator.KEY_MAX, "10")), IntegerValidator.ID, new ValidationContext(session)).toResult().isValid()); + Assert.assertTrue(BuiltinValidators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(IntegerValidator.KEY_MIN, "10", IntegerValidator.KEY_MAX, "11")), IntegerValidator.ID, new ValidationContext(session)).toResult().isValid()); // max is smaller than min - Assert.assertFalse(Validators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(IntegerValidator.KEY_MIN, "10", IntegerValidator.KEY_MAX, "10")), IntegerValidator.ID).toResult().isValid()); + Assert.assertFalse(BuiltinValidators.validatorConfigValidator().validate(new ValidatorConfig(ImmutableMap.of(IntegerValidator.KEY_MIN, "10", IntegerValidator.KEY_MAX, "10")), IntegerValidator.ID, new ValidationContext(session)).toResult().isValid()); } @Test public void testPatternValidator() { - Validator validator = Validators.patternValidator(); + Validator validator = BuiltinValidators.patternValidator(); // Pattern object in the configuration ValidatorConfig config = configFromMap(Collections.singletonMap(PatternValidator.CFG_PATTERN, Pattern.compile("^start-.*-end$"))); @@ -456,7 +493,7 @@ public void testPatternValidator() { @Test public void testUriValidator() throws Exception { - Validator validator = Validators.uriValidator(); + Validator validator = BuiltinValidators.uriValidator(); Assert.assertTrue(validator.validate(null, "baseUrl").isValid()); Assert.assertTrue(validator.validate("", "baseUrl").isValid()); @@ -472,14 +509,14 @@ public void testUriValidator() throws Exception { Assert.assertFalse(validator.validate("https://localhost:3000/#someFragment", "baseUrl", config).isValid()); // it is also possible to call dedicated validation methods on a built-in validator - Assert.assertTrue(Validators.uriValidator().validateUri(new URI("https://customurl"), Collections.singleton("https"), true, true)); + Assert.assertTrue(BuiltinValidators.uriValidator().validateUri(new URI("https://customurl"), Collections.singleton("https"), true, true)); - Assert.assertFalse(Validators.uriValidator().validateUri(new URI("http://customurl"), Collections.singleton("https"), true, true)); + Assert.assertFalse(BuiltinValidators.uriValidator().validateUri(new URI("http://customurl"), Collections.singleton("https"), true, true)); } @Test public void testOptionsValidator(){ - Validator validator = Validators.optionsValidator(); + Validator validator = BuiltinValidators.optionsValidator(); // options not configured - always invalid Assert.assertFalse(validator.validate(null, "test", ValidatorConfig.builder().config(OptionsValidator.KEY_OPTIONS, null).build()).isValid()); @@ -517,16 +554,17 @@ public void testOptionsValidator(){ } @Test - public void testOptionsValidator_Config_Validation() { + @ModelTest + public void testOptionsValidator_Config_Validation(KeycloakSession session) { - ValidationResult result = Validators.validatorConfigValidator().validate(ValidatorConfig.builder().build(), OptionsValidator.ID).toResult(); + ValidationResult result = BuiltinValidators.validatorConfigValidator().validate(ValidatorConfig.builder().build(), OptionsValidator.ID, new ValidationContext(session)).toResult(); Assert.assertFalse(result.isValid()); // invalid type of the config value - result = Validators.validatorConfigValidator().validate(ValidatorConfig.builder().config(OptionsValidator.KEY_OPTIONS, "a").build(), OptionsValidator.ID).toResult(); + result = BuiltinValidators.validatorConfigValidator().validate(ValidatorConfig.builder().config(OptionsValidator.KEY_OPTIONS, "a").build(), OptionsValidator.ID, new ValidationContext(session)).toResult(); Assert.assertFalse(result.isValid()); - result = Validators.validatorConfigValidator().validate(ValidatorConfig.builder().config(OptionsValidator.KEY_OPTIONS, Arrays.asList("opt1")).build(), OptionsValidator.ID).toResult(); + result = BuiltinValidators.validatorConfigValidator().validate(ValidatorConfig.builder().config(OptionsValidator.KEY_OPTIONS, Arrays.asList("opt1")).build(), OptionsValidator.ID, new ValidationContext(session)).toResult(); Assert.assertTrue(result.isValid()); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/validation/ValidatorTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/validation/ValidatorTest.java index b0404723a72f..39f58172af9c 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/validation/ValidatorTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/validation/ValidatorTest.java @@ -25,7 +25,6 @@ import java.util.Collections; import java.util.Locale; -import org.junit.Assert; import org.junit.Test; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; @@ -34,7 +33,7 @@ import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; import org.keycloak.testsuite.runonserver.RunOnServer; import org.keycloak.validate.ValidationContext; -import org.keycloak.validate.Validators; +import org.keycloak.validate.BuiltinValidators; /** * @author Pedro Igor @@ -51,21 +50,21 @@ public void testDateValidator() { } private static void testDateValidator(KeycloakSession session) { - assertTrue(Validators.dateValidator().validate(null, new ValidationContext(session)).isValid()); - assertTrue(Validators.dateValidator().validate("", new ValidationContext(session)).isValid()); + assertTrue(BuiltinValidators.dateValidator().validate(null, new ValidationContext(session)).isValid()); + assertTrue(BuiltinValidators.dateValidator().validate("", new ValidationContext(session)).isValid()); // defaults to Locale.ENGLISH as per default locale selector - assertFalse(Validators.dateValidator().validate("13/12/2021", new ValidationContext(session)).isValid()); - assertFalse(Validators.dateValidator().validate("13/12/21", new ValidationContext(session)).isValid()); - assertTrue(Validators.dateValidator().validate("12/13/2021", new ValidationContext(session)).isValid()); + assertFalse(BuiltinValidators.dateValidator().validate("13/12/2021", new ValidationContext(session)).isValid()); + assertFalse(BuiltinValidators.dateValidator().validate("13/12/21", new ValidationContext(session)).isValid()); + assertTrue(BuiltinValidators.dateValidator().validate("12/13/2021", new ValidationContext(session)).isValid()); RealmModel realm = session.getContext().getRealm(); realm.setInternationalizationEnabled(true); realm.setDefaultLocale(Locale.FRANCE.getLanguage()); - assertTrue(Validators.dateValidator().validate("13/12/21", new ValidationContext(session)).isValid()); - assertTrue(Validators.dateValidator().validate("13/12/2021", new ValidationContext(session)).isValid()); - assertFalse(Validators.dateValidator().validate("12/13/2021", new ValidationContext(session)).isValid()); + assertTrue(BuiltinValidators.dateValidator().validate("13/12/21", new ValidationContext(session)).isValid()); + assertTrue(BuiltinValidators.dateValidator().validate("13/12/2021", new ValidationContext(session)).isValid()); + assertFalse(BuiltinValidators.dateValidator().validate("12/13/2021", new ValidationContext(session)).isValid()); UserModel alice = session.users().getUserByUsername(realm, "alice"); @@ -75,6 +74,6 @@ private static void testDateValidator(KeycloakSession session) { context.getAttributes().put(UserModel.class.getName(), alice); - assertFalse(Validators.dateValidator().validate("13/12/2021", context).isValid()); + assertFalse(BuiltinValidators.dateValidator().validate("13/12/2021", context).isValid()); } } diff --git a/server-spi-private/src/test/java/org/keycloak/validate/ValidatorTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/validation/ValidatorsTest.java similarity index 76% rename from server-spi-private/src/test/java/org/keycloak/validate/ValidatorTest.java rename to testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/validation/ValidatorsTest.java index f4d7952e14bf..1e8d1012470c 100644 --- a/server-spi-private/src/test/java/org/keycloak/validate/ValidatorTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/validation/ValidatorsTest.java @@ -1,4 +1,23 @@ -package org.keycloak.validate; +/* + * 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.validation; import static org.keycloak.validate.ValidatorConfig.configFromMap; @@ -14,28 +33,42 @@ import org.junit.Assert; import org.junit.Test; import org.keycloak.models.KeycloakSession; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.arquillian.annotation.ModelTest; +import org.keycloak.validate.SimpleValidator; +import org.keycloak.validate.ValidationContext; +import org.keycloak.validate.ValidationError; +import org.keycloak.validate.ValidationResult; +import org.keycloak.validate.Validator; +import org.keycloak.validate.ValidatorConfig; +import org.keycloak.validate.Validators; import org.keycloak.validate.validators.EmailValidator; import org.keycloak.validate.validators.LengthValidator; import org.keycloak.validate.validators.NotBlankValidator; +import org.keycloak.validate.BuiltinValidators; import org.keycloak.validate.validators.ValidatorConfigValidator; -public class ValidatorTest { +public class ValidatorsTest extends AbstractKeycloakTest { - KeycloakSession session = null; + @Override + public void addTestRealms(List testRealms) { + } @Test public void simpleValidation() { - Validator validator = Validators.notEmptyValidator(); + Validator validator = BuiltinValidators.notEmptyValidator(); Assert.assertTrue(validator.validate("a").isValid()); Assert.assertFalse(validator.validate("").isValid()); } @Test - public void simpleValidationWithContext() { + @ModelTest + public void simpleValidationWithContext(KeycloakSession session) { - Validator validator = Validators.lengthValidator(); + Validator validator = BuiltinValidators.lengthValidator(); ValidationContext context = new ValidationContext(session); validator.validate("a", "username", context); @@ -45,17 +78,19 @@ public void simpleValidationWithContext() { } @Test - public void simpleValidationFluent() { + @ModelTest + public void simpleValidationFluent(KeycloakSession session) { ValidationContext context = new ValidationContext(session); - ValidationResult result = Validators.lengthValidator().validate("a", "username", context).toResult(); + ValidationResult result = BuiltinValidators.lengthValidator().validate("a", "username", context).toResult(); Assert.assertTrue(result.isValid()); } @Test - public void simpleValidationLookup() { + @ModelTest + public void simpleValidationLookup(KeycloakSession session) { // later: session.validators().validator(LengthValidator.ID); Validator validator = Validators.validator(session, LengthValidator.ID); @@ -68,7 +103,8 @@ public void simpleValidationLookup() { } @Test - public void simpleValidationError() { + @ModelTest + public void simpleValidationError(KeycloakSession session) { Validator validator = LengthValidator.INSTANCE; @@ -104,20 +140,21 @@ public void simpleValidationError() { public void acceptOnError() { AtomicBoolean bool1 = new AtomicBoolean(); - Validators.notEmptyValidator().validate("a").toResult().ifNotValidAccept(r -> bool1.set(true)); + BuiltinValidators.notEmptyValidator().validate("a").toResult().ifNotValidAccept(r -> bool1.set(true)); Assert.assertFalse(bool1.get()); AtomicBoolean bool2 = new AtomicBoolean(); - Validators.notEmptyValidator().validate("").toResult().ifNotValidAccept(r -> bool2.set(true)); + BuiltinValidators.notEmptyValidator().validate("").toResult().ifNotValidAccept(r -> bool2.set(true)); Assert.assertTrue(bool2.get()); } @Test - public void forEachError() { + @ModelTest + public void forEachError(KeycloakSession session) { List errors = new ArrayList<>(); MockAddress faultyAddress = new MockAddress("", "Saint-Maur-des-Fossés", null, "Germany"); - MockAddressValidator.INSTANCE.validate(faultyAddress, "address").toResult().forEachError(e -> { + MockAddressValidator.INSTANCE.validate(faultyAddress, "address", new ValidationContext(session)).toResult().forEachError(e -> { errors.add(e.getMessage()); }); @@ -125,7 +162,8 @@ public void forEachError() { } @Test - public void formatError() { + @ModelTest + public void formatError(KeycloakSession session) { Map miniResourceBundle = new HashMap<>(); miniResourceBundle.put("error-invalid-blank", "{0} is blank: <{1}>"); @@ -133,7 +171,7 @@ public void formatError() { List errors = new ArrayList<>(); MockAddress faultyAddress = new MockAddress("", "Saint-Maur-des-Fossés", null, "Germany"); - MockAddressValidator.INSTANCE.validate(faultyAddress, "address").toResult().forEachError(e -> { + MockAddressValidator.INSTANCE.validate(faultyAddress, "address", new ValidationContext(session)).toResult().forEachError(e -> { errors.add(e.formatMessage((message, args) -> MessageFormat.format(miniResourceBundle.getOrDefault(message, message), args))); }); @@ -141,15 +179,16 @@ public void formatError() { } @Test - public void multipleValidations() { + @ModelTest + public void multipleValidations(KeycloakSession session) { ValidationContext context = new ValidationContext(session); String input = "aaa"; String inputHint = "username"; - Validators.lengthValidator().validate(input, inputHint, context); - Validators.notEmptyValidator().validate(input, inputHint, context); + BuiltinValidators.lengthValidator().validate(input, inputHint, context); + BuiltinValidators.notEmptyValidator().validate(input, inputHint, context); ValidationResult result = context.toResult(); @@ -157,15 +196,16 @@ public void multipleValidations() { } @Test - public void multipleValidationsError() { + @ModelTest + public void multipleValidationsError(KeycloakSession session) { ValidationContext context = new ValidationContext(session); String input = " "; String inputHint = "username"; - Validators.lengthValidator().validate(input, inputHint, context, configFromMap(Collections.singletonMap(LengthValidator.KEY_MIN, 1))); - Validators.notBlankValidator().validate(input, inputHint, context); + BuiltinValidators.lengthValidator().validate(input, inputHint, context, configFromMap(Collections.singletonMap(LengthValidator.KEY_MIN, 1))); + BuiltinValidators.notBlankValidator().validate(input, inputHint, context); ValidationResult result = context.toResult(); @@ -184,7 +224,8 @@ public void multipleValidationsError() { } @Test - public void validateValidatorConfigSimple() { + @ModelTest + public void validateValidatorConfigSimple(KeycloakSession session) { SimpleValidator validator = LengthValidator.INSTANCE; @@ -197,8 +238,9 @@ public void validateValidatorConfigSimple() { } @Test - public void validateEmailValidator() { - SimpleValidator validator = Validators.emailValidator(); + @ModelTest + public void validateEmailValidator(KeycloakSession session) { + SimpleValidator validator = BuiltinValidators.emailValidator(); Assert.assertTrue(validator.validateConfig(session, null).isValid()); Assert.assertTrue(validator.validateConfig(session, ValidatorConfig.EMPTY).isValid()); @@ -215,7 +257,8 @@ public void validateEmailValidator() { } @Test - public void validateValidatorConfigMultipleOptions() { + @ModelTest + public void validateValidatorConfigMultipleOptions(KeycloakSession session) { SimpleValidator validator = LengthValidator.INSTANCE; @@ -229,7 +272,8 @@ public void validateValidatorConfigMultipleOptions() { } @Test - public void validateValidatorConfigMultipleOptionsInvalidValues() { + @ModelTest + public void validateValidatorConfigMultipleOptionsInvalidValues(KeycloakSession session) { SimpleValidator validator = LengthValidator.INSTANCE; @@ -253,7 +297,8 @@ public void validateValidatorConfigMultipleOptionsInvalidValues() { } @Test - public void validateValidatorConfigViaValidatorFactory() { + @ModelTest + public void validateValidatorConfigViaValidatorFactory(KeycloakSession session) { Map config = new HashMap<>(); config.put("min", "a"); @@ -275,15 +320,16 @@ public void validateValidatorConfigViaValidatorFactory() { } @Test - public void nestedValidation() { + @ModelTest + public void nestedValidation(KeycloakSession session) { Assert.assertTrue(MockAddressValidator.INSTANCE.validate( new MockAddress("4848 Arcu St.", "Saint-Maur-des-Fossés", "02206", "Germany") - , "address").isValid()); + , "address", new ValidationContext(session)).isValid()); ValidationResult result = MockAddressValidator.INSTANCE.validate( new MockAddress("", "Saint-Maur-des-Fossés", null, "Germany") - , "address").toResult(); + , "address", new ValidationContext(session)).toResult(); Assert.assertFalse(result.isValid()); Assert.assertEquals(2, result.getErrors().size()); diff --git a/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme-preview/account/messages/messages_en.properties b/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme-preview/account/messages/messages_en.properties index da90117b3123..ccf4342cdee6 100644 --- a/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme-preview/account/messages/messages_en.properties +++ b/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme-preview/account/messages/messages_en.properties @@ -1,3 +1,2 @@ -#encoding: utf-8 locale_test=Přísný jazyk client_localized-client=Přespříliš lokalizovaný klient diff --git a/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme-preview/account/messages/messages_test.properties b/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme-preview/account/messages/messages_test.properties index 41173f4cc4a0..b2cc17b5a0b5 100644 --- a/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme-preview/account/messages/messages_test.properties +++ b/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme-preview/account/messages/messages_test.properties @@ -1,4 +1,3 @@ -#encoding: utf-8 locale_test=Přísný jazyk accountManagementWelcomeMessage=Vítejte v Keycloaku personalInfoHtmlTitle=Osobní údaje \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme/account/messages/messages_en.properties b/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme/account/messages/messages_en.properties index da90117b3123..ccf4342cdee6 100644 --- a/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme/account/messages/messages_en.properties +++ b/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme/account/messages/messages_en.properties @@ -1,3 +1,2 @@ -#encoding: utf-8 locale_test=Přísný jazyk client_localized-client=Přespříliš lokalizovaný klient diff --git a/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme/account/messages/messages_test.properties b/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme/account/messages/messages_test.properties index 41173f4cc4a0..b2cc17b5a0b5 100644 --- a/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme/account/messages/messages_test.properties +++ b/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme/account/messages/messages_test.properties @@ -1,4 +1,3 @@ -#encoding: utf-8 locale_test=Přísný jazyk accountManagementWelcomeMessage=Vítejte v Keycloaku personalInfoHtmlTitle=Osobní údaje \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme/email/messages/messages_en.properties b/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme/email/messages/messages_en.properties index d5ac3fa3ad00..fc49db6b0a4c 100644 --- a/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme/email/messages/messages_en.properties +++ b/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme/email/messages/messages_en.properties @@ -1,2 +1 @@ -#encoding: utf-8 locale_test=Přísný jazyk diff --git a/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme/email/messages/messages_test.properties b/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme/email/messages/messages_test.properties index 363f47efd009..0eda2dc94ce6 100644 --- a/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme/email/messages/messages_test.properties +++ b/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme/email/messages/messages_test.properties @@ -1,2 +1 @@ -#encoding: utf-8 locale_test=Přísný jazyk \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme/login/messages/messages_en.properties b/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme/login/messages/messages_en.properties index d5ac3fa3ad00..fc49db6b0a4c 100644 --- a/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme/login/messages/messages_en.properties +++ b/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme/login/messages/messages_en.properties @@ -1,2 +1 @@ -#encoding: utf-8 locale_test=Přísný jazyk diff --git a/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme/login/messages/messages_test.properties b/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme/login/messages/messages_test.properties index 42f3e4a00ca5..555e51305d54 100644 --- a/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme/login/messages/messages_test.properties +++ b/testsuite/integration-arquillian/tests/other/base-ui/src/main/resources/themes/localized-theme/login/messages/messages_test.properties @@ -1,4 +1,3 @@ -#encoding: utf-8 locale_test=Přísný jazyk termsText=[TEST LOCALE] souhlas s podmínkami notMatchPasswordMessage=[TEST LOCALE] hesla se neshodují diff --git a/testsuite/integration-arquillian/tests/other/springboot-tests/src/test/java/org/keycloak/testsuite/springboot/AccountLinkSpringBootTest.java b/testsuite/integration-arquillian/tests/other/springboot-tests/src/test/java/org/keycloak/testsuite/springboot/AccountLinkSpringBootTest.java index e5255cef4e7e..7311bd1ba623 100644 --- a/testsuite/integration-arquillian/tests/other/springboot-tests/src/test/java/org/keycloak/testsuite/springboot/AccountLinkSpringBootTest.java +++ b/testsuite/integration-arquillian/tests/other/springboot-tests/src/test/java/org/keycloak/testsuite/springboot/AccountLinkSpringBootTest.java @@ -466,7 +466,7 @@ public void testLinkOnlyProvider() throws Exception { links = realm.users().get(childUserId).getFederatedIdentity(); assertThat(links, is(empty())); - pause(500) + pause(500); logoutAll(); diff --git a/testsuite/model/README.md b/testsuite/model/README.md index 45fc693701a6..e992cd433cac 100644 --- a/testsuite/model/README.md +++ b/testsuite/model/README.md @@ -49,76 +49,4 @@ mvn test -Pjpa -Dtest=ClientModelTest \ The results are available in the `target/profile.html` file. -Usage of Testcontainers ------------------------ - -Some profiles within model tests require running 3rd party software, for -example, database or Infinispan. For running these we are using -[Testcontainers](https://www.testcontainers.org/). This may require some -additional configuration of your container engine. - -#### Podman settings - -For more details see the following [Podman guide from Quarkus webpage](https://quarkus.io/guides/podman). - -Specifically, these steps are required: -```shell -# Enable the podman socket with Docker REST API (only needs to be done once) -systemctl --user enable podman.socket --now - -# Set the required environment variables (need to be run everytime or added to profile) -export DOCKER_HOST=unix:///run/user/${UID}/podman/podman.sock -``` - -Testcontainers are using [ryuk](https://hub.docker.com/r/testcontainers/ryuk) -to cleanup containers after tests. To make this work with Podman add the -following line to `~/.testcontainers.properties` -```shell -ryuk.container.privileged=true -``` -Alternatively, disable usage of ryuk (using this may result in stale containers -still running after tests finish. This is not recommended especially if you are -executing tests from Intellij IDE as it [may not stop](https://youtrack.jetbrains.com/issue/IDEA-190385) -the containers created during test run). -```shell -export TESTCONTAINERS_RYUK_DISABLED=true #not recommended - see above! -``` - -#### Docker settings - -To use Testcontainers with Docker it is necessary to -[make Docker available for non-root users](https://docs.docker.com/engine/install/linux-postinstall/). - -Running HotRod tests with external Infinispan ---------------------------------------------- - -By default, Model tests with `hot-rod` profile spawn a new Infinispan container -with each test execution. It is also possible, to configure Model tests to -connect to an external instance of Infinispan. To do so, execute tests with -the following command: -```shell -mvn test -Phot-rod \ - -Dkeycloak.testsuite.start-hotrod-container=false \ - -Dkeycloak.connectionsHotRod.host= \ - -Dkeycloak.connectionsHotRod.port= \ - -Dkeycloak.connectionsHotRod.username= \ - -Dkeycloak.connectionsHotRod.password= -``` - -Running tests with `map-jpa` profile using external Postgres database ---------------------------------------------- - -By default, Model tests with `map-jpa` profile spawns a new Postgres container -with each test execution. Default image used is "postgres:alpine". To spawn different -version, it can be used "keycloak.map.storage.postgres.docker.image" system property. - -It is also possible, to configure Model tests to connect to an external instance -of Postgres. To do so, execute tests with the following command: -```shell -mvn test -Pmap-jpa \ - -Dpostgres.start-container=false \ - -Dkeycloak.map.storage.connectionsJpa.url= \ - -Dkeycloak.map.storage.connectionsJpa.user= \ - -Dkeycloak.map.storage.connectionsJpa.password= -``` diff --git a/testsuite/model/pom.xml b/testsuite/model/pom.xml index a94e48310857..f866f1b54131 100644 --- a/testsuite/model/pom.xml +++ b/testsuite/model/pom.xml @@ -29,7 +29,6 @@ ${h2.version} file:${project.build.directory}/dependency/log4j.properties true - disabled false @@ -93,23 +92,11 @@ org.keycloak keycloak-model-infinispan - - org.keycloak - keycloak-model-map - org.keycloak.testsuite integration-arquillian-testsuite-providers ${project.version} - - org.keycloak - keycloak-model-map-hot-rod - - - org.keycloak - keycloak-model-map-ldap - org.infinispan infinispan-core-jakarta @@ -119,18 +106,6 @@ postgresql ${postgresql-jdbc.version} - - org.testcontainers - testcontainers - ${testcontainers.version} - test - - - org.testcontainers - postgresql - ${testcontainers.version} - test - org.infinispan @@ -181,11 +156,7 @@ ${keycloak.connectionsJpa.user} ${keycloak.connectionsJpa.password} ${keycloak.connectionsJpa.url} - ${keycloak.map.storage.connectionsJpa.url} - ${keycloak.map.storage.connectionsJpa.user} - ${keycloak.map.storage.connectionsJpa.password} file:${project.build.directory}/test-classes/log4j.properties - ${keycloak.profile.feature.map_storage} ${keycloak.userSessions.infinispan.preloadOfflineSessionsFromDatabase} org.jboss.logmanager.LogManager log4j @@ -200,26 +171,6 @@ - - org.apache.maven.plugins - maven-antrun-plugin - - - process-test-resources - - run - - - - - - - - - - - - maven-dependency-plugin @@ -341,46 +292,6 @@ - - map - - enabled - Map,ConcurrentHashMapStorage - - - - - file - - enabled - Map,FileMapStorage - - - - - hot-rod - - enabled - Map,HotRodMapStorage - - - - - map-ldap - - enabled - Map,LdapMapStorage - - - - - map-jpa - - enabled - Map,JpaMapStorage - - - .asyncProfiler diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/ConcurrentHashMapStorageTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/ConcurrentHashMapStorageTest.java deleted file mode 100644 index b64c1901cd9e..000000000000 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/ConcurrentHashMapStorageTest.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright 2020 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.keycloak.testsuite.model; - -import org.hamcrest.Matchers; -import org.jboss.logging.Logger; -import org.junit.Before; -import org.junit.Test; -import org.keycloak.component.ComponentModel; -import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientProvider; -import org.keycloak.models.Constants; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RealmProvider; -import org.keycloak.models.map.client.MapClientEntity; -import org.keycloak.models.map.client.MapClientEntityImpl; -import org.keycloak.models.map.client.MapClientProviderFactory; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.StringKeyConverter; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.MapStorageProvider; -import org.keycloak.models.map.storage.MapStorageProviderFactory; -import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorage; -import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProviderFactory; -import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.provider.InvalidationHandler; - -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.nullValue; - - -/** - * - * @author hmlnarik - */ -@RequireProvider(value = ClientProvider.class, only = {MapClientProviderFactory.PROVIDER_ID}) -@RequireProvider(RealmProvider.class) -@RequireProvider(value = MapStorageProvider.class, only = {ConcurrentHashMapStorageProviderFactory.PROVIDER_ID}) -public class ConcurrentHashMapStorageTest extends KeycloakModelTest { - - private static final Logger LOG = Logger.getLogger(ConcurrentHashMapStorageTest.class.getName()); - - private String realmId; - - private String mapStorageProviderId; - - @Before - public void initMapStorageProviderId() { - MapStorageProviderFactory ms = (MapStorageProviderFactory) getFactory().getProviderFactory(MapStorageProvider.class, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID); - mapStorageProviderId = ms.getId(); - assertThat(mapStorageProviderId, Matchers.notNullValue()); - } - - @Override - public void createEnvironment(KeycloakSession s) { - RealmModel realm = createRealm(s, "realm"); - realm.setDefaultRole(s.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName())); - this.realmId = realm.getId(); - } - - @Override - public void cleanEnvironment(KeycloakSession s) { - s.realms().removeRealm(realmId); - } - - @Test - @SuppressWarnings("unchecked") - public void testStorageSeparation() { - String component1Id = createMapStorageComponent("component1", "keyType", "ulong"); - String component2Id = createMapStorageComponent("component2", "keyType", "string"); - - String[] ids = withRealm(realmId, (session, realm) -> { - ConcurrentHashMapStorage storageMain = (ConcurrentHashMapStorage) (MapStorage) session.getProvider(MapStorageProvider.class, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID).getMapStorage(ClientModel.class); - ConcurrentHashMapStorage storage1 = (ConcurrentHashMapStorage) (MapStorage) session.getComponentProvider(MapStorageProvider.class, component1Id).getMapStorage(ClientModel.class); - ConcurrentHashMapStorage storage2 = (ConcurrentHashMapStorage) (MapStorage) session.getComponentProvider(MapStorageProvider.class, component2Id).getMapStorage(ClientModel.class); - - // Assert that the map storage can be used both as a standalone store and a component - assertThat(storageMain, notNullValue()); - assertThat(storage1, notNullValue()); - assertThat(storage2, notNullValue()); - - final StringKeyConverter kcMain = (StringKeyConverter) StringKeyConverter.UUIDKey.INSTANCE; - final StringKeyConverter kc1 = (StringKeyConverter) StringKeyConverter.ULongKey.INSTANCE; - final StringKeyConverter kc2 = (StringKeyConverter) StringKeyConverter.StringKey.INSTANCE; - - String idMain = kcMain.keyToString(kcMain.yieldNewUniqueKey()); - String id1 = kc1.keyToString(kc1.yieldNewUniqueKey()); - String id2 = kc2.keyToString(kc2.yieldNewUniqueKey()); - - assertThat(idMain, notNullValue()); - assertThat(id1, notNullValue()); - assertThat(id2, notNullValue()); - - // Assert that the stores do not contain the to-be-created clients - assertThat(storageMain.read(idMain), nullValue()); - assertThat(storage1.read(id1), nullValue()); - assertThat(storage2.read(id2), nullValue()); - - assertClientDoesNotExist(storageMain, id1, kc1, kcMain); - assertClientDoesNotExist(storageMain, id2, kc2, kcMain); - assertClientDoesNotExist(storage1, idMain, kcMain, kc1); - assertClientDoesNotExist(storage1, id2, kc2, kc1); - assertClientDoesNotExist(storage2, idMain, kcMain, kc2); - assertClientDoesNotExist(storage2, id1, kc1, kc2); - - MapClientEntity clientMain = new MapClientEntityImpl(DeepCloner.DUMB_CLONER); - clientMain.setId(idMain); - clientMain.setRealmId(realmId); - MapClientEntity client1 = new MapClientEntityImpl(DeepCloner.DUMB_CLONER); - client1.setId(id1); - client1.setRealmId(realmId); - MapClientEntity client2 = new MapClientEntityImpl(DeepCloner.DUMB_CLONER); - client2.setId(id2); - client2.setRealmId(realmId); - - clientMain = storageMain.create(clientMain); - client1 = storage1.create(client1); - client2 = storage2.create(client2); - - return new String[] {clientMain.getId(), client1.getId(), client2.getId()}; - }); - - String idMain = ids[0]; - String id1 = ids[1]; - String id2 = ids[2]; - - LOG.debugf("Object IDs: %s, %s, %s", idMain, id1, id2); - - assertClientsPersisted(component1Id, component2Id, idMain, id1, id2); - - // Invalidate one component and check that the storage still contains what it should - getFactory().invalidate(null, InvalidationHandler.ObjectType.COMPONENT, component1Id); - assertClientsPersisted(component1Id, component2Id, idMain, id1, id2); - - // Invalidate whole realm and check that the storage still contains what it should - getFactory().invalidate(null, InvalidationHandler.ObjectType.REALM, realmId); - assertClientsPersisted(component1Id, component2Id, idMain, id1, id2); - - // Refresh factory (akin server restart) and check that the storage still contains what it should - reinitializeKeycloakSessionFactory(); - assertClientsPersisted(component1Id, component2Id, idMain, id1, id2); - } - - private void assertClientDoesNotExist(ConcurrentHashMapStorage storage, String id, final StringKeyConverter kc, final StringKeyConverter kcStorage) { - // Assert that the other stores do not contain the to-be-created clients (if they use compatible key format) - try { - assertThat(storage.read(id), nullValue()); - } catch (Exception ex) { - // If the format is incompatible then the object does not exist in the store - } - } - - private void assertClientsPersisted(String component1Id, String component2Id, String idMain, String id1, String id2) { - // Check that in the next transaction, the objects are still there - withRealm(realmId, (session, realm) -> { - @SuppressWarnings("unchecked") - ConcurrentHashMapStorage storageMain = (ConcurrentHashMapStorage) (MapStorage) session.getProvider(MapStorageProvider.class, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID).getMapStorage(ClientModel.class); - @SuppressWarnings("unchecked") - ConcurrentHashMapStorage storage1 = (ConcurrentHashMapStorage) (MapStorage) session.getComponentProvider(MapStorageProvider.class, component1Id).getMapStorage(ClientModel.class); - @SuppressWarnings("unchecked") - ConcurrentHashMapStorage storage2 = (ConcurrentHashMapStorage) (MapStorage) session.getComponentProvider(MapStorageProvider.class, component2Id).getMapStorage(ClientModel.class); - - final StringKeyConverter kcMain = (StringKeyConverter) StringKeyConverter.UUIDKey.INSTANCE; - final StringKeyConverter kc1 = (StringKeyConverter) StringKeyConverter.ULongKey.INSTANCE; - final StringKeyConverter kc2 = (StringKeyConverter) StringKeyConverter.StringKey.INSTANCE; - - // Assert that the stores contain the created clients - assertThat(storageMain.read(idMain), notNullValue()); - assertThat(storage1.read(id1), notNullValue()); - assertThat(storage2.read(id2), notNullValue()); - - // Assert that the other stores do not contain the to-be-created clients (if they use compatible key format) - assertClientDoesNotExist(storageMain, id1, kc1, kcMain); - assertClientDoesNotExist(storageMain, id2, kc2, kcMain); - assertClientDoesNotExist(storage1, idMain, kcMain, kc1); - assertClientDoesNotExist(storage1, id2, kc2, kc1); - assertClientDoesNotExist(storage2, idMain, kcMain, kc2); - assertClientDoesNotExist(storage2, id1, kc1, kc2); - assertThat(storageMain.read(idMain), notNullValue()); - - return null; - }); - } - - private String createMapStorageComponent(String name, String... config) { - ComponentModel c1 = KeycloakModelUtils.createComponentModel(name, realmId, mapStorageProviderId, MapStorageProvider.class.getName(), config); - - return withRealm(realmId, (s, r) -> r.addComponentModel(c1).getId()); - } -} diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/client/ClientModelTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/client/ClientModelTest.java index 596ff38d1a01..0824644f2543 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/client/ClientModelTest.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/client/ClientModelTest.java @@ -16,10 +16,7 @@ */ package org.keycloak.testsuite.model.client; -import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; @@ -28,23 +25,17 @@ import org.junit.Test; import org.keycloak.models.ClientModel; import org.keycloak.models.ClientProvider; -import org.keycloak.models.ClientScopeModel; import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RealmProvider; import org.keycloak.models.RoleModel; import org.keycloak.models.RoleProvider; -import org.keycloak.models.map.client.MapClientProvider; -import org.keycloak.models.map.client.MapClientProviderFactory; import org.keycloak.testsuite.model.KeycloakModelTest; import org.keycloak.testsuite.model.RequireProvider; -import java.util.LinkedList; -import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; /** * @@ -154,23 +145,6 @@ public void testClientsBasics() { } } - @Test - @RequireProvider(value = ClientProvider.class, only = MapClientProviderFactory.PROVIDER_ID) - public void testDeleteClientUsingQueryParameters() { - final String CLIENT_ID = "createDeleteClientId"; - // Create client - withRealm(realmId, (session, realm) -> session.clients().addClient(realm, CLIENT_ID)); - - // Check if exists - assertThat(withRealm(realmId, (session, realm) -> session.clients().getClientByClientId(realm, CLIENT_ID)), notNullValue()); - - // Remove - withRealm(realmId, (session, realm) -> {((MapClientProvider)session.clients()).preRemove(realm); return null;}); - - // Check is null - assertThat(withRealm(realmId, (session, realm) -> session.clients().getClientByClientId(realm, CLIENT_ID)), nullValue()); - } - @Test public void testScopeMappingRoleRemoval() { // create two clients, one realm role and one client role and assign both to one of the clients diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/events/AdminEventQueryTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/events/AdminEventQueryTest.java index aa411e1210fc..bc961c1e14e8 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/events/AdminEventQueryTest.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/events/AdminEventQueryTest.java @@ -17,8 +17,11 @@ package org.keycloak.testsuite.model.events; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; +import org.apache.commons.lang3.RandomStringUtils; import org.junit.Test; import org.keycloak.common.ClientConnection; import org.keycloak.events.EventStoreProvider; @@ -38,6 +41,7 @@ import org.keycloak.testsuite.model.RequireProvider; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; @RequireProvider(EventStoreProvider.class) @@ -113,6 +117,30 @@ public void testQueryOrder() { }); } + @Test + public void testAdminEventRepresentationLongValue() { + String longValue = RandomStringUtils.random(30000, true, true); + + withRealm(realmId, (session, realm) -> { + + AdminEvent event = createClientEvent(realm, OperationType.CREATE); + event.setRepresentation(longValue); + + session.getProvider(EventStoreProvider.class).onEvent(event, true); + + return null; + }); + + withRealm(realmId, (session, realm) -> { + List events = session.getProvider(EventStoreProvider.class).createAdminQuery().realm(realmId).getResultStream().collect(Collectors.toList()); + assertThat(events, hasSize(1)); + + assertThat(events.get(0).getRepresentation(), equalTo(longValue)); + + return null; + }); + } + private AdminEvent createClientEvent(RealmModel realm, OperationType operation) { return new AdminEventBuilder(realm, new DummyAuth(realm), null, DummyClientConnection.DUMMY_CONNECTION) .resource(ResourceType.CLIENT).operation(operation).getEvent(); diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/events/EventQueryTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/events/EventQueryTest.java index 7d9f86bd5243..ffadf60ce9ee 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/events/EventQueryTest.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/events/EventQueryTest.java @@ -20,26 +20,21 @@ import org.keycloak.events.Event; import org.keycloak.events.EventBuilder; import org.keycloak.events.EventStoreProvider; -import org.keycloak.events.EventStoreSpi; import org.keycloak.events.EventType; -import org.keycloak.events.admin.AdminEvent; import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; -import org.keycloak.models.map.events.MapEventStoreProviderFactory; -import org.keycloak.models.map.storage.file.FileMapStorageProviderFactory; import org.keycloak.testsuite.model.KeycloakModelTest; import org.keycloak.testsuite.model.RequireProvider; import java.util.List; -import java.util.Set; -import java.util.function.Consumer; +import java.util.Map; import java.util.stream.Collectors; +import org.apache.commons.lang3.RandomStringUtils; import org.junit.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; -import static org.junit.Assume.assumeFalse; /** * @@ -64,12 +59,6 @@ public void cleanEnvironment(KeycloakSession s) { @Test public void testClear() { - // Skip the test if EventProvider == File - String evProvider = CONFIG.getConfig().get(EventStoreSpi.NAME + ".provider"); - String evMapStorageProvider = CONFIG.getConfig().get(EventStoreSpi.NAME + ".map.storage-auth-events.provider"); - assumeFalse(MapEventStoreProviderFactory.PROVIDER_ID.equals(evProvider) && - (evMapStorageProvider == null || FileMapStorageProviderFactory.PROVIDER_ID.equals(evMapStorageProvider))); - inRolledBackTransaction(null, (session, t) -> { EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class); eventStore.clear(); @@ -149,81 +138,35 @@ public void testQueryOrder() { }); } - @Test - @RequireProvider(value = EventStoreProvider.class, only = "map") - public void testEventExpiration() { - withRealm(realmId, (session, realm) -> { - EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class); - - // Set expiration so no event is valid - realm.setEventsExpiration(5); - Event e = createAuthEventForUser(session, realm, "u1"); - eventStore.onEvent(e); - - // Set expiration to 1000 seconds - realm.setEventsExpiration(1000); - e = createAuthEventForUser(session, realm, "u2"); - eventStore.onEvent(e); - - return null; - }); - - setTimeOffset(10); - - try { - withRealm(realmId, (session, realm) -> { - EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class); - - Set events = eventStore.createQuery() - .realm(realmId) - .getResultStream().collect(Collectors.toSet()); - - assertThat(events, hasSize(1)); - assertThat(events.iterator().next().getUserId(), equalTo("u2")); - return null; - }); - } finally { - setTimeOffset(0); - } + public void testEventDetailsLongValue() { + String v1 = RandomStringUtils.random(1000, true, true); + String v2 = RandomStringUtils.random(1000, true, true); + String v3 = RandomStringUtils.random(1000, true, true); + String v4 = RandomStringUtils.random(1000, true, true); + withRealm(realmId, (session, realm) -> { - } - - @Test - @RequireProvider(value = EventStoreProvider.class, only = "map") - public void testEventsClearedOnRealmRemoval() { - // Create another realm - String newRealmId = inComittedTransaction(null, (session, t) -> { - RealmModel realm = session.realms().createRealm("events-realm"); - realm.setDefaultRole(session.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName())); - - EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class); - Event e = createAuthEventForUser(session, realm, "u1"); - eventStore.onEvent(e); + Map details = Map.of("k1", v1, "k2", v2, "k3", v3, "k4", v4); - AdminEvent ae = new AdminEvent(); - ae.setRealmId(realm.getId()); - eventStore.onEvent(ae, false); + Event event = createAuthEventForUser(session, realm, "u1"); + event.setDetails(details); - return realm.getId(); - }); + session.getProvider(EventStoreProvider.class).onEvent(event); - // Check if events were created - inComittedTransaction(session -> { - EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class); - assertThat(eventStore.createQuery().realm(newRealmId).getResultStream().count(), is(1L)); - assertThat(eventStore.createAdminQuery().realm(newRealmId).getResultStream().count(), is(1L)); + return null; }); - // Remove realm - inComittedTransaction((Consumer) session -> session.realms().removeRealm(newRealmId)); - - // Check events were removed - inComittedTransaction(session -> { - EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class); - assertThat(eventStore.createQuery().realm(newRealmId).getResultStream().count(), is(0L)); - assertThat(eventStore.createAdminQuery().realm(newRealmId).getResultStream().count(), is(0L)); + withRealm(realmId, (session, realm) -> { + List events = session.getProvider(EventStoreProvider.class).createQuery().realm(realmId).getResultStream().collect(Collectors.toList()); + assertThat(events, hasSize(1)); + Map details = events.get(0).getDetails(); + + assertThat(details.get("k1"), equalTo(v1)); + assertThat(details.get("k2"), equalTo(v2)); + assertThat(details.get("k3"), equalTo(v3)); + assertThat(details.get("k4"), equalTo(v4)); + return null; }); } diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/ConcurrentHashMapStorage.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/ConcurrentHashMapStorage.java deleted file mode 100644 index 71f7ac34e073..000000000000 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/ConcurrentHashMapStorage.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2020 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.keycloak.testsuite.model.parameters; - -import org.keycloak.common.crypto.CryptoIntegration; -import org.keycloak.common.crypto.CryptoProvider; -import org.keycloak.exportimport.ExportSpi; -import org.keycloak.exportimport.ImportSpi; -import org.keycloak.exportimport.dir.DirExportProviderFactory; -import org.keycloak.exportimport.dir.DirImportProviderFactory; -import org.keycloak.exportimport.singlefile.SingleFileExportProviderFactory; -import org.keycloak.exportimport.singlefile.SingleFileImportProviderFactory; -import org.keycloak.keys.KeyProviderFactory; -import org.keycloak.keys.KeySpi; -import org.keycloak.models.ClientScopeSpi; -import org.keycloak.models.map.storage.MapStorageSpi; -import org.keycloak.services.clientpolicy.ClientPolicyManagerFactory; -import org.keycloak.services.clientpolicy.ClientPolicyManagerSpi; -import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyFactory; -import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicySpi; -import org.keycloak.testsuite.model.KeycloakModelParameters; -import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProviderFactory; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.provider.Spi; -import org.keycloak.testsuite.model.Config; -import com.google.common.collect.ImmutableSet; -import java.util.Set; - -/** - * - * @author hmlnarik - */ -public class ConcurrentHashMapStorage extends KeycloakModelParameters { - - static final Set> ALLOWED_SPIS = ImmutableSet.>builder() - .add(ExportSpi.class) - .add(ClientPolicyManagerSpi.class) - .add(ImportSpi.class) - .add(ClientRegistrationPolicySpi.class) - .add(ClientScopeSpi.class) - .add(KeySpi.class) - .build(); - - static { - // CryptoIntegration needed for import of realms - CryptoIntegration.init(CryptoProvider.class.getClassLoader()); - } - - static final Set> ALLOWED_FACTORIES = ImmutableSet.>builder() - .add(ConcurrentHashMapStorageProviderFactory.class) - // start providers needed for export - .add(SingleFileExportProviderFactory.class) - .add(DirExportProviderFactory.class) - .add(ClientPolicyManagerFactory.class) - // end providers needed for export - // start providers needed for import - .add(SingleFileImportProviderFactory.class) - .add(DirImportProviderFactory.class) - .add(ClientRegistrationPolicyFactory.class) - .add(KeyProviderFactory.class) - // end providers needed for import - .build(); - - @Override - public void updateConfig(Config cf) { - cf.spi(MapStorageSpi.NAME) - .defaultProvider(ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .provider(ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .config("dir", "${project.build.directory:target}"); - } - - public ConcurrentHashMapStorage() { - super(ALLOWED_SPIS, ALLOWED_FACTORIES); - } - -} diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/FileMapStorage.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/FileMapStorage.java deleted file mode 100644 index f3c4b55749af..000000000000 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/FileMapStorage.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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.model.parameters; - -import com.google.common.collect.ImmutableSet; -import java.util.Set; -import org.keycloak.authorization.store.StoreFactorySpi; -import org.keycloak.events.EventStoreSpi; -import org.keycloak.models.DeploymentStateSpi; -import org.keycloak.models.SingleUseObjectSpi; -import org.keycloak.models.UserLoginFailureSpi; -import org.keycloak.models.UserSessionSpi; -import org.keycloak.models.map.authSession.MapRootAuthenticationSessionProviderFactory; -import org.keycloak.models.map.authorization.MapAuthorizationStoreFactory; -import org.keycloak.models.map.client.MapClientProviderFactory; -import org.keycloak.models.map.clientscope.MapClientScopeProviderFactory; -import org.keycloak.models.map.deploymentState.MapDeploymentStateProviderFactory; -import org.keycloak.models.map.events.MapEventStoreProviderFactory; -import org.keycloak.models.map.group.MapGroupProviderFactory; -import org.keycloak.models.map.keys.MapPublicKeyStorageProviderFactory; -import org.keycloak.models.map.loginFailure.MapUserLoginFailureProviderFactory; -import org.keycloak.models.map.realm.MapRealmProviderFactory; -import org.keycloak.models.map.role.MapRoleProviderFactory; -import org.keycloak.models.map.singleUseObject.MapSingleUseObjectProviderFactory; -import org.keycloak.models.map.storage.MapStorageSpi; -import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProviderFactory; -import org.keycloak.models.map.storage.file.FileMapStorageProviderFactory; -import org.keycloak.models.map.user.MapUserProviderFactory; -import org.keycloak.models.map.userSession.MapUserSessionProviderFactory; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.provider.Spi; -import org.keycloak.sessions.AuthenticationSessionSpi; -import org.keycloak.testsuite.model.Config; -import org.keycloak.testsuite.model.KeycloakModelParameters; - -public class FileMapStorage extends KeycloakModelParameters { - - static final Set> ALLOWED_SPIS = ImmutableSet.>builder() - .build(); - - static final Set> ALLOWED_FACTORIES = ImmutableSet.>builder() - .add(FileMapStorageProviderFactory.class) - .add(ConcurrentHashMapStorageProviderFactory.class) - .build(); - - public FileMapStorage() { - super(ALLOWED_SPIS, ALLOWED_FACTORIES); - } - - @Override - public void updateConfig(Config cf) { - cf.spi(MapStorageSpi.NAME) - .provider(FileMapStorageProviderFactory.PROVIDER_ID) - .config("dir", "${project.build.directory:target/file}") - .provider(ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .config("dir", "${project.build.directory:target/chm}") - - .spi(AuthenticationSessionSpi.PROVIDER_ID).provider(MapRootAuthenticationSessionProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, FileMapStorageProviderFactory.PROVIDER_ID) - .spi("client").provider(MapClientProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, FileMapStorageProviderFactory.PROVIDER_ID) - .spi("clientScope").provider(MapClientScopeProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, FileMapStorageProviderFactory.PROVIDER_ID) - .spi("group").provider(MapGroupProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, FileMapStorageProviderFactory.PROVIDER_ID) - .spi("realm").provider(MapRealmProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, FileMapStorageProviderFactory.PROVIDER_ID) - .spi("role").provider(MapRoleProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, FileMapStorageProviderFactory.PROVIDER_ID) - .spi(DeploymentStateSpi.NAME).provider(MapDeploymentStateProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .spi(StoreFactorySpi.NAME).provider(MapAuthorizationStoreFactory.PROVIDER_ID) .config(STORAGE_CONFIG, FileMapStorageProviderFactory.PROVIDER_ID) - .spi("user").provider(MapUserProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, FileMapStorageProviderFactory.PROVIDER_ID) - .spi(UserLoginFailureSpi.NAME).provider(MapUserLoginFailureProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, FileMapStorageProviderFactory.PROVIDER_ID) - .spi(SingleUseObjectSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .spi("publicKeyStorage").provider(MapPublicKeyStorageProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .spi(UserSessionSpi.NAME).provider(MapUserSessionProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, FileMapStorageProviderFactory.PROVIDER_ID) - .spi(EventStoreSpi.NAME).provider(MapEventStoreProviderFactory.PROVIDER_ID) .config("storage-admin-events.provider", FileMapStorageProviderFactory.PROVIDER_ID) - .config("storage-auth-events.provider", FileMapStorageProviderFactory.PROVIDER_ID); - } - -} diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/HotRodMapStorage.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/HotRodMapStorage.java deleted file mode 100644 index d894ea41a7c5..000000000000 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/HotRodMapStorage.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2020 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.keycloak.testsuite.model.parameters; - -import com.google.common.collect.ImmutableSet; -import org.jboss.logging.Logger; -import org.keycloak.authorization.store.StoreFactorySpi; -import org.keycloak.events.EventStoreSpi; -import org.keycloak.models.DeploymentStateSpi; -import org.keycloak.models.SingleUseObjectSpi; -import org.keycloak.models.UserLoginFailureSpi; -import org.keycloak.models.UserSessionSpi; -import org.keycloak.models.locking.GlobalLockProviderSpi; -import org.keycloak.models.locking.NoneGlobalLockProviderFactory; -import org.keycloak.models.map.authSession.MapRootAuthenticationSessionProviderFactory; -import org.keycloak.models.map.authorization.MapAuthorizationStoreFactory; -import org.keycloak.models.map.client.MapClientProviderFactory; -import org.keycloak.models.map.clientscope.MapClientScopeProviderFactory; -import org.keycloak.models.map.keys.MapPublicKeyStorageProviderFactory; -import org.keycloak.models.map.singleUseObject.MapSingleUseObjectProviderFactory; -import org.keycloak.models.map.storage.hotRod.connections.DefaultHotRodConnectionProviderFactory; -import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProviderFactory; -import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionSpi; -import org.keycloak.models.map.deploymentState.MapDeploymentStateProviderFactory; -import org.keycloak.models.map.group.MapGroupProviderFactory; -import org.keycloak.models.map.loginFailure.MapUserLoginFailureProviderFactory; -import org.keycloak.models.map.realm.MapRealmProviderFactory; -import org.keycloak.models.map.role.MapRoleProviderFactory; -import org.keycloak.models.map.storage.MapStorageSpi; -import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProviderFactory; -import org.keycloak.models.map.storage.hotRod.HotRodMapStorageProviderFactory; -import org.keycloak.models.map.storage.hotRod.locking.HotRodGlobalLockProviderFactory; -import org.keycloak.models.map.user.MapUserProviderFactory; -import org.keycloak.models.map.userSession.MapUserSessionProviderFactory; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.provider.Spi; -import org.keycloak.sessions.AuthenticationSessionSpi; -import org.keycloak.testsuite.model.Config; -import org.keycloak.testsuite.model.KeycloakModelParameters; -import org.keycloak.testsuite.util.InfinispanContainer; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.wait.strategy.Wait; - -import java.time.Duration; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static org.keycloak.testsuite.model.transaction.StorageTransactionTest.LOCK_TIMEOUT_SYSTEM_PROPERTY; - -/** - * - * @author hmlnarik - */ -public class HotRodMapStorage extends KeycloakModelParameters { - - private final Logger LOG = Logger.getLogger(getClass()); - public static final Boolean START_CONTAINER = Boolean.valueOf(System.getProperty("keycloak.testsuite.start-hotrod-container", "true")); - static final Set> ALLOWED_SPIS = ImmutableSet.>builder() - .add(HotRodConnectionSpi.class) - .build(); - - static final Set> ALLOWED_FACTORIES = ImmutableSet.>builder() - .add(HotRodMapStorageProviderFactory.class) - .add(HotRodConnectionProviderFactory.class) - .add(HotRodGlobalLockProviderFactory.class) - .build(); - - private final InfinispanContainer hotRodContainer = new InfinispanContainer(); - - @Override - public void updateConfig(Config cf) { - cf.spi(AuthenticationSessionSpi.PROVIDER_ID).provider(MapRootAuthenticationSessionProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID) - .spi(SingleUseObjectSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID) - .spi("publicKeyStorage").provider(MapPublicKeyStorageProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .spi("client").provider(MapClientProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID) - .spi("clientScope").provider(MapClientScopeProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID) - .spi("group").provider(MapGroupProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID) - .spi("realm").provider(MapRealmProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID) - .spi("role").provider(MapRoleProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID) - .spi(DeploymentStateSpi.NAME).provider(MapDeploymentStateProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .spi(StoreFactorySpi.NAME).provider(MapAuthorizationStoreFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID) - .spi("user").provider(MapUserProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID) - .spi(UserSessionSpi.NAME).provider(MapUserSessionProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID) - .spi(UserLoginFailureSpi.NAME).provider(MapUserLoginFailureProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID) - .spi(EventStoreSpi.NAME).provider(MapUserSessionProviderFactory.PROVIDER_ID).config("storage-admin-events.provider", HotRodMapStorageProviderFactory.PROVIDER_ID) - .config("storage-auth-events.provider", HotRodMapStorageProviderFactory.PROVIDER_ID) - .spi(GlobalLockProviderSpi.GLOBAL_LOCK).defaultProvider(HotRodGlobalLockProviderFactory.PROVIDER_ID); - - cf.spi(MapStorageSpi.NAME) - .provider(ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .config("dir", "${project.build.directory:target}") - .config("keyType.single-use-objects", "string"); - - cf.spi(HotRodConnectionSpi.NAME).provider(DefaultHotRodConnectionProviderFactory.PROVIDER_ID) - .config("host", hotRodContainer.getHost()) - .config("port", hotRodContainer.getPort()) - .config("username", hotRodContainer.getUsername()) - .config("password", hotRodContainer.getPassword()) - .config("configureRemoteCaches", "true") - .config("lockTimeout", "${" + LOCK_TIMEOUT_SYSTEM_PROPERTY + ":}"); - } - - @Override - public void beforeSuite(Config cf) { - if (START_CONTAINER) { - hotRodContainer.start(); - } - } - - @Override - public void afterSuite() { - if (START_CONTAINER) { - hotRodContainer.stop(); - } - } - - public HotRodMapStorage() { - super(ALLOWED_SPIS, ALLOWED_FACTORIES); - } - -} diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/JpaMapStorage.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/JpaMapStorage.java deleted file mode 100644 index 913e496c2928..000000000000 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/JpaMapStorage.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2020 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.keycloak.testsuite.model.parameters; - -import com.google.common.collect.ImmutableSet; -import java.util.Set; -import org.jboss.logging.Logger; -import org.keycloak.authorization.store.StoreFactorySpi; -import org.keycloak.events.EventStoreSpi; -import org.keycloak.models.DeploymentStateSpi; -import org.keycloak.models.SingleUseObjectSpi; -import org.keycloak.models.UserLoginFailureSpi; -import org.keycloak.models.UserSessionSpi; -import org.keycloak.models.locking.GlobalLockProviderSpi; -import org.keycloak.models.map.authSession.MapRootAuthenticationSessionProviderFactory; -import org.keycloak.models.map.authorization.MapAuthorizationStoreFactory; -import org.keycloak.models.map.client.MapClientProviderFactory; -import org.keycloak.models.map.clientscope.MapClientScopeProviderFactory; -import org.keycloak.models.map.deploymentState.MapDeploymentStateProviderFactory; -import org.keycloak.models.map.events.MapEventStoreProviderFactory; -import org.keycloak.models.map.group.MapGroupProviderFactory; -import org.keycloak.models.map.keys.MapPublicKeyStorageProviderFactory; -import org.keycloak.models.map.loginFailure.MapUserLoginFailureProviderFactory; -import org.keycloak.models.map.realm.MapRealmProviderFactory; -import org.keycloak.models.map.role.MapRoleProviderFactory; -import org.keycloak.models.map.singleUseObject.MapSingleUseObjectProviderFactory; -import org.keycloak.models.map.storage.MapStorageSpi; -import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProviderFactory; -import org.keycloak.models.map.lock.MapGlobalLockProviderFactory; -import org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory; -import org.keycloak.models.map.storage.jpa.liquibase.connection.MapLiquibaseConnectionProviderFactory; -import org.keycloak.models.map.storage.jpa.liquibase.connection.MapLiquibaseConnectionSpi; -import org.keycloak.models.map.storage.jpa.updater.MapJpaUpdaterProviderFactory; -import org.keycloak.models.map.storage.jpa.updater.MapJpaUpdaterSpi; -import org.keycloak.models.map.user.MapUserProviderFactory; -import org.keycloak.models.map.userSession.MapUserSessionProviderFactory; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.provider.Spi; -import org.keycloak.sessions.AuthenticationSessionSpi; -import org.keycloak.testsuite.model.Config; -import org.keycloak.testsuite.model.KeycloakModelParameters; -import org.testcontainers.containers.PostgreSQLContainer; -import org.testcontainers.utility.DockerImageName; - -import static org.keycloak.testsuite.model.transaction.StorageTransactionTest.LOCK_TIMEOUT_SYSTEM_PROPERTY; - -public class JpaMapStorage extends KeycloakModelParameters { - - private static final Logger LOG = Logger.getLogger(JpaMapStorage.class.getName()); - - private static final Boolean START_CONTAINER = Boolean.valueOf(System.getProperty("postgres.start-container", "true")); - private static final String POSTGRES_DOCKER_IMAGE_NAME = System.getProperty("keycloak.map.storage.postgres.docker.image", "postgres:alpine"); - private static final PostgreSQLContainer POSTGRES_CONTAINER = new PostgreSQLContainer(DockerImageName.parse(POSTGRES_DOCKER_IMAGE_NAME).asCompatibleSubstituteFor("postgres")); - private static final String POSTGRES_DB_DEFAULT_NAME = System.getProperty("keycloak.map.storage.connectionsJpa.databaseName", "keycloak"); - private static final String POSTGRES_DB_USER = System.getProperty("keycloak.map.storage.connectionsJpa.user", "keycloak"); - private static final String POSTGRES_DB_PASSWORD = System.getProperty("keycloak.map.storage.connectionsJpa.password", "pass"); - - private static String POSTGRES_DB_JDBC_URL = System.getProperty("keycloak.map.storage.connectionsJpa.url"); - - static final Set> ALLOWED_SPIS = ImmutableSet.>builder() - .add(MapJpaUpdaterSpi.class) - .add(MapLiquibaseConnectionSpi.class) - .build(); - - static final Set> ALLOWED_FACTORIES = ImmutableSet.>builder() - .add(ConcurrentHashMapStorageProviderFactory.class) - .add(JpaMapStorageProviderFactory.class) - .add(MapJpaUpdaterProviderFactory.class) - .add(MapLiquibaseConnectionProviderFactory.class) - .add(MapGlobalLockProviderFactory.class) - .build(); - - public JpaMapStorage() { - super(ALLOWED_SPIS, ALLOWED_FACTORIES); - } - - @Override - public void updateConfig(Config cf) { - cf.spi(MapStorageSpi.NAME) - .provider(ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .config("dir", "${project.build.directory:target}"); - - cf.spi(MapStorageSpi.NAME) - .provider(JpaMapStorageProviderFactory.PROVIDER_ID) - .config("url", POSTGRES_DB_JDBC_URL) - .config("user", POSTGRES_DB_USER) - .config("password", POSTGRES_DB_PASSWORD) - .config("driver", "org.postgresql.Driver") - .config("lockTimeout", "${" + LOCK_TIMEOUT_SYSTEM_PROPERTY + ":}"); - - cf.spi(AuthenticationSessionSpi.PROVIDER_ID).provider(MapRootAuthenticationSessionProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID) - .spi("client").provider(MapClientProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID) - .spi("clientScope").provider(MapClientScopeProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID) - .spi("group").provider(MapGroupProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID) - .spi("realm").provider(MapRealmProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID) - .spi("role").provider(MapRoleProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID) - .spi(DeploymentStateSpi.NAME).provider(MapDeploymentStateProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .spi(StoreFactorySpi.NAME).provider(MapAuthorizationStoreFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID) - .spi("user").provider(MapUserProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID) - .spi(UserLoginFailureSpi.NAME).provider(MapUserLoginFailureProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID) - .spi(SingleUseObjectSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID) - .spi("publicKeyStorage").provider(MapPublicKeyStorageProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .spi(UserSessionSpi.NAME).provider(MapUserSessionProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID) - .spi(EventStoreSpi.NAME).provider(MapEventStoreProviderFactory.PROVIDER_ID) .config("storage-admin-events.provider", JpaMapStorageProviderFactory.PROVIDER_ID) - .config("storage-auth-events.provider", JpaMapStorageProviderFactory.PROVIDER_ID) - .spi(GlobalLockProviderSpi.GLOBAL_LOCK) .config("provider", MapGlobalLockProviderFactory.PROVIDER_ID) - .spi(GlobalLockProviderSpi.GLOBAL_LOCK).provider(MapGlobalLockProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID); - } - - @Override - public void beforeSuite(Config cf) { - if (START_CONTAINER) { - POSTGRES_CONTAINER - .withDatabaseName(POSTGRES_DB_DEFAULT_NAME) - .withUsername(POSTGRES_DB_USER) - .withPassword(POSTGRES_DB_PASSWORD) - .start(); - - POSTGRES_DB_JDBC_URL = POSTGRES_CONTAINER.getJdbcUrl(); - } - } - - @Override - public void afterSuite() { - if (START_CONTAINER) { - POSTGRES_CONTAINER.stop(); - } - } -} diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/JpaMapStorageCockroachdb.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/JpaMapStorageCockroachdb.java deleted file mode 100644 index f5c8e17c2a53..000000000000 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/JpaMapStorageCockroachdb.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright 2022 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.model.parameters; - -import com.google.common.collect.ImmutableSet; -import org.keycloak.authorization.store.StoreFactorySpi; -import org.keycloak.events.EventStoreSpi; -import org.keycloak.models.DeploymentStateSpi; -import org.keycloak.models.SingleUseObjectSpi; -import org.keycloak.models.UserLoginFailureSpi; -import org.keycloak.models.UserSessionSpi; -import org.keycloak.models.locking.GlobalLockProviderSpi; -import org.keycloak.models.map.authSession.MapRootAuthenticationSessionProviderFactory; -import org.keycloak.models.map.authorization.MapAuthorizationStoreFactory; -import org.keycloak.models.map.client.MapClientProviderFactory; -import org.keycloak.models.map.clientscope.MapClientScopeProviderFactory; -import org.keycloak.models.map.deploymentState.MapDeploymentStateProviderFactory; -import org.keycloak.models.map.events.MapEventStoreProviderFactory; -import org.keycloak.models.map.group.MapGroupProviderFactory; -import org.keycloak.models.map.keys.MapPublicKeyStorageProviderFactory; -import org.keycloak.models.map.lock.MapGlobalLockProviderFactory; -import org.keycloak.models.map.loginFailure.MapUserLoginFailureProviderFactory; -import org.keycloak.models.map.realm.MapRealmProviderFactory; -import org.keycloak.models.map.role.MapRoleProviderFactory; -import org.keycloak.models.map.singleUseObject.MapSingleUseObjectProviderFactory; -import org.keycloak.models.map.storage.MapStorageSpi; -import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProviderFactory; -import org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory; -import org.keycloak.models.map.storage.jpa.liquibase.connection.MapLiquibaseConnectionProviderFactory; -import org.keycloak.models.map.storage.jpa.liquibase.connection.MapLiquibaseConnectionSpi; -import org.keycloak.models.map.storage.jpa.updater.MapJpaUpdaterProviderFactory; -import org.keycloak.models.map.storage.jpa.updater.MapJpaUpdaterSpi; -import org.keycloak.models.map.user.MapUserProviderFactory; -import org.keycloak.models.map.userSession.MapUserSessionProviderFactory; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.provider.Spi; -import org.keycloak.sessions.AuthenticationSessionSpi; -import org.keycloak.testsuite.model.Config; -import org.keycloak.testsuite.model.KeycloakModelParameters; -import org.keycloak.testsuite.model.KeycloakModelTest; -import org.testcontainers.containers.CockroachContainer; -import org.testcontainers.utility.DockerImageName; - -import java.util.Set; - -import static org.keycloak.testsuite.model.transaction.StorageTransactionTest.LOCK_TIMEOUT_SYSTEM_PROPERTY; - -public class JpaMapStorageCockroachdb extends KeycloakModelParameters { - - private static final Boolean START_CONTAINER = Boolean.valueOf(System.getProperty("cockroachdb.start-container", "true")); - private static final String COCKROACHDB_DOCKER_IMAGE_NAME = System.getProperty("keycloak.map.storage.cockroachdb.docker.image", "cockroachdb/cockroach:v22.1.0"); - private static final CockroachContainer COCKROACHDB_CONTAINER = new CockroachContainer(DockerImageName.parse(COCKROACHDB_DOCKER_IMAGE_NAME).asCompatibleSubstituteFor("cockroachdb")); - private static final String COCKROACHDB_DB_USER = System.getProperty("keycloak.map.storage.connectionsJpa.user", "keycloak"); - private static final String COCKROACHDB_DB_PASSWORD = System.getProperty("keycloak.map.storage.connectionsJpa.password", "pass"); - - private static String COCKROACHDB_DB_JDBC_URL = System.getProperty("keycloak.map.storage.connectionsJpa.url"); - - static final Set> ALLOWED_SPIS = ImmutableSet.>builder() - .add(MapJpaUpdaterSpi.class) - .add(MapLiquibaseConnectionSpi.class) - .build(); - - static final Set> ALLOWED_FACTORIES = ImmutableSet.>builder() - .add(ConcurrentHashMapStorageProviderFactory.class) - .add(JpaMapStorageProviderFactory.class) - .add(MapJpaUpdaterProviderFactory.class) - .add(MapLiquibaseConnectionProviderFactory.class) - .add(MapGlobalLockProviderFactory.class) - .build(); - - public JpaMapStorageCockroachdb() { - super(ALLOWED_SPIS, ALLOWED_FACTORIES); - } - - @Override - public void updateConfig(Config cf) { - cf.spi(MapStorageSpi.NAME) - .provider(ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .config("dir", "${project.build.directory:target}"); - - cf.spi(MapStorageSpi.NAME) - .provider(JpaMapStorageProviderFactory.PROVIDER_ID) - .config("url", COCKROACHDB_DB_JDBC_URL) - .config("user", COCKROACHDB_DB_USER) - .config("password", COCKROACHDB_DB_PASSWORD) - .config("driver", "org.postgresql.Driver") - .config("lockTimeout", "${" + LOCK_TIMEOUT_SYSTEM_PROPERTY + ":}"); - - cf.spi(AuthenticationSessionSpi.PROVIDER_ID).provider(MapRootAuthenticationSessionProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID) - .spi("client").provider(MapClientProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID) - .spi("clientScope").provider(MapClientScopeProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID) - .spi("group").provider(MapGroupProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID) - .spi("realm").provider(MapRealmProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID) - .spi("role").provider(MapRoleProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID) - .spi(DeploymentStateSpi.NAME).provider(MapDeploymentStateProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .spi(StoreFactorySpi.NAME).provider(MapAuthorizationStoreFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID) - .spi("user").provider(MapUserProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID) - .spi(UserLoginFailureSpi.NAME).provider(MapUserLoginFailureProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID) - .spi(SingleUseObjectSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID) - .spi("publicKeyStorage").provider(MapPublicKeyStorageProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .spi(UserSessionSpi.NAME).provider(MapUserSessionProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID) - .spi(EventStoreSpi.NAME).provider(MapEventStoreProviderFactory.PROVIDER_ID) .config("storage-admin-events.provider", JpaMapStorageProviderFactory.PROVIDER_ID) - .config("storage-auth-events.provider", JpaMapStorageProviderFactory.PROVIDER_ID) - .spi(GlobalLockProviderSpi.GLOBAL_LOCK) .config("provider", MapGlobalLockProviderFactory.PROVIDER_ID) - .spi(GlobalLockProviderSpi.GLOBAL_LOCK).provider(MapGlobalLockProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID); - } - - @Override - public void beforeSuite(Config cf) { - if (START_CONTAINER) { - COCKROACHDB_CONTAINER - // Using the environment variables for now where using the withXXX() method is not supported, yet. - // https://github.com/testcontainers/testcontainers-java/issues/6299 - .withEnv("COCKROACH_DATABASE", "keycloak") - .withEnv("COCKROACH_USER", COCKROACHDB_DB_USER) - // password is not used/supported in insecure mode - .withCommand("start-single-node", "--insecure") - .start(); - - COCKROACHDB_DB_JDBC_URL = COCKROACHDB_CONTAINER.getJdbcUrl(); - } - System.setProperty(KeycloakModelTest.KEYCLOAK_MODELTESTS_RETRY_TRANSACTIONS, "true"); - } - - @Override - public void afterSuite() { - if (START_CONTAINER) { - COCKROACHDB_CONTAINER.stop(); - } - } -} diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/LdapMapStorage.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/LdapMapStorage.java deleted file mode 100644 index f5a6041d4bbe..000000000000 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/LdapMapStorage.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2020 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.keycloak.testsuite.model.parameters; - -import com.google.common.collect.ImmutableSet; -import org.jboss.logging.Logger; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; -import org.keycloak.authorization.store.StoreFactorySpi; -import org.keycloak.events.EventStoreSpi; -import org.keycloak.models.DeploymentStateSpi; -import org.keycloak.models.LDAPConstants; -import org.keycloak.models.SingleUseObjectSpi; -import org.keycloak.models.UserLoginFailureSpi; -import org.keycloak.models.UserSessionSpi; -import org.keycloak.models.map.storage.MapStorageSpi; -import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProviderFactory; -import org.keycloak.models.map.storage.ldap.LdapMapStorageProviderFactory; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.provider.Spi; -import org.keycloak.testsuite.model.Config; -import org.keycloak.testsuite.model.KeycloakModelParameters; -import org.keycloak.testsuite.util.LDAPRule; -import org.keycloak.util.ldap.LDAPEmbeddedServer; - -import java.util.Set; - -/** - * @author Alexander Schwartz - */ -public class LdapMapStorage extends KeycloakModelParameters { - - private static final Logger LOG = Logger.getLogger(LdapMapStorage.class.getName()); - - static final Set> ALLOWED_SPIS = ImmutableSet.>builder() - .build(); - - static final Set> ALLOWED_FACTORIES = ImmutableSet.>builder() - .add(ConcurrentHashMapStorageProviderFactory.class) - .add(LdapMapStorageProviderFactory.class) - .build(); - - private final LDAPRule ldapRule = new LDAPRule(); - - public LdapMapStorage() { - super(ALLOWED_SPIS, ALLOWED_FACTORIES); - } - - @Override - public void updateConfig(Config cf) { - cf.spi(MapStorageSpi.NAME) - .provider(ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .config("dir", "${project.build.directory:target}"); - - cf.spi(MapStorageSpi.NAME) - .provider(LdapMapStorageProviderFactory.PROVIDER_ID) - .config("vendor", "other") - .config("usernameLDAPAttribute", "uid") - .config("rdnLDAPAttribute", "uid") - .config("uuidLDAPAttribute", "entryUUID") - .config("userObjectClasses", "inetOrgPerson, organizationalPerson") - .config("connectionUrl", "ldap://localhost:10389") - .config("usersDn", "ou=People,dc=keycloak,dc=org") - .config("bindDn", "uid=admin,ou=system") - .config("bindCredential", "secret") - .config("roles.realm.dn", "ou=RealmRoles,dc=keycloak,dc=org") - .config("roles.client.dn", "ou={0},dc=keycloak,dc=org") - .config("roles.common.dn", "dc=keycloak,dc=org") // this is the top DN that finds both client and realm roles - .config("membership.ldap.attribute", "member") - .config("role.name.ldap.attribute", "cn") - .config("role.object.classes", "groupOfNames") - .config("role.attributes", "ou") - .config("mode", "LDAP_ONLY") - .config("use.realm.roles.mapping", "true") - .config(LDAPConstants.CONNECTION_POOLING, "true"); - - cf.spi("client").config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .spi("clientScope").config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .spi("group").config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .spi("realm").config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .spi("role").config("map.storage.provider", LdapMapStorageProviderFactory.PROVIDER_ID) - .spi(DeploymentStateSpi.NAME).config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .spi(StoreFactorySpi.NAME).config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .spi("user").config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .spi(UserSessionSpi.NAME).config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .spi(UserLoginFailureSpi.NAME).config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .spi("authorizationPersister").config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .spi("authenticationSessions").config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .spi(EventStoreSpi.NAME).config("map.storage-admin-events.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .spi(EventStoreSpi.NAME).config("map.storage-auth-events.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .spi(SingleUseObjectSpi.NAME).config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) - .spi("publicKeyStorage").config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID); - - } - - static { - System.setProperty(LDAPEmbeddedServer.PROPERTY_ENABLE_SSL, "false"); - } - - @Override - public Statement classRule(Statement base, Description description) { - return ldapRule.apply(base, description); - } - -} diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Map.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Map.java deleted file mode 100644 index 0f140cd757ac..000000000000 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/parameters/Map.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2020 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.keycloak.testsuite.model.parameters; - -import org.keycloak.authorization.store.StoreFactorySpi; -import org.keycloak.events.EventStoreSpi; -import org.keycloak.keys.PublicKeyStorageSpi; -import org.keycloak.models.DeploymentStateSpi; -import org.keycloak.models.SingleUseObjectProviderFactory; -import org.keycloak.models.SingleUseObjectSpi; -import org.keycloak.models.UserLoginFailureSpi; -import org.keycloak.models.UserSessionSpi; -import org.keycloak.models.locking.GlobalLockProviderSpi; -import org.keycloak.models.locking.NoneGlobalLockProviderFactory; -import org.keycloak.models.map.authSession.MapRootAuthenticationSessionProviderFactory; -import org.keycloak.models.map.authorization.MapAuthorizationStoreFactory; -import org.keycloak.models.map.events.MapEventStoreProviderFactory; -import org.keycloak.models.map.keys.MapPublicKeyStorageProviderFactory; -import org.keycloak.models.map.loginFailure.MapUserLoginFailureProviderFactory; -import org.keycloak.models.map.singleUseObject.MapSingleUseObjectProviderFactory; -import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProviderFactory; -import org.keycloak.models.map.userSession.MapUserSessionProviderFactory; -import org.keycloak.sessions.AuthenticationSessionSpi; -import org.keycloak.testsuite.model.KeycloakModelParameters; -import org.keycloak.models.map.client.MapClientProviderFactory; -import org.keycloak.models.map.clientscope.MapClientScopeProviderFactory; -import org.keycloak.models.map.group.MapGroupProviderFactory; -import org.keycloak.models.map.realm.MapRealmProviderFactory; -import org.keycloak.models.map.role.MapRoleProviderFactory; -import org.keycloak.models.map.deploymentState.MapDeploymentStateProviderFactory; -import org.keycloak.models.map.storage.MapStorageSpi; -import org.keycloak.models.map.user.MapUserProviderFactory; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.provider.Spi; -import org.keycloak.testsuite.model.Config; -import com.google.common.collect.ImmutableSet; -import java.util.Set; - -/** - * - * @author hmlnarik - */ -public class Map extends KeycloakModelParameters { - - static final Set> ALLOWED_SPIS = ImmutableSet.>builder() - .add(AuthenticationSessionSpi.class) - .add(SingleUseObjectSpi.class) - .add(PublicKeyStorageSpi.class) - .add(MapStorageSpi.class) - - .build(); - - static final Set> ALLOWED_FACTORIES = ImmutableSet.>builder() - .add(MapAuthorizationStoreFactory.class) - .add(MapClientProviderFactory.class) - .add(MapClientScopeProviderFactory.class) - .add(MapGroupProviderFactory.class) - .add(MapRealmProviderFactory.class) - .add(MapRoleProviderFactory.class) - .add(MapRootAuthenticationSessionProviderFactory.class) - .add(MapDeploymentStateProviderFactory.class) - .add(MapUserProviderFactory.class) - .add(MapUserSessionProviderFactory.class) - .add(MapUserLoginFailureProviderFactory.class) - .add(NoneGlobalLockProviderFactory.class) - .add(MapEventStoreProviderFactory.class) - .add(SingleUseObjectProviderFactory.class) - .add(MapPublicKeyStorageProviderFactory.class) - .build(); - - public Map() { - super(ALLOWED_SPIS, ALLOWED_FACTORIES); - } - - @Override - public void updateConfig(Config cf) { - cf.spi(AuthenticationSessionSpi.PROVIDER_ID).defaultProvider(MapRootAuthenticationSessionProviderFactory.PROVIDER_ID) - .spi(SingleUseObjectSpi.NAME).defaultProvider(MapSingleUseObjectProviderFactory.PROVIDER_ID) - .spi("client").defaultProvider(MapClientProviderFactory.PROVIDER_ID) - .spi("clientScope").defaultProvider(MapClientScopeProviderFactory.PROVIDER_ID) - .spi("group").defaultProvider(MapGroupProviderFactory.PROVIDER_ID) - .spi("realm").defaultProvider(MapRealmProviderFactory.PROVIDER_ID) - .spi("role").defaultProvider(MapRoleProviderFactory.PROVIDER_ID) - .spi(DeploymentStateSpi.NAME).defaultProvider(MapDeploymentStateProviderFactory.PROVIDER_ID) - .spi(StoreFactorySpi.NAME).defaultProvider(MapAuthorizationStoreFactory.PROVIDER_ID) - .spi("user").defaultProvider(MapUserProviderFactory.PROVIDER_ID) - .spi(UserSessionSpi.NAME).defaultProvider(MapUserSessionProviderFactory.PROVIDER_ID) - .spi(UserLoginFailureSpi.NAME).defaultProvider(MapUserLoginFailureProviderFactory.PROVIDER_ID) - .spi(GlobalLockProviderSpi.GLOBAL_LOCK).defaultProvider(NoneGlobalLockProviderFactory.PROVIDER_ID) - .spi(EventStoreSpi.NAME).defaultProvider(MapEventStoreProviderFactory.PROVIDER_ID) - .spi("publicKeyStorage").defaultProvider(MapPublicKeyStorageProviderFactory.PROVIDER_ID) - ; - cf.spi(MapStorageSpi.NAME).provider(ConcurrentHashMapStorageProviderFactory.PROVIDER_ID).config("keyType.single-use-objects", "string"); - } -} diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/HotRodUserSessionClientSessionRelationshipTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/HotRodUserSessionClientSessionRelationshipTest.java deleted file mode 100644 index bc5427f7ce25..000000000000 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/HotRodUserSessionClientSessionRelationshipTest.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright 2022 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.model.session; - -import org.infinispan.client.hotrod.RemoteCache; -import org.junit.Test; -import org.keycloak.models.AuthenticatedClientSessionModel; -import org.keycloak.models.ClientModel; -import org.keycloak.models.Constants; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserSessionModel; -import org.keycloak.models.UserSessionProvider; -import org.keycloak.models.map.storage.ModelEntityUtil; -import org.keycloak.models.map.storage.hotRod.connections.DefaultHotRodConnectionProviderFactory; -import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProvider; -import org.keycloak.models.map.storage.hotRod.userSession.HotRodUserSessionEntity; -import org.keycloak.testsuite.model.KeycloakModelTest; -import org.keycloak.testsuite.model.RequireProvider; - -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.aMapWithSize; -import static org.hamcrest.Matchers.anEmptyMap; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.notNullValue; -import static org.keycloak.protocol.oidc.OIDCConfigAttributes.CLIENT_SESSION_IDLE_TIMEOUT; -import static org.keycloak.protocol.oidc.OIDCConfigAttributes.CLIENT_SESSION_MAX_LIFESPAN; - -@RequireProvider(UserSessionProvider.class) -@RequireProvider(value = HotRodConnectionProvider.class, only = DefaultHotRodConnectionProviderFactory.PROVIDER_ID) -public class HotRodUserSessionClientSessionRelationshipTest extends KeycloakModelTest { - - private String realmId; - private String CLIENT0_CLIENT_ID = "client0"; - - @Override - public void createEnvironment(KeycloakSession s) { - RealmModel realm = createRealm(s, "test"); - realm.setDefaultRole(s.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName())); - realm.setSsoSessionIdleTimeout(1800); - realm.setSsoSessionMaxLifespan(36000); - this.realmId = realm.getId(); - s.clients().addClient(realm, CLIENT0_CLIENT_ID); - - s.users().addUser(realm, "user1").setEmail("user1@localhost"); - } - - @Override - public void cleanEnvironment(KeycloakSession s) { - if (realmId != null) { - s.realms().removeRealm(realmId); - } - } - - @Test - public void testClientSessionAreRemovedOnUserSessionRemoval() { - AtomicReference uSessionId = new AtomicReference<>(); - AtomicReference cSessionId = new AtomicReference<>(); - prepareSessions(uSessionId, cSessionId); - - withRealm(realmId, (session, realm) -> { - UserSessionModel uSession = session.sessions().getUserSession(realm, uSessionId.get()); - session.sessions().removeUserSession(realm, uSession); - return null; - }); - - assertCacheContains(remoteCache -> assertThat(remoteCache, anEmptyMap())); - } - - @Test - public void testSessionsAreRemovedOnUserRemoval() { - AtomicReference uSessionId = new AtomicReference<>(); - AtomicReference cSessionId = new AtomicReference<>(); - prepareSessions(uSessionId, cSessionId); - - withRealm(realmId, (session, realm) -> { - session.users().removeUser(realm, session.users().getUserByUsername(realm, "user1")); - return null; - }); - - assertCacheContains(remoteCache -> { - assertThat(remoteCache, anEmptyMap()); - }); - } - - @Test - public void testSessionsAreRemovedOnRealmRemoval() { - AtomicReference uSessionId = new AtomicReference<>(); - AtomicReference cSessionId = new AtomicReference<>(); - prepareSessions(uSessionId, cSessionId); - - withRealm(realmId, (session, realm) -> { - session.realms().removeRealm(realm.getId()); - return null; - }); - - assertCacheContains(remoteCache -> { - assertThat(remoteCache, anEmptyMap()); - }); - } - - @Test - public void testExpiredClientSessionReferenceIsNotPresentInUserSession() { - // Set lower client session timeouts - withRealm(realmId, (session, realm) -> { - ClientModel client = realm.getClientByClientId(CLIENT0_CLIENT_ID); - client.setAttribute(CLIENT_SESSION_IDLE_TIMEOUT, "60"); - client.setAttribute(CLIENT_SESSION_MAX_LIFESPAN, "65"); - return null; - }); - - AtomicReference uSessionId = new AtomicReference<>(); - AtomicReference cSessionId = new AtomicReference<>(); - prepareSessions(uSessionId, cSessionId); - - // Move in time when client session should be expired but user session not - setTimeOffset(70); - - // Try to create a new client session for the same user session - withRealm(realmId, (session, realm) -> { - ClientModel client = realm.getClientByClientId(CLIENT0_CLIENT_ID); - UserSessionModel uSession = session.sessions().getUserSession(realm, uSessionId.get()); - assertThat(uSession.getAuthenticatedClientSessions(), anEmptyMap()); - assertThat(session.sessions().createClientSession(realm, client, uSession), notNullValue()); - return null; - }); - - // Check session does not contain a reference to expired client session - assertCacheContains(remoteCache -> { - HotRodUserSessionEntity hotRodUserSessionEntity = remoteCache.get(uSessionId.get()); - assertThat(hotRodUserSessionEntity.authenticatedClientSessions, hasSize(1)); - }); - } - - private void assertCacheContains(Consumer> checker) { - withRealm(realmId, (session, realm) -> { - HotRodConnectionProvider provider = session.getProvider(HotRodConnectionProvider.class); - RemoteCache remoteCache = provider.getRemoteCache(ModelEntityUtil.getModelName(UserSessionModel.class)); - checker.accept(remoteCache); - return null; - }); - } - - private void prepareSessions(AtomicReference uSessionId, AtomicReference cSessionId) { - withRealm(realmId, (session, realm) -> { - UserSessionModel uSession = session.sessions().createUserSession(null, realm, session.users().getUserByUsername(realm, "user1"), "user1", "127.0.0.1", "form", true, null, null, UserSessionModel.SessionPersistenceState.PERSISTENT); - ClientModel client = realm.getClientByClientId(CLIENT0_CLIENT_ID); - - AuthenticatedClientSessionModel cSession = session.sessions().createClientSession(realm, client, uSession); - - uSessionId.set(uSession.getId()); - cSessionId.set(cSession.getId()); - return null; - }); - - assertCacheContains(remoteCache -> { - assertThat(remoteCache, aMapWithSize(2)); - assertThat(remoteCache.keySet(), containsInAnyOrder(uSessionId.get(), cSessionId.get())); - }); - } -} diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/OfflineSessionPersistenceTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/OfflineSessionPersistenceTest.java index 802202c93bf9..569a9ba6424a 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/OfflineSessionPersistenceTest.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/OfflineSessionPersistenceTest.java @@ -16,7 +16,6 @@ */ package org.keycloak.testsuite.model.session; -import org.infinispan.client.hotrod.RemoteCache; import org.infinispan.commons.CacheException; import org.keycloak.models.AuthenticatedClientSessionModel; import org.keycloak.models.ClientModel; @@ -28,13 +27,8 @@ import org.keycloak.models.UserProvider; import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionProvider; -import org.keycloak.models.map.storage.ModelEntityUtil; -import org.keycloak.models.map.storage.hotRod.connections.DefaultHotRodConnectionProviderFactory; -import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProvider; -import org.keycloak.models.map.storage.hotRod.userSession.HotRodUserSessionEntity; import org.keycloak.models.session.UserSessionPersisterProvider; import org.keycloak.models.sessions.infinispan.InfinispanUserSessionProvider; -import org.keycloak.models.sessions.infinispan.InfinispanUserSessionProviderFactory; import org.keycloak.services.managers.RealmManager; import org.keycloak.testsuite.model.KeycloakModelTest; import org.keycloak.testsuite.model.RequireProvider; @@ -131,39 +125,6 @@ public void testPersistenceSingleNodeDeleteRealm() { } } - @Test - @RequireProvider(value = HotRodConnectionProvider.class, only = DefaultHotRodConnectionProviderFactory.PROVIDER_ID) - public void testOfflineSessionsRemovedAfterDeleteRealm() { - String realmId2 = inComittedTransaction(session -> { return prepareRealm(session, "realm2").getId(); }); - List userIds2 = withRealm(realmId2, (session, realm) -> IntStream.range(0, USER_COUNT) - .mapToObj(i -> session.users().addUser(realm, "user2-" + i)) - .map(UserModel::getId) - .collect(Collectors.toList()) - ); - - try { - List offlineSessionIds2 = createOfflineSessions(realmId2, userIds2); - assertOfflineSessionsExist(realmId2, offlineSessionIds2); - - // Simulate server restart - reinitializeKeycloakSessionFactory(); - - assertOfflineSessionsExist(realmId2, offlineSessionIds2); - - inComittedTransaction(session -> { - session.realms().removeRealm(realmId2); - }); - - inComittedTransaction(session -> { - HotRodConnectionProvider provider = session.getProvider(HotRodConnectionProvider.class); - RemoteCache remoteCache = provider.getRemoteCache(ModelEntityUtil.getModelName(UserSessionModel.class)); - assertThat(remoteCache, Matchers.anEmptyMap()); - }); - } finally { - withRealm(realmId2, (session, realm) -> realm == null ? false : new RealmManager(session).removeRealm(realm)); - } - } - @Test public void testPersistenceSingleNode() { List offlineSessionIds = createOfflineSessions(realmId, userIds); @@ -176,7 +137,6 @@ public void testPersistenceSingleNode() { @Test(timeout = 90 * 1000) @RequireProvider(UserSessionPersisterProvider.class) - @RequireProvider(value = UserSessionProvider.class, only = InfinispanUserSessionProviderFactory.PROVIDER_ID) public void testPersistenceMultipleNodesClientSessionAtSameNode() throws InterruptedException { int numClients = 2; List clientIds = withRealm(realmId, (session, realm) -> IntStream.range(0, numClients) @@ -233,7 +193,6 @@ public void testPersistenceMultipleNodesClientSessionAtSameNode() throws Interru @Test(timeout = 90 * 1000) @RequireProvider(UserSessionPersisterProvider.class) - @RequireProvider(value = UserSessionProvider.class, only = InfinispanUserSessionProviderFactory.PROVIDER_ID) public void testPersistenceMultipleNodesClientSessionsAtRandomNode() throws InterruptedException { List clientIds = withRealm(realmId, (session, realm) -> IntStream.range(0, 5) .mapToObj(cid -> session.clients().addClient(realm, "client-" + cid)) @@ -286,7 +245,6 @@ public void testPersistenceMultipleNodesClientSessionsAtRandomNode() throws Inte @Test @RequireProvider(UserSessionPersisterProvider.class) - @RequireProvider(value = UserSessionProvider.class, only = InfinispanUserSessionProviderFactory.PROVIDER_ID) public void testOfflineSessionLoadingAfterCacheRemoval() { List offlineSessionIds = createOfflineSessions(realmId, userIds); assertOfflineSessionsExist(realmId, offlineSessionIds); @@ -311,7 +269,6 @@ public void testOfflineSessionLoadingAfterCacheRemoval() { @Test @RequireProvider(UserSessionPersisterProvider.class) - @RequireProvider(value = UserSessionProvider.class, only = InfinispanUserSessionProviderFactory.PROVIDER_ID) public void testLazyClientSessionStatsFetching() { List clientIds = withRealm(realmId, (session, realm) -> IntStream.range(0, 5) .mapToObj(cid -> session.clients().addClient(realm, "client-" + cid)) @@ -346,7 +303,6 @@ public void testLazyClientSessionStatsFetching() { @Test @RequireProvider(UserSessionPersisterProvider.class) - @RequireProvider(value = UserSessionProvider.class, only = InfinispanUserSessionProviderFactory.PROVIDER_ID) public void testLazyOfflineUserSessionFetching() { Map> offlineSessionIdsDetailed = createOfflineSessionsDetailed(realmId, userIds); Collection offlineSessionIds = offlineSessionIdsDetailed.values().stream().flatMap(Set::stream).collect(Collectors.toCollection(TreeSet::new)); @@ -380,7 +336,6 @@ private String createOfflineClientSession(String offlineUserSessionId, String cl @Test(timeout = 90 * 1000) @RequireProvider(UserSessionPersisterProvider.class) - @RequireProvider(value = UserSessionProvider.class, only = InfinispanUserSessionProviderFactory.PROVIDER_ID) public void testPersistenceClientSessionsMultipleNodes() throws InterruptedException { // Create offline sessions List offlineSessionIds = createOfflineSessions(realmId, userIds); diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionConcurrencyTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionConcurrencyTest.java index 97f50b9fbf62..0b09cdba27f1 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionConcurrencyTest.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionConcurrencyTest.java @@ -25,17 +25,10 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionProvider; -import org.keycloak.models.UserSessionSpi; -import org.keycloak.models.map.storage.ModelEntityUtil; -import org.keycloak.models.map.storage.file.FileMapStorageProviderFactory; -import org.keycloak.models.map.storage.hotRod.HotRodMapStorageProviderFactory; -import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProvider; -import org.keycloak.models.map.userSession.MapUserSessionProviderFactory; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.testsuite.model.KeycloakModelTest; import org.keycloak.testsuite.model.RequireProvider; -import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -43,9 +36,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.aMapWithSize; -import static org.hamcrest.Matchers.anEmptyMap; import static org.hamcrest.Matchers.startsWith; -import static org.junit.Assume.assumeFalse; import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification; @@ -56,7 +47,6 @@ public class UserSessionConcurrencyTest extends KeycloakModelTest { private static final int CLIENTS_COUNT = 10; private static final ThreadLocal wasWriting = ThreadLocal.withInitial(() -> false); - private final boolean isHotRodStore = HotRodMapStorageProviderFactory.PROVIDER_ID.equals(CONFIG.getConfig().get("userSessions.map.storage.provider")); @Override public void createEnvironment(KeycloakSession s) { @@ -87,13 +77,6 @@ protected boolean isUseSameKeycloakSessionFactoryForAllThreads() { @Test public void testConcurrentNotesChange() throws InterruptedException { - // Defer this one until file locking is available - // Skip the test if EventProvider == File - String evProvider = CONFIG.getConfig().get(UserSessionSpi.NAME + ".provider"); - String evMapStorageProvider = CONFIG.getConfig().get(UserSessionSpi.NAME + ".map.storage.provider"); - assumeFalse(MapUserSessionProviderFactory.PROVIDER_ID.equals(evProvider) && - (evMapStorageProvider == null || FileMapStorageProviderFactory.PROVIDER_ID.equals(evMapStorageProvider))); - // Create user session String uId = withRealm(this.realmId, (session, realm) -> session.sessions().createUserSession(null, realm, session.users().getUserByUsername(realm, "user1"), "user1", "127.0.0.1", "form", true, null, null, UserSessionModel.SessionPersistenceState.PERSISTENT)).getId(); @@ -134,13 +117,5 @@ public void testConcurrentNotesChange() throws InterruptedException { }); inComittedTransaction((Consumer) session -> session.realms().removeRealm(realmId)); - if (isHotRodStore) { - inComittedTransaction(session -> { - HotRodConnectionProvider provider = session.getProvider(HotRodConnectionProvider.class); - Map remoteCache = provider.getRemoteCache(ModelEntityUtil.getModelName(UserSessionModel.class)); - - assertThat(remoteCache, anEmptyMap()); - }); - } } } diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionExpirationTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionExpirationTest.java deleted file mode 100644 index 288e9430bc2f..000000000000 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionExpirationTest.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2022 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.model.session; - -import org.junit.Test; -import org.keycloak.models.Constants; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RealmProvider; -import org.keycloak.models.UserSessionModel; -import org.keycloak.models.UserSessionProvider; -import org.keycloak.models.map.userSession.MapUserSessionProviderFactory; -import org.keycloak.testsuite.model.KeycloakModelTest; -import org.keycloak.testsuite.model.RequireProvider; - -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; - -@RequireProvider(value = UserSessionProvider.class, only = MapUserSessionProviderFactory.PROVIDER_ID) -@RequireProvider(RealmProvider.class) -public class UserSessionExpirationTest extends KeycloakModelTest { - - private String realmId; - - @Override - public void createEnvironment(KeycloakSession s) { - RealmModel realm = createRealm(s, "test"); - realm.setDefaultRole(s.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName())); - - s.users().addUser(realm, "user1").setEmail("user1@localhost"); - this.realmId = realm.getId(); - } - - @Override - public void cleanEnvironment(KeycloakSession s) { - s.realms().removeRealm(realmId); - } - - @Test - public void testSsoSessionIdleTimeout() { - - // Set low ssoSessionIdleTimeout - withRealm(realmId, (session, realm) -> { - realm.setSsoSessionIdleTimeout(5); - realm.setSsoSessionMaxLifespan(36000); - realm.setClientSessionIdleTimeout(5); - return null; - }); - - String uSId= withRealm(realmId, (session, realm) -> session.sessions().createUserSession(null, realm, session.users().getUserByUsername(realm, "user1"), "user1", "127.0.0.1", "form", true, null, null, UserSessionModel.SessionPersistenceState.PERSISTENT).getId()); - - assertThat(withRealm(realmId, (session, realm) -> session.sessions().getUserSession(realm, uSId)), notNullValue()); - - setTimeOffset(5); - - assertThat(withRealm(realmId, (session, realm) -> session.sessions().getUserSession(realm, uSId)), nullValue()); - } -} diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionInitializerTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionInitializerTest.java index a96c044132ad..2a9292c628fc 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionInitializerTest.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionInitializerTest.java @@ -33,7 +33,6 @@ import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionProvider; import org.keycloak.models.session.UserSessionPersisterProvider; -import org.keycloak.models.sessions.infinispan.InfinispanUserSessionProviderFactory; import java.util.LinkedList; import java.util.List; @@ -155,7 +154,6 @@ public void testUserSessionInitializerWithDeletingClient() { } @Test - @RequireProvider(value = UserSessionProvider.class, only = InfinispanUserSessionProviderFactory.PROVIDER_ID) public void testUserSessionPropagationBetweenSites() throws InterruptedException { AtomicInteger index = new AtomicInteger(); AtomicReference userSessionId = new AtomicReference<>(); diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionPersisterProviderTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionPersisterProviderTest.java index b5a96cf8cc76..2f51d3d62efa 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionPersisterProviderTest.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionPersisterProviderTest.java @@ -53,7 +53,6 @@ import static org.junit.Assert.assertTrue; import static org.hamcrest.MatcherAssert.assertThat; import org.keycloak.models.Constants; -import org.keycloak.models.sessions.infinispan.InfinispanUserSessionProviderFactory; import org.hamcrest.Matchers; import org.keycloak.storage.client.ClientStorageProvider; import org.keycloak.storage.client.ClientStorageProviderModel; @@ -67,7 +66,7 @@ * @author Martin Kanis */ @RequireProvider(UserSessionPersisterProvider.class) -@RequireProvider(value = UserSessionProvider.class, only = InfinispanUserSessionProviderFactory.PROVIDER_ID) +@RequireProvider(UserSessionProvider.class) @RequireProvider(UserProvider.class) @RequireProvider(RealmProvider.class) public class UserSessionPersisterProviderTest extends KeycloakModelTest { diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionProviderModelTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionProviderModelTest.java index b19e6b6d79c2..e04d5cd3ad6c 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionProviderModelTest.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionProviderModelTest.java @@ -17,7 +17,6 @@ package org.keycloak.testsuite.model.session; import org.hamcrest.Matchers; -import org.infinispan.client.hotrod.RemoteCache; import org.junit.Assert; import org.junit.Test; import org.keycloak.models.AuthenticatedClientSessionModel; @@ -26,19 +25,10 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RealmProvider; -import org.keycloak.models.UserManager; import org.keycloak.models.UserModel; import org.keycloak.models.UserProvider; import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionProvider; -import org.keycloak.models.map.storage.ModelEntityUtil; -import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProviderFactory; -import org.keycloak.models.map.storage.hotRod.connections.DefaultHotRodConnectionProviderFactory; -import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProvider; -import org.keycloak.models.map.storage.hotRod.userSession.HotRodUserSessionEntity; -import org.keycloak.models.map.userSession.MapUserSessionProvider; -import org.keycloak.models.map.userSession.MapUserSessionProviderFactory; -import org.keycloak.models.sessions.infinispan.InfinispanUserSessionProviderFactory; import org.keycloak.models.sessions.infinispan.changes.sessions.PersisterLastSessionRefreshStoreFactory; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.ResetTimeOffsetEvent; @@ -50,7 +40,6 @@ import java.util.Collections; import java.util.List; import java.util.Set; -import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; @@ -59,7 +48,6 @@ import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assume.assumeFalse; import static org.keycloak.testsuite.model.session.UserSessionPersisterProviderTest.createClients; import static org.keycloak.testsuite.model.session.UserSessionPersisterProviderTest.createSessions; @@ -164,22 +152,8 @@ public void testExpiredClientSessions() { Assert.assertEquals(origSessions[1], userSession); }); - - // not possible to expire client session without expiring user sessions with time offset in map storage because - // expiration in map storage takes min of (clientSessionIdleExpiration, ssoSessionIdleTimeout) inComittedTransaction(session -> { - if (session.getProvider(UserSessionProvider.class) instanceof MapUserSessionProvider) { - RealmModel realm = session.realms().getRealm(realmId); - - UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[0].getId()); - - userSession.getAuthenticatedClientSessions().values().stream().forEach(clientSession -> { - // expire client sessions - clientSession.setTimestamp(1); - }); - } else { - setTimeOffset(1000); - } + setTimeOffset(1000); }); inComittedTransaction(session -> { @@ -230,7 +204,6 @@ public void testTransientUserSessionIsNotPersisted() { } @Test - @RequireProvider(value = UserSessionProvider.class, only = InfinispanUserSessionProviderFactory.PROVIDER_ID) public void testClientSessionIsNotPersistedForTransientUserSession() { Object[] transientUserSessionWithClientSessionId = inComittedTransaction(session -> { RealmModel realm = session.realms().getRealm(realmId); @@ -255,33 +228,8 @@ public void testClientSessionIsNotPersistedForTransientUserSession() { }); } - @Test - @RequireProvider(value = HotRodConnectionProvider.class, only = DefaultHotRodConnectionProviderFactory.PROVIDER_ID) - public void testRemoteCachesParallel() throws InterruptedException { - inIndependentFactories(4, 30, () -> inComittedTransaction(session -> { - HotRodConnectionProvider provider = session.getProvider(HotRodConnectionProvider.class); - RemoteCache remoteCache = provider.getRemoteCache(ModelEntityUtil.getModelName(UserSessionModel.class)); - HotRodUserSessionEntity userSessionEntity = new HotRodUserSessionEntity(); - userSessionEntity.id = UUID.randomUUID().toString(); - userSessionEntity.realmId = realmId; - remoteCache.put(userSessionEntity.id, userSessionEntity); - })); - - inComittedTransaction(session -> { - HotRodConnectionProvider provider = session.getProvider(HotRodConnectionProvider.class); - RemoteCache remoteCache = provider.getRemoteCache(ModelEntityUtil.getModelName(UserSessionModel.class)); - assertThat(remoteCache.size(), Matchers.is(4)); - }); - } - @Test public void testCreateUserSessionsParallel() throws InterruptedException { - // Skip the test if MapUserSessionProvider == CHM - String usProvider = CONFIG.getConfig().get("userSessions.provider"); - String usMapStorageProvider = CONFIG.getConfig().get("userSessions.map.storage.provider"); - assumeFalse(MapUserSessionProviderFactory.PROVIDER_ID.equals(usProvider) && - (usMapStorageProvider == null || ConcurrentHashMapStorageProviderFactory.PROVIDER_ID.equals(usMapStorageProvider))); - Set userSessionIds = Collections.newSetFromMap(new ConcurrentHashMap<>()); CountDownLatch latch = new CountDownLatch(4); diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionProviderOfflineModelTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionProviderOfflineModelTest.java index 4d4d18ebd217..bbb6c3774f3d 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionProviderOfflineModelTest.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/session/UserSessionProviderOfflineModelTest.java @@ -68,7 +68,7 @@ * @author Martin Kanis */ @RequireProvider(UserSessionPersisterProvider.class) -@RequireProvider(value=UserSessionProvider.class, only={"infinispan"}) +@RequireProvider(UserSessionProvider.class) @RequireProvider(UserProvider.class) @RequireProvider(RealmProvider.class) public class UserSessionProviderOfflineModelTest extends KeycloakModelTest { diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/singleUseObject/SingleUseObjectModelTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/singleUseObject/SingleUseObjectModelTest.java index 74017c1dfad1..41992ba000ae 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/singleUseObject/SingleUseObjectModelTest.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/singleUseObject/SingleUseObjectModelTest.java @@ -26,12 +26,7 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.SingleUseObjectProvider; -import org.keycloak.models.SingleUseObjectProviderFactory; -import org.keycloak.models.SingleUseObjectSpi; import org.keycloak.models.UserModel; -import org.keycloak.models.map.singleUseObject.MapSingleUseObjectProviderFactory; -import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProviderFactory; -import org.keycloak.models.map.userSession.MapUserSessionProviderFactory; import org.keycloak.testsuite.model.KeycloakModelTest; import org.keycloak.testsuite.model.RequireProvider; @@ -43,7 +38,6 @@ import java.util.concurrent.atomic.AtomicReference; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assume.assumeFalse; @RequireProvider(SingleUseObjectProvider.class) public class SingleUseObjectModelTest extends KeycloakModelTest { @@ -171,12 +165,6 @@ public void testSingleUseStore() { @Test public void testCluster() throws InterruptedException { - // Skip the test if SingleUseObjectProvider == CHM - String suProvider = CONFIG.getConfig().get(SingleUseObjectSpi.NAME + ".provider"); - String suMapStorageProvider = CONFIG.getConfig().get(SingleUseObjectSpi.NAME + ".map.storage.provider"); - assumeFalse(MapSingleUseObjectProviderFactory.PROVIDER_ID.equals(suProvider) && - (suMapStorageProvider == null || ConcurrentHashMapStorageProviderFactory.PROVIDER_ID.equals(suMapStorageProvider))); - AtomicInteger index = new AtomicInteger(); CountDownLatch afterFirstNodeLatch = new CountDownLatch(1); CountDownLatch afterDeleteLatch = new CountDownLatch(1); diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/storage/tree/sample/Dict.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/storage/tree/sample/Dict.java deleted file mode 100644 index 1beaaf80cb97..000000000000 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/storage/tree/sample/Dict.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright 2021 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.keycloak.testsuite.model.storage.tree.sample; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -import org.keycloak.models.map.client.MapClientEntity; -import org.keycloak.models.map.client.MapClientEntityFields; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.common.EntityField; -import org.keycloak.models.map.common.UpdatableEntity; -import org.keycloak.models.map.common.delegate.EntityFieldDelegate; -import org.keycloak.models.map.common.delegate.HasEntityFieldDelegate; - -/** - * - * @author hmlnarik - */ -public class Dict extends UpdatableEntity.Impl implements EntityFieldDelegate { - - public static final String CLIENT_FIELD_LOGO = "LOGO"; - public static final String CLIENT_FIELD_ENABLED = "ENABLED"; - public static final String CLIENT_FIELD_NAME = "NAME"; - - private static final Set CLIENT_ALLOWED_KEYS = new HashSet<>(Arrays.asList(CLIENT_FIELD_NAME, CLIENT_FIELD_ENABLED, CLIENT_FIELD_LOGO)); - - public static MapClientEntity clientDelegate() { - // To be replaced by dynamic mapper config - Map fieldName2key = new HashMap<>(); - fieldName2key.put(MapClientEntityFields.ID.getName(), CLIENT_FIELD_NAME); - fieldName2key.put(MapClientEntityFields.CLIENT_ID.getName(), CLIENT_FIELD_NAME); - fieldName2key.put(MapClientEntityFields.ENABLED.getName(), CLIENT_FIELD_ENABLED); - - Map attributeName2key = new HashMap<>(); - attributeName2key.put("logo", CLIENT_FIELD_LOGO); - - Dict dict = new Dict<>(CLIENT_ALLOWED_KEYS, fieldName2key, attributeName2key); - return DeepCloner.DUMB_CLONER.entityFieldDelegate(MapClientEntity.class, dict); - } - - @SuppressWarnings("unchecked") - public static Dict asDict(E entity) { - return (entity instanceof HasEntityFieldDelegate && ((HasEntityFieldDelegate) entity).getEntityFieldDelegate() instanceof Dict) - ? (Dict) ((HasEntityFieldDelegate) entity).getEntityFieldDelegate() - : null; - } - - private final Set allowedKeys; - private final Map contents = new HashMap<>(); - private final Map fieldName2key; - private final Map attributeName2key; - - public Dict(Set allowedKeys, Map fieldName2key, Map attributeName2key) { - this.allowedKeys = allowedKeys; - this.fieldName2key = fieldName2key; - this.attributeName2key = attributeName2key; - } - - @Override - public > & EntityField> Object get(EF field) { - if ("Attributes".equals(field.getName())) { - return attributeName2key.entrySet().stream() - .filter(me -> get(me.getValue()) != null) - .collect(Collectors.toMap(me -> me.getKey(), me -> Collections.singletonList(get(me.getValue())))); - } - String key = fieldName2key.get(field.getName()); - if (key != null) { - return get(key); - } - return null; - } - - @Override - public > & EntityField> void set(EF field, T value) { - String key = fieldName2key.get(field.getName()); - if (key != null) { - put(key, value); - } - } - - @Override - public > & EntityField> Object mapGet(EF field, K key) { - if ("Attributes".equals(field.getName()) && attributeName2key.containsKey(key)) { - Object v = get(attributeName2key.get(key)); - return v == null ? null : Collections.singletonList(get(attributeName2key.get(key))); - } - return null; - } - - @Override - public > & EntityField> void mapPut(EF field, K key, T value) { - if ("Attributes".equals(field.getName()) && attributeName2key.containsKey(key) && (value instanceof List)) { - List l = (List) value; - if (l.isEmpty()) { - remove(attributeName2key.get(key)); - } else { - put(attributeName2key.get(key), l.get(0)); - } - } - } - - @Override - public > & EntityField> Object mapRemove(EF field, K key) { - if ("Attributes".equals(field.getName()) && attributeName2key.containsKey(key)) { - Object o = remove(attributeName2key.get(key)); - return o == null ? null : Collections.singletonList(o); - } - return null; - } - - - @Override - public > & org.keycloak.models.map.common.EntityField> void collectionAdd(EF field, T value) { - throw new UnsupportedOperationException("Not supported yet."); - } - - @Override - public > & org.keycloak.models.map.common.EntityField> Object collectionRemove(EF field, T value) { - throw new UnsupportedOperationException("Not supported yet."); - } - - protected boolean isKeyAllowed(String key) { - return allowedKeys.contains(key); - } - - public Object get(String key) { - return isKeyAllowed(key) ? contents.get(key) : null; - } - - public void put(String key, Object value) { - if (isKeyAllowed(key)) { - updated |= ! Objects.equals(contents.put(key, value), value); - } - } - - public Object remove(String key) { - key = key == null ? null : key.toUpperCase(); - if (isKeyAllowed(key)) { - Object res = contents.remove(key); - updated |= res != null; - return res; - } - return null; - } -} diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/storage/tree/sample/DictStorage.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/storage/tree/sample/DictStorage.java deleted file mode 100644 index 994bb749ccf4..000000000000 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/storage/tree/sample/DictStorage.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2021 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.keycloak.testsuite.model.storage.tree.sample; - -import org.keycloak.models.map.common.AbstractEntity; -import org.keycloak.models.map.common.DeepCloner; -import org.keycloak.models.map.storage.MapStorage; -import org.keycloak.models.map.storage.QueryParameters; - -import java.util.List; -import java.util.Objects; -import java.util.stream.Stream; - -/** - * - * @author hmlnarik - */ -public class DictStorage implements MapStorage { - - private final DeepCloner cloner; - - private final List store; - - public DictStorage(DeepCloner cloner, List store) { - this.cloner = cloner; - this.store = store; - } - - List getStore() { - return store; - } - - @Override - public V create(V value) { - V res = cloner.from(value); - store.add(res); - return res; - } - - @Override - public V read(String key) { - return store.stream() - .filter(e -> Objects.equals(e.getId(), key)) - .findFirst() - .orElse(null); - } - - @Override - public Stream read(QueryParameters queryParameters) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public long getCount(QueryParameters queryParameters) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public boolean delete(String key) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public long delete(QueryParameters queryParameters) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } -} diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/storage/tree/sample/DictTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/storage/tree/sample/DictTest.java deleted file mode 100644 index a24fa3749f64..000000000000 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/storage/tree/sample/DictTest.java +++ /dev/null @@ -1,60 +0,0 @@ -package org.keycloak.testsuite.model.storage.tree.sample; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.hasItems; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; - -import java.util.Arrays; - -import org.junit.Test; -import org.keycloak.models.map.client.MapClientEntity; - -public class DictTest { - @Test - public void testDictClientFromMap() { - MapClientEntity mce = Dict.clientDelegate(); - assertThat(mce.getClientId(), nullValue()); - assertThat(mce.isEnabled(), nullValue()); - assertThat(mce.getAttribute("logo"), nullValue()); - assertThat(mce.getAttributes().keySet(), is(empty())); - - Dict.asDict(mce).put(Dict.CLIENT_FIELD_NAME, "name"); - Dict.asDict(mce).put(Dict.CLIENT_FIELD_ENABLED, false); - Dict.asDict(mce).put(Dict.CLIENT_FIELD_LOGO, "thisShouldBeBase64Logo"); - Dict.asDict(mce).put("nonexistent", "nonexistent"); - - assertThat(mce.getId(), is("name")); - assertThat(mce.getClientId(), is("name")); - assertThat(mce.isEnabled(), is(false)); - assertThat(mce.getAttribute("logo"), hasItems("thisShouldBeBase64Logo")); - assertThat(mce.getAttributes().keySet(), hasItems("logo")); - } - - @Test - public void testDictClientFromEntity() { - MapClientEntity mce = Dict.clientDelegate(); - - assertThat(Dict.asDict(mce).get(Dict.CLIENT_FIELD_NAME), nullValue()); - assertThat(Dict.asDict(mce).get(Dict.CLIENT_FIELD_ENABLED), nullValue()); - assertThat(Dict.asDict(mce).get(Dict.CLIENT_FIELD_LOGO), nullValue()); - - mce.setClientId("name"); - mce.setEnabled(false); - mce.setAttribute("logo", Arrays.asList("thisShouldBeBase64Logo")); - mce.setAttribute("blah", Arrays.asList("thisShouldBeBase64Logofdas")); - - assertThat(mce.getAttributes().keySet(), hasItems("logo")); - - assertThat(Dict.asDict(mce).get(Dict.CLIENT_FIELD_NAME), is("name")); - assertThat(Dict.asDict(mce).get(Dict.CLIENT_FIELD_ENABLED), is(false)); - assertThat(Dict.asDict(mce).get(Dict.CLIENT_FIELD_LOGO), is("thisShouldBeBase64Logo")); - - mce.setAttribute("logo", Arrays.asList("thisShouldBeAnotherBase64Logo")); - assertThat(Dict.asDict(mce).get(Dict.CLIENT_FIELD_LOGO), is("thisShouldBeAnotherBase64Logo")); - - mce.removeAttribute("logo"); - assertThat(Dict.asDict(mce).get(Dict.CLIENT_FIELD_LOGO), nullValue()); - } -} diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/transaction/StorageTransactionTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/transaction/StorageTransactionTest.java index 2acac7a52c5d..d9dd6877ffc3 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/transaction/StorageTransactionTest.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/transaction/StorageTransactionTest.java @@ -20,43 +20,18 @@ import org.junit.Test; import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelException; import org.keycloak.models.RealmModel; import org.keycloak.models.RealmProvider; -import org.keycloak.models.UserModel; -import org.keycloak.models.UserSessionModel; -import org.keycloak.models.locking.LockAcquiringTimeoutException; -import org.keycloak.models.map.storage.MapStorageProvider; -import org.keycloak.models.map.storage.hotRod.HotRodMapStorageProviderFactory; -import org.keycloak.models.map.storage.jpa.JpaMapStorageProviderFactory; import org.keycloak.testsuite.model.KeycloakModelTest; import org.keycloak.testsuite.model.RequireProvider; import org.keycloak.testsuite.model.util.TransactionController; -import org.keycloak.utils.LockObjectsForModification; -import jakarta.persistence.OptimisticLockException; -import jakarta.persistence.PessimisticLockException; -import jakarta.transaction.RollbackException; - -import java.util.UUID; -import java.util.function.Function; - -import static org.hamcrest.CoreMatchers.allOf; -import static org.hamcrest.CoreMatchers.anyOf; import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.internal.matchers.ThrowableCauseMatcher.hasCause; -import static org.keycloak.testsuite.model.util.KeycloakAssertions.assertException; @RequireProvider(RealmProvider.class) public class StorageTransactionTest extends KeycloakModelTest { - // System variable is used to simplify configuration for more storages that support pessimistic locking. - // Instead of searching which storage is used and then configure its factory, we can just configure - // lockTimeout like this: .config("lockTimeout", "${keycloak.model.tests.lockTimeout:}") and - // system property will be picked when factory is reinitialized. - public static final String LOCK_TIMEOUT_SYSTEM_PROPERTY = "keycloak.model.tests.lockTimeout"; private String realmId; @Override @@ -135,235 +110,4 @@ public void testRepeatableRead() throws Exception { tx3.commit(); } } - - @Test - // LockObjectForModification currently works only in map-jpa and map-hotrod - @RequireProvider(value = MapStorageProvider.class, only = {JpaMapStorageProviderFactory.PROVIDER_ID, HotRodMapStorageProviderFactory.PROVIDER_ID}) - public void testLockObjectForModificationById() throws Exception { - testLockObjectForModification(session -> LockObjectsForModification.lockRealmsForModification(session, () -> session.realms().getRealm(realmId))); - } - - @Test - // LockObjectForModification currently works only in map-jpa and map-hotrod - @RequireProvider(value = MapStorageProvider.class, only = {JpaMapStorageProviderFactory.PROVIDER_ID, HotRodMapStorageProviderFactory.PROVIDER_ID}) - public void testLockUserSessionForModificationByQuery() throws Exception { - // Create user session - final String sessionId = withRealm(realmId, (session, realm) -> { - UserModel myUser = session.users().addUser(realm, "myUser"); - return session.sessions().createUserSession(realm, myUser, "myUser", "127.0.0.1", "form", true, null, null).getId(); - }); - - testLockObjectForModification(session -> LockObjectsForModification.lockUserSessionsForModification(session, readUserSessionByIdUsingQueryParameters(session, sessionId))); - } - - private void testLockObjectForModification(Function lockedExecution) throws Exception { - String originalTimeoutValue = System.getProperty(LOCK_TIMEOUT_SYSTEM_PROPERTY); - try { - System.setProperty(LOCK_TIMEOUT_SYSTEM_PROPERTY, "300"); - reinitializeKeycloakSessionFactory(); - try (TransactionController tx1 = new TransactionController(getFactory()); - TransactionController tx2 = new TransactionController(getFactory()); - TransactionController tx3 = new TransactionController(getFactory())) { - - tx1.begin(); - tx2.begin(); - - // tx1 acquires lock - tx1.runStep(lockedExecution); - - // tx2 should fail as tx1 locked the realm - assertException(() -> tx2.runStep(lockedExecution), - anyOf(allOf(instanceOf(ModelException.class), hasCause(anyOf(instanceOf(PessimisticLockException.class), instanceOf(org.hibernate.PessimisticLockException.class)))), - instanceOf(LockAcquiringTimeoutException.class))); - - // end both transactions - tx2.rollback(); - tx1.commit(); - - // start new transaction and read again, it should be successful - tx3.begin(); - tx3.runStep(lockedExecution); - tx3.commit(); - } - } finally { - if (originalTimeoutValue == null) { - System.clearProperty(LOCK_TIMEOUT_SYSTEM_PROPERTY); - } else { - System.setProperty(LOCK_TIMEOUT_SYSTEM_PROPERTY, originalTimeoutValue); - } - reinitializeKeycloakSessionFactory(); - } - } - - private LockObjectsForModification.CallableWithoutThrowingAnException readUserSessionByIdUsingQueryParameters(KeycloakSession session, String sessionId) { - RealmModel realm = session.realms().getRealm(realmId); - return () -> session.sessions().getUserSession(realm, sessionId); - } - - @Test - // Optimistic locking works only with map-jpa and map-hotrod - @RequireProvider(value = MapStorageProvider.class, only = {JpaMapStorageProviderFactory.PROVIDER_ID, - HotRodMapStorageProviderFactory.PROVIDER_ID}) - public void testOptimisticLockingExceptionReadById() throws Exception { - withRealm(realmId, (session, realm) -> { - realm.setDisplayName("displayName1"); - return null; - }); - - try (TransactionController tx1 = new TransactionController(getFactory()); - TransactionController tx2 = new TransactionController(getFactory())) { - - // tx1 acquires lock - tx1.begin(); - tx2.begin(); - - // both transactions touch the same entity - tx1.runStep(session -> { - session.realms().getRealm(realmId).setDisplayName("displayName2"); - return null; - }); - tx2.runStep(session -> { - session.realms().getRealm(realmId).setDisplayName("displayName3"); - return null; - }); - - // tx1 transaction should be successful - tx1.commit(); - - // tx2 should fail as tx1 already changed the value - assertException(tx2::commit, - anyOf( - allOf(instanceOf(RuntimeException.class), hasCause(instanceOf(RollbackException.class))), - allOf(instanceOf(ModelException.class), hasCause(instanceOf(OptimisticLockException.class))), - allOf(instanceOf(OptimisticLockException.class)) - )); - } - } - - @Test - // Optimistic locking works only with map-jpa and map-hotrod - @RequireProvider(value = MapStorageProvider.class, only = {JpaMapStorageProviderFactory.PROVIDER_ID, - HotRodMapStorageProviderFactory.PROVIDER_ID}) - public void testOptimisticLockingExceptionReadByQuery() throws Exception { - withRealm(realmId, (session, realm) -> { - realm.setDisplayName("displayName1"); - return null; - }); - - try (TransactionController tx1 = new TransactionController(getFactory()); - TransactionController tx2 = new TransactionController(getFactory())) { - - // tx1 acquires lock - tx1.begin(); - tx2.begin(); - - // both transactions touch the same entity - tx1.runStep(session -> { - session.realms().getRealmByName("1").setDisplayName("displayName2"); - return null; - }); - tx2.runStep(session -> { - session.realms().getRealmByName("1").setDisplayName("displayName3"); - return null; - }); - - // tx1 transaction should be successful - tx1.commit(); - - // tx2 should fail as tx1 already changed the value - assertException(tx2::commit, - anyOf( - allOf(instanceOf(RuntimeException.class), hasCause(instanceOf(RollbackException.class))), - allOf(instanceOf(ModelException.class), hasCause(instanceOf(OptimisticLockException.class))), - allOf(instanceOf(OptimisticLockException.class)) - )); - } - } - - @Test - // Optimistic locking works only with map-jpa and map-hotrod - @RequireProvider(value = MapStorageProvider.class, only = {JpaMapStorageProviderFactory.PROVIDER_ID, - HotRodMapStorageProviderFactory.PROVIDER_ID}) - public void testOptimisticLockingDeleteWhenReadingByQuery() throws Exception { - withRealm(realmId, (session, realm) -> { - session.users().addUser(realm, "user", "user", false, false); - return null; - }); - - try (TransactionController tx1 = new TransactionController(getFactory()); - TransactionController tx2 = new TransactionController(getFactory())) { - - tx1.begin(); - tx2.begin(); - - // both transactions touch the same entity - tx1.runStep(session -> { - // read by criteria - session.users().getUserByUsername(session.realms().getRealm(realmId), "user").setFirstName("firstName"); - return null; - }); - tx2.runStep(session -> { - RealmModel realm = session.realms().getRealm(realmId); - - // remove by id - session.users().removeUser(realm, session.users().getUserByUsername(realm, "user")); - return null; - }); - - // tx1 transaction should be successful - tx1.commit(); - - // tx2 should fail as tx1 already changed the value - assertException(tx2::commit, - anyOf( - allOf(instanceOf(RuntimeException.class), hasCause(instanceOf(RollbackException.class))), - allOf(instanceOf(ModelException.class), hasCause(instanceOf(OptimisticLockException.class))), - allOf(instanceOf(OptimisticLockException.class)) - )); - } - } - - @Test - // Optimistic locking works only with map-jpa and map-hotrod - @RequireProvider(value = MapStorageProvider.class, only = {JpaMapStorageProviderFactory.PROVIDER_ID, - HotRodMapStorageProviderFactory.PROVIDER_ID}) - public void testOptimisticLockingDeleteWhenReadingById() throws Exception { - String userId = UUID.randomUUID().toString(); - withRealm(realmId, (session, realm) -> { - session.users().addUser(realm, userId, "user", false, false); - return null; - }); - - try (TransactionController tx1 = new TransactionController(getFactory()); - TransactionController tx2 = new TransactionController(getFactory())) { - - tx1.begin(); - tx2.begin(); - - // both transactions touch the same entity - tx1.runStep(session -> { - // read by id - session.users().getUserById(session.realms().getRealm(realmId), userId).setFirstName("firstName"); - return null; - }); - tx2.runStep(session -> { - RealmModel realm = session.realms().getRealm(realmId); - - // remove by id after read by id - session.users().removeUser(realm, session.users().getUserById(realm, userId)); - return null; - }); - - // tx1 transaction should be successful - tx1.commit(); - - // tx2 should fail as tx1 already changed the value - assertException(tx2::commit, - anyOf( - allOf(instanceOf(RuntimeException.class), hasCause(instanceOf(RollbackException.class))), - allOf(instanceOf(ModelException.class), hasCause(instanceOf(OptimisticLockException.class))), - allOf(instanceOf(OptimisticLockException.class)) - )); - } - } } diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/user/UserModelTest.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/user/UserModelTest.java index 6f92548b7378..5928aca66109 100644 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/user/UserModelTest.java +++ b/testsuite/model/src/test/java/org/keycloak/testsuite/model/user/UserModelTest.java @@ -16,17 +16,16 @@ */ package org.keycloak.testsuite.model.user; +import org.hamcrest.Matchers; +import org.junit.Test; import org.keycloak.component.ComponentModel; import org.keycloak.models.Constants; import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.RealmModel; import org.keycloak.models.RealmProvider; import org.keycloak.models.UserModel; import org.keycloak.models.UserProvider; -import org.keycloak.models.map.realm.MapRealmProviderFactory; -import org.keycloak.models.map.user.MapUserProviderFactory; import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.UserStorageProviderFactory; import org.keycloak.storage.UserStorageProviderModel; @@ -34,29 +33,23 @@ import org.keycloak.storage.user.UserRegistrationProvider; import org.keycloak.testsuite.model.KeycloakModelTest; import org.keycloak.testsuite.model.RequireProvider; + import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; -import org.hamcrest.Matchers; -import org.junit.Test; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import static org.junit.Assume.assumeThat; /** @@ -73,8 +66,6 @@ public class UserModelTest extends KeycloakModelTest { private static final int DELETED_USER_COUNT = LAST_DELETED_USER_INDEX - FIRST_DELETED_USER_INDEX; private String realmId; - private String realm1Id; - private String realm2Id; private final List groupIds = new ArrayList<>(NUM_GROUPS); private String userFederationId; @@ -92,8 +83,6 @@ public void createEnvironment(KeycloakSession s) { @Override public void cleanEnvironment(KeycloakSession s) { s.realms().removeRealm(realmId); - if (realm1Id != null) s.realms().removeRealm(realm1Id); - if (realm2Id != null) s.realms().removeRealm(realm2Id); } @Override @@ -126,67 +115,6 @@ private Void addRemoveUser(KeycloakSession session, int i) { return null; } - @Test - @RequireProvider(value = UserProvider.class, only = {MapUserProviderFactory.PROVIDER_ID}) - @RequireProvider(value = RealmProvider.class, only = {MapRealmProviderFactory.PROVIDER_ID}) - public void testCaseSensitivityGetUserByUsername() { - - realm1Id = inComittedTransaction((Function) session -> { - RealmModel realm = session.realms().createRealm("realm1"); - realm.setDefaultRole(session.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName())); - realm.setAttribute(Constants.REALM_ATTR_USERNAME_CASE_SENSITIVE, true); - return realm.getId(); - }); - - withRealm(realm1Id, (session, realm) -> { - UserModel user1 = session.users().addUser(realm, "user"); - UserModel user2 = session.users().addUser(realm, "USER"); - - assertThat(user1, not(nullValue())); - assertThat(user2, not(nullValue())); - - assertThat(user1.getUsername(), equalTo("user")); - assertThat(user2.getUsername(), equalTo("USER")); - - return null; - }); - - // try to query storage in a separate transaction to make sure that storage can handle case-sensitive usernames - withRealm(realm1Id, (session, realm) -> { - UserModel user1 = session.users().getUserByUsername(realm, "user"); - UserModel user2 = session.users().getUserByUsername(realm, "USER"); - - assertThat(user1, not(nullValue())); - assertThat(user2, not(nullValue())); - - assertThat(user1.getUsername(), equalTo("user")); - assertThat(user2.getUsername(), equalTo("USER")); - - return null; - }); - - realm2Id = inComittedTransaction((Function) session -> { - RealmModel realm = session.realms().createRealm("realm2"); - realm.setDefaultRole(session.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName())); - realm.setAttribute(Constants.REALM_ATTR_USERNAME_CASE_SENSITIVE, false); - return realm.getId(); - }); - - withRealm(realm2Id, (session, realm) -> { - UserModel user1 = session.users().addUser(realm, "user"); - assertThat(user1, not(nullValue())); - - try { - session.users().addUser(realm, "USER"); - } catch (ModelDuplicateException e) { - return null; // expected - } - - fail("ModelDuplicateException expected"); - return null; - }); - } - @Test public void testAddRemoveUser() { inRolledBackTransaction(1, this::addRemoveUser); diff --git a/testsuite/model/src/test/java/org/keycloak/testsuite/model/util/KeycloakAssertions.java b/testsuite/model/src/test/java/org/keycloak/testsuite/model/util/KeycloakAssertions.java deleted file mode 100644 index 70bd85a7c351..000000000000 --- a/testsuite/model/src/test/java/org/keycloak/testsuite/model/util/KeycloakAssertions.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.model.util; - -import org.hamcrest.Matcher; - -import static org.hamcrest.CoreMatchers.allOf; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.MatcherAssert.assertThat; - -public class KeycloakAssertions { - - /** - * Runs {@code task} and checks whether the execution resulted in - * an exception that matches {@code matcher}. The method fails also - * when no exception is thrown. - * - * @param task task - * @param matcher matcher that the exception should match - */ - public static void assertException(Runnable task, Matcher matcher) { - Throwable ex = catchException(task); - assertThat(ex, allOf(notNullValue(), matcher)); - } - - /** - * Runs the {@code task} and returns any throwable that is thrown. - * If not exception is thrown, the method returns {@code null} - * - * @param task task - */ - public static Throwable catchException(Runnable task) { - try { - task.run(); - return null; - } catch (Throwable ex) { - return ex; - } - } -} diff --git a/testsuite/model/src/test/resources/log4j.properties b/testsuite/model/src/test/resources/log4j.properties index 9f922038ce7e..dbbf4539b433 100644 --- a/testsuite/model/src/test/resources/log4j.properties +++ b/testsuite/model/src/test/resources/log4j.properties @@ -42,9 +42,6 @@ log4j.logger.org.keycloak.connections.jpa.updater.liquibase=${keycloak.liquibase log4j.logger.org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory=debug # Enable to log short stack traces for log entries enabled with StackUtil.getShortStackTrace() calls -#log4j.logger.org.keycloak.models.map=trace -#log4j.logger.org.keycloak.models.map.transaction=debug -# #log4j.logger.org.keycloak.STACK_TRACE=trace #log4j.logger.org.keycloak.models.sessions.infinispan=trace diff --git a/themes/src/main/resources-community/theme/base/account/messages/messages_th.properties b/themes/src/main/resources-community/theme/base/account/messages/messages_th.properties index d5c5ce3a4ddb..7e11fc77ce9c 100644 --- a/themes/src/main/resources-community/theme/base/account/messages/messages_th.properties +++ b/themes/src/main/resources-community/theme/base/account/messages/messages_th.properties @@ -1,4 +1,3 @@ -# encoding: utf-8 doSave=บันทึก doCancel=ยกเลิก doLogOutAllSessions=ออกจากระบบทุกเซสชัน diff --git a/themes/src/main/resources-community/theme/base/email/messages/messages_th.properties b/themes/src/main/resources-community/theme/base/email/messages/messages_th.properties index c276362fed6e..dc3d181f3dbb 100644 --- a/themes/src/main/resources-community/theme/base/email/messages/messages_th.properties +++ b/themes/src/main/resources-community/theme/base/email/messages/messages_th.properties @@ -1,4 +1,3 @@ -# encoding: UTF-8 emailVerificationSubject=ตรวจสอบอีเมล emailVerificationBody=มีคนสร้างบัญชี {2} ด้วยที่อยู่อีเมลนี้ หากคุณเป็นผู้สร้างบัญชี คลิกที่ลิงก์ด้านล่างเพื่อยืนยันที่อยู่อีเมลของคุณ\n\n{0}\n\nลิงก์นี้จะหมดอายุภายใน {3}.\n\nถ้าคุณไม่ได้สร้างบัญชี ไม่ต้องสนใจข้อความนี้ emailVerificationBodyHtml=

มีคนสร้างบัญชี {2} ด้วยที่อยู่อีเมลนี้ หากคุณเป็นผู้สร้างบัญชี คลิกที่ลิงก์ด้านล่างเพื่อยืนยันที่อยู่อีเมลของคุณ

ลิงก์สำหรับการตรวจสอบที่อยู่อีเมล

ลิงก์นี้จะหมดอายุภายใน {3}.

ถ้าคุณไม่ได้สร้างบัญชี ไม่ต้องสนใจข้อความนี้

diff --git a/themes/src/main/resources-community/theme/base/login/messages/messages_th.properties b/themes/src/main/resources-community/theme/base/login/messages/messages_th.properties index 6b5d160558b3..77d23dd2d4c9 100644 --- a/themes/src/main/resources-community/theme/base/login/messages/messages_th.properties +++ b/themes/src/main/resources-community/theme/base/login/messages/messages_th.properties @@ -1,4 +1,3 @@ -# encoding: UTF-8 doLogIn=เข้าสู่ระบบ doRegister=ลงทะเบียน doCancel=ยกเลิก diff --git a/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_ca.properties b/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_ca.properties index 6b207ef999f1..5b57fc1fccc0 100644 --- a/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_ca.properties +++ b/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_ca.properties @@ -1,5 +1,3 @@ -# Put new messages for Account Console Here -# Feel free to use any existing messages from the base theme pageNotFound=No s''ha trobat la pàgina forbidden=Prohibit needAccessRights=No teniu permisos d''accés a aquesta petició. Contacteu amb l''administrador. diff --git a/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_de.properties b/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_de.properties index 3bc37b6c6a5a..ac97ab110235 100644 --- a/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_de.properties +++ b/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_de.properties @@ -1,5 +1,3 @@ -# Put new messages for Account Console Here -# Feel free to use any existing messages from the base theme pageNotFound=Seite nicht gefunden forbidden=Verboten needAccessRights=Sie haben keine Zugriffsrechte auf diese Anfrage. Wenden Sie sich an Ihren Administrator. diff --git a/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_el.properties b/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_el.properties index bb1b2804e289..11eb3af23aa0 100644 --- a/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_el.properties +++ b/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_el.properties @@ -167,6 +167,4 @@ actionRequiresIDP=Αυτή η ενέργεια απαιτεί ανακατεύθ invalidRoute=Το {0} δεν είναι μια έγκυρη διαδρομή. needAccessRights=Δεν έχετε άδειες πρόσβασης για αυτή την αίτηση. Επικοινωνήστε με το διαχειριστή σας. forbidden=Απαγορεύεται -# Put new messages for Account Console Here -# Feel free to use any existing messages from the base theme pageNotFound=Η σελίδα δε βρέθηκε diff --git a/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_fa.properties b/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_fa.properties index 5b181d854754..8438a7706cf4 100644 --- a/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_fa.properties +++ b/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_fa.properties @@ -1,6 +1,3 @@ -# Put new messages for Account Console Here -# Feel free to use any existing messages from the base theme - pageNotFound=صفحه یافت نشد forbidden=غیرمجاز needAccessRights=شما مجوز دسترسی به این درخواست را ندارید. لطفا با مدیریت خود صحبت کنید. diff --git a/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_hu.properties b/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_hu.properties index 8b67ce7a6775..c117761b5a90 100644 --- a/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_hu.properties +++ b/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_hu.properties @@ -1,5 +1,3 @@ -# Put new messages for Account Console Here -# Feel free to use any existing messages from the base theme pageNotFound=Az oldal nem található forbidden=Hozzáférés megtagadva needAccessRights=Nincs jogosultsága a művelet elvégzéséhez. Kérem, vegye fel a kapcsolatot az alkalmazás adminisztrátorával. diff --git a/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_pt_BR.properties b/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_pt_BR.properties index 096681d0684c..dd34bac34753 100644 --- a/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_pt_BR.properties +++ b/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_pt_BR.properties @@ -1,5 +1,3 @@ -# Put new messages for Account Console Here -# Feel free to use any existing messages from the base theme pageNotFound=Página Não Encontrada forbidden=Proibido needAccessRights=Você não tem as permissões de acesso para esta solicitação. Entre em contato com um administrador. diff --git a/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_th.properties b/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_th.properties index c4cfac3d25e6..7b437b5042d8 100644 --- a/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_th.properties +++ b/themes/src/main/resources-community/theme/keycloak.v2/account/messages/messages_th.properties @@ -1,6 +1,3 @@ -# encoding: UTF-8 -# Put new messages for Account Console Here -# Feel free to use any existing messages from the base theme pageNotFound=ไม่พบหน้า forbidden=ไม่มีสิทธิ์ needAccessRights=คุณไม่มีสิทธิ์เข้าถึงคำขอนี้ ติดต่อผู้ดูแลระบบของคุณ diff --git a/themes/src/main/resources/theme/base/login/login-update-password.ftl b/themes/src/main/resources/theme/base/login/login-update-password.ftl index 4eb2d6a5b94d..f1ac3b99cbd7 100755 --- a/themes/src/main/resources/theme/base/login/login-update-password.ftl +++ b/themes/src/main/resources/theme/base/login/login-update-password.ftl @@ -5,17 +5,6 @@ ${msg("updatePasswordTitle")} <#elseif section = "form">
- -
- - -
-
diff --git a/themes/src/main/resources/theme/keycloak.v2/account/messages/messages_en.properties b/themes/src/main/resources/theme/keycloak.v2/account/messages/messages_en.properties index 45f8bdad68e7..fcc289d8ff74 100644 --- a/themes/src/main/resources/theme/keycloak.v2/account/messages/messages_en.properties +++ b/themes/src/main/resources/theme/keycloak.v2/account/messages/messages_en.properties @@ -1,5 +1,3 @@ -# Put new messages for Account Console Here -# Feel free to use any existing messages from the base theme pageNotFound=Page not found forbidden=Forbidden needAccessRights=You do not have access rights to this request. Contact your administrator.