mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-10 15:32:05 -03:30
fix: adding a -- separator for spi options (#40005)
* fix: adding a -- separator for spi options closes: #39063 Signed-off-by: Steve Hawkins <shawkins@redhat.com> * adding a warning for ambiguous spi options also adding a note about the change Signed-off-by: Steve Hawkins <shawkins@redhat.com> # Conflicts: # docs/documentation/upgrading/topics/changes/changes-26_3_0.adoc * updating docs to the new format Signed-off-by: Steve Hawkins <shawkins@redhat.com> # Conflicts: # docs/guides/high-availability/examples/generated/keycloak-ispn.yaml # docs/guides/high-availability/examples/generated/keycloak.yaml * internally using the new spi options also adding a deprecation notice Signed-off-by: Steve Hawkins <shawkins@redhat.com> * Apply suggestions from code review Co-authored-by: Martin Bartoš <mabartos@redhat.com> Signed-off-by: Steven Hawkins <shawkins@redhat.com> * correcting options output adding + + inlining where needed Signed-off-by: Steve Hawkins <shawkins@redhat.com> * adding test showing the env mapping with __ Signed-off-by: Steve Hawkins <shawkins@redhat.com> --------- Signed-off-by: Steve Hawkins <shawkins@redhat.com> Signed-off-by: Steven Hawkins <shawkins@redhat.com> Co-authored-by: Martin Bartoš <mabartos@redhat.com>
This commit is contained in:
parent
2c65234c8f
commit
76bc9fadcb
@ -235,6 +235,13 @@ public class Config {
|
||||
|
||||
Scope scope(String... scope);
|
||||
|
||||
/**
|
||||
* @deprecated since 26.3.0, to be removed
|
||||
*
|
||||
* <br>Was introduced for testing purposes and was not fully / correctly implements
|
||||
* across Scope implementations
|
||||
*/
|
||||
@Deprecated
|
||||
Set<String> getPropertyNames();
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ Supported password hashing algorithms are shown in the following table.
|
||||
It is highly recommended to use Argon2 when possible as it has significantly less CPU requirements compared to PBKDF2, while
|
||||
at the same time being more secure.
|
||||
|
||||
The default password hashing algorithm for the server can be configured with `--spi-password-hashing-provider-default=<algorithm>`.
|
||||
The default password hashing algorithm for the server can be configured with `--spi-password-hashing--provider-default=<algorithm>`.
|
||||
|
||||
To prevent excessive memory and CPU usage, the parallel computation of hashes by Argon2 is by default limited to the number of cores available to the JVM.
|
||||
To configure the Argon2 hashing provider, use its provider options.
|
||||
@ -141,14 +141,14 @@ Password must not be in a blacklist file.
|
||||
* The value of the blacklist file must be the name of the blacklist file, for example, `100k_passwords.txt`.
|
||||
* Blacklist files resolve against `+${kc.home.dir}/data/password-blacklists/+` by default. Customize this path using:
|
||||
** The `keycloak.password.blacklists.path` system property.
|
||||
** The `blacklistsPath` property of the `passwordBlacklist` policy SPI configuration. To configure the blacklist folder using the CLI, use `--spi-password-policy-password-blacklist-blacklists-path=/path/to/blacklistsFolder`.
|
||||
** The `blacklistsPath` property of the `passwordBlacklist` policy SPI configuration. To configure the blacklist folder using the CLI, use `--spi-password-policy--password-blacklist--blacklists-path=/path/to/blacklistsFolder`.
|
||||
|
||||
.A note about False Positives
|
||||
|
||||
The current implementation uses a BloomFilter for fast and memory efficient containment checks, such as whether a given password is contained in a blacklist, with the possibility for false positives.
|
||||
|
||||
* By default a false positive probability of `0.01%` is used.
|
||||
* To change the false positive probability by CLI configuration, use `--spi-password-policy-password-blacklist-false-positive-probability=0.00001`.
|
||||
* To change the false positive probability by CLI configuration, use `--spi-password-policy--password-blacklist--false-positive-probability=0.00001`.
|
||||
|
||||
[[maximum-authentication-age]]
|
||||
===== Maximum Authentication Age
|
||||
|
||||
@ -35,9 +35,9 @@ You can now view admin events.
|
||||
.Admin events
|
||||
image:images/admin-events.png[Admin events]
|
||||
|
||||
When the `Include Representation` switch is ON, it can lead to storing a lot of information in the database. You can set a maximum length of the representation 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:
|
||||
When the `Include Representation` switch is ON, it can lead to storing a lot of information in the database. You can set a maximum length of the representation 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
|
||||
kc.[sh|bat] --spi-events-store--jpa--max-field-length=2500
|
||||
----
|
||||
@ -184,7 +184,7 @@ To change the log level used by the Logging Event listener, add the following:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
bin/kc.[sh|bat] start --spi-events-listener-jboss-logging-success-level=info --spi-events-listener-jboss-logging-error-level=error
|
||||
bin/kc.[sh|bat] start --spi-events-listener-jboss-logging-success-level=info --spi-events-listener--jboss-logging--error-level=error
|
||||
----
|
||||
|
||||
The valid values for log levels are `debug`, `info`, `warn`, `error`, and `fatal`.
|
||||
@ -226,15 +226,15 @@ To enable the Email Listener:
|
||||
.Event listeners
|
||||
image:images/event-listeners.png[Event listeners]
|
||||
|
||||
You can exclude events by using the `--spi-events-listener-email-exclude-events` argument. For example:
|
||||
You can exclude events by using the `--spi-events-listener--email--exclude-events` argument. For example:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
kc.[sh|bat] --spi-events-listener-email-exclude-events=UPDATE_CREDENTIAL,REMOVE_CREDENTIAL
|
||||
kc.[sh|bat] --spi-events-listener--email--exclude-events=UPDATE_CREDENTIAL,REMOVE_CREDENTIAL
|
||||
----
|
||||
|
||||
To enable optional events, use the following command:
|
||||
[source,bash]
|
||||
----
|
||||
kc.[sh|bat] --spi-events-listener-email-include-events=USER_DISABLED_BY_TEMPORARY_LOCKOUT_ERROR,USER_DISABLED_BY_PERMANENT_LOCKOUT
|
||||
kc.[sh|bat] --spi-events-listener--email--include-events=USER_DISABLED_BY_TEMPORARY_LOCKOUT_ERROR,USER_DISABLED_BY_PERMANENT_LOCKOUT
|
||||
----
|
||||
|
||||
@ -14,11 +14,11 @@ If you have a parent group and a child group, and a user that belongs only to th
|
||||
|
||||
The hierarchy of a group is sometimes represented using the group path. The path is the complete list of names that represents the hierarchy of a specific group, from top to bottom and separated by slashes `/` (similar to files in a File System). For example a path can be `/top/level1/level2` which means that `top` is a top level group and is parent of `level1`, which in turn is parent of `level2`. This path represents unambiguously the hierarchy for the group `level2`.
|
||||
|
||||
Because of historical reasons {project_name}, does not escape slashes in the group name itself. Therefore a group named `level1/group` under `top` uses the path `/top/level1/group`, which is misleading. {project_name} can be started with the option `--spi-group-jpa-escape-slashes-in-group-path` to `true` and then the slashes in the name are escaped with the character `~`. The escape char marks that the slash is part of the name and has no hierarchical meaning. The previous path example would be `/top/level1~/group` when escaped.
|
||||
Because of historical reasons {project_name}, does not escape slashes in the group name itself. Therefore a group named `level1/group` under `top` uses the path `/top/level1/group`, which is misleading. {project_name} can be started with the option `--spi-group--jpa--escape-slashes-in-group-path` to `true` and then the slashes in the name are escaped with the character `~`. The escape char marks that the slash is part of the name and has no hierarchical meaning. The previous path example would be `/top/level1~/group` when escaped.
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
bin/kc.[sh|bat] start --spi-group-jpa-escape-slashes-in-group-path=true
|
||||
bin/kc.[sh|bat] start --spi-group--jpa--escape-slashes-in-group-path=true
|
||||
----
|
||||
|
||||
The following example includes a top-level *Sales* group and a child *North America* subgroup.
|
||||
|
||||
@ -32,12 +32,12 @@ To specify the lifespan override for offline user sessions, start {project_name}
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
--spi-user-sessions-infinispan-offline-session-cache-entry-lifespan-override=<lifespan-in-seconds>
|
||||
--spi-user-sessions--infinispan--offline-session-cache-entry-lifespan-override=<lifespan-in-seconds>
|
||||
----
|
||||
|
||||
Similarly for offline client sessions:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
--spi-user-sessions-infinispan-offline-client-session-cache-entry-lifespan-override=<lifespan-in-seconds>
|
||||
--spi-user-sessions--infinispan--offline-client-session-cache-entry-lifespan-override=<lifespan-in-seconds>
|
||||
----
|
||||
|
||||
@ -154,7 +154,7 @@ The CIBA grant uses the following two providers.
|
||||
|
||||
[source,bash,subs="attributes+"]
|
||||
----
|
||||
kc.[sh|bat] start --spi-ciba-auth-channel-ciba-http-auth-channel-http-authentication-channel-uri=https://backend.internal.example.com{kc_base_path}
|
||||
kc.[sh|bat] start --spi-ciba-auth-channel--ciba-http-auth-channel--http-authentication-channel-uri=https://backend.internal.example.com{kc_base_path}
|
||||
----
|
||||
|
||||
The configurable items and their description follow.
|
||||
|
||||
@ -157,7 +157,7 @@ The CIBA grant uses the following two providers.
|
||||
|
||||
[source,bash,subs="attributes+"]
|
||||
----
|
||||
kc.[sh|bat] start --spi-ciba-auth-channel-ciba-http-auth-channel-http-authentication-channel-uri=https://backend.internal.example.com{kc_base_path}
|
||||
kc.[sh|bat] start --spi-ciba-auth-channel--ciba-http-auth-channel--http-authentication-channel-uri=https://backend.internal.example.com{kc_base_path}
|
||||
----
|
||||
|
||||
The configurable items and their description follow.
|
||||
|
||||
@ -25,7 +25,7 @@ The following example shows how to limit the number of active `AuthenticationSes
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
bin/kc.[sh|bat] start --spi-authentication-sessions-infinispan-auth-sessions-limit=100
|
||||
bin/kc.[sh|bat] start --spi-authentication-sessions--infinispan--auth-sessions-limit=100
|
||||
----
|
||||
|
||||
ifeval::[{project_community}==true]
|
||||
@ -33,6 +33,6 @@ The equivalent command for the new map storage:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
bin/kc.[sh|bat] start --spi-authentication-sessions-map-auth-sessions-limit=100
|
||||
bin/kc.[sh|bat] start --spi-authentication-sessions--map--auth-sessions-limit=100
|
||||
----
|
||||
endif::[]
|
||||
|
||||
@ -29,11 +29,11 @@ This is the list of the read-only attributes, which are used internally by the {
|
||||
|
||||
System administrators have a way to add additional attributes to this list. The configuration is currently available at the server level.
|
||||
|
||||
You can add this configuration by using the `spi-user-profile-declarative-user-profile-read-only-attributes` and `spi-user-profile-declarative-user-profile-admin-read-only-attributes` options. For example:
|
||||
You can add this configuration by using the `spi-user-profile--declarative-user-profile--read-only-attributes` and `spi-user-profile--declarative-user-profile--admin-read-only-attributes` options. For example:
|
||||
|
||||
[source,bash,options="nowrap"]
|
||||
----
|
||||
kc.[sh|bat] start --spi-user-profile-declarative-user-profile-read-only-attributes=foo,bar*
|
||||
kc.[sh|bat] start --spi-user-profile--declarative-user-profile--read-only-attributes=foo,bar*
|
||||
----
|
||||
|
||||
For this example, users and administrators would not be able to update attribute `foo`. Users would not be able to edit any attributes starting with the `bar`.
|
||||
|
||||
@ -33,7 +33,7 @@ All built-in providers support the configuration of key resolvers. A key resolve
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
kc.[sh|bat] start --spi-vault-file-key-resolvers=REALM_UNDERSCORE_KEY,KEY_ONLY
|
||||
kc.[sh|bat] start --spi-vault--file--key-resolvers=REALM_UNDERSCORE_KEY,KEY_ONLY
|
||||
----
|
||||
|
||||
The resolvers run in the same order you declare them in the configuration. For each resolver, {project_name} uses the last entry name the resolver produces, which combines the realm with the vault key to search for the vault's secret. If {project_name} finds a secret, it returns the secret. If not, {project_name} uses the next resolver. This search continues until {project_name} finds a non-empty secret or runs out of resolvers. If {project_name} finds no secret, {project_name} returns an empty secret.
|
||||
|
||||
@ -91,7 +91,7 @@ For example, to configure a provider you can set options as follows:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
bin/kc.[sh|bat] --spi-theme-selector-my-theme-selector-enabled=true --spi-theme-selector-my-theme-selector-theme=my-theme
|
||||
bin/kc.[sh|bat] --spi-theme-selector--my-theme-selector--enabled=true --spi-theme-selector--my-theme-selector--theme=my-theme
|
||||
----
|
||||
|
||||
Then you can retrieve the config in the `ProviderFactory` init method:
|
||||
@ -234,7 +234,7 @@ one of them needs to be specified as the default one.
|
||||
For example such as:
|
||||
[source,bash]
|
||||
----
|
||||
bin/kc.[sh|bat] build --spi-hostname-provider=default
|
||||
bin/kc.[sh|bat] build --spi-hostname--provider=default
|
||||
----
|
||||
|
||||
The value `default` used as the value of `default-provider` must match the ID returned by the `ProviderFactory.getId()` of the particular provider factory implementation.
|
||||
@ -293,7 +293,7 @@ For example to disable the Infinispan user cache provider use:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
bin/kc.[sh|bat] build --spi-user-cache-infinispan-enabled=false
|
||||
bin/kc.[sh|bat] build --spi-user-cache--infinispan--enabled=false
|
||||
----
|
||||
|
||||
[[_script_providers]]
|
||||
|
||||
@ -32,13 +32,13 @@ NOTE: To set the theme for the `master` Admin Console you need to set the Admin
|
||||
+
|
||||
. To see the changes to the Admin Console refresh the page.
|
||||
|
||||
. Change the welcome theme by using the `spi-theme-welcome-theme` option.
|
||||
. Change the welcome theme by using the `spi-theme--welcome-theme` option.
|
||||
|
||||
. For example:
|
||||
+
|
||||
[source,bash]
|
||||
----
|
||||
bin/kc.[sh|bat] start --spi-theme-welcome-theme=custom-theme
|
||||
bin/kc.[sh|bat] start --spi-theme--welcome-theme=custom-theme
|
||||
----
|
||||
|
||||
[[_default-themes]]
|
||||
@ -74,7 +74,7 @@ restarting {project_name}.
|
||||
+
|
||||
[source,bash]
|
||||
----
|
||||
bin/kc.[sh|bat] start --spi-theme-static-max-age=-1 --spi-theme-cache-themes=false --spi-theme-cache-templates=false
|
||||
bin/kc.[sh|bat] start --spi-theme--static-max-age=-1 --spi-theme--cache-themes=false --spi-theme-cache--templates=false
|
||||
----
|
||||
|
||||
. Create a directory in the `themes` directory.
|
||||
|
||||
@ -213,7 +213,7 @@ For example, by running the server with the following argument:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
kc.[sh|bat] start --spi-storage-readonly-property-file-path=/other-users.properties
|
||||
kc.[sh|bat] start --spi-storage--readonly-property-file--path=/other-users.properties
|
||||
----
|
||||
|
||||
We can specify the classpath of the user property file instead of hardcoding it. Then you can retrieve the configuration in the `PropertyFileUserStorageProviderFactory.init()`:
|
||||
|
||||
@ -3,6 +3,18 @@
|
||||
Breaking changes are identified as requiring changes from existing users to their configurations.
|
||||
In minor or patch releases we will only do breaking changes to fix bugs.
|
||||
|
||||
=== Buildtime SPI Options
|
||||
|
||||
SPI options ending in `-enabled`, `-provider-default`, or `-provider` were previously treated as buildtime. However in some instances this was not correct as a provider could have a configuration property ending in one of those suffixes as well.
|
||||
|
||||
To resolve this ambiguity, and any potential ambiguity involving SPI and provider names, a new SPI option format was introduced where the scopes and suffix are separated by `--`(double dash) instead of `-`(dash).
|
||||
|
||||
An SPI property ending in `-enabled`, `-provider-default`, or `-provider` will only be treated as buildtime if it is using the new format, e.g. `spi-<spi-name>--<provider-name>--enabled` will be recognized as a buildtime option.
|
||||
|
||||
For instance, the correct way to reference your custom email template is: `--spi-email-template--mycustomprovider--enabled` (not `--spi-email-template-mycustomprovider-enabled`).
|
||||
|
||||
Options using the legacy format and ending in `-enabled`, `-provider-default`, or `-provider` will instead be treated as runtime and a warning will be emitted.
|
||||
|
||||
=== Reading information about temporarily locked users
|
||||
|
||||
In previous releases there was an inconsistency in the REST endpoint result of getting a user (`+GET /admin/realms/{realm}/users/{user-id}+`) and searching for a user (`+GET /admin/realms/{realm}/users+`). When BruteForce is enabled and a user was temporarily locked out the former endpoint would return `enabled=false` while the latter would return `enabled=true`. If the user was updated and enabled was false due to temporary lockout then the user would be disabled permanently. Both endpoints now return `enabled=true` when a user is temporarily locked out. To check whether a user is temporarily locked out the BruteForceUserResource endpoint should be utilised (`+GET /admin/realms/{realm}/attack-detection/brute-force/users/{userId}+`).
|
||||
@ -137,6 +149,10 @@ In relation to supported Recovery codes, we deprecated the password policy `Reco
|
||||
This password policy was not related to passwords at all, but was related to recovery codes, and hence using password policy is not appropriate way for the configuration of the threshold. It is
|
||||
recommended to use the configuration option *Warning Threshold* of the *Recovery Authentication Codes* required action instead of using password policy. For more details, see the link:{adminguide_link}#_recovery-codes[Recovery codes documentation].
|
||||
|
||||
=== Scope.getPropertyNames deprecated for removal
|
||||
|
||||
The `org.keycloak.Config.Scope.getPropertyNames` method has been deprecated for removal.
|
||||
|
||||
== Removed features
|
||||
|
||||
The following features have been removed from this release.
|
||||
|
||||
@ -25,7 +25,7 @@ To change the threshold, set the `index-creation-threshold` property, value for
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
kc.[sh|bat] start --spi-connections-liquibase-quarkus-index-creation-threshold=300000
|
||||
kc.[sh|bat] start --spi-connections-liquibase--quarkus--index-creation-threshold=300000
|
||||
----
|
||||
|
||||
=== Manual relational database migration
|
||||
@ -35,7 +35,7 @@ default `connections-jpa` provider:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
kc.[sh|bat] start --spi-connections-jpa-quarkus-migration-strategy=manual
|
||||
kc.[sh|bat] start --spi-connections-jpa--quarkus--migration-strategy=manual
|
||||
----
|
||||
|
||||
When you start the server with this configuration, the server checks if the database needs to be migrated. If migration is needed, the required changes are written to the `bin/keycloak-database-update.sql` SQL file. You can review and manually run these commands against the database.
|
||||
@ -45,7 +45,7 @@ default `connections-jpa` provider:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
kc.[sh|bat] start --spi-connections-jpa-quarkus-migration-export=<path>/<file.sql>
|
||||
kc.[sh|bat] start --spi-connections-jpa--quarkus--migration-export=<path>/<file.sql>
|
||||
----
|
||||
|
||||
For further details on how to apply this file to the database, see the documentation for your relational database.
|
||||
|
||||
@ -73,7 +73,7 @@ Possible values:
|
||||
--
|
||||
====
|
||||
|
||||
To configure what tags are available provide a comma-separated list of tag names to the following option `spi-credential-keycloak-password-validations-counter-tags`.
|
||||
To configure what tags are available provide a comma-separated list of tag names to the following option `spi-credential--keycloak-password--validations-counter-tags`.
|
||||
By default, all tags are enabled.
|
||||
|
||||
The snippet below is an example of a response provided by the metric endpoint:
|
||||
|
||||
@ -91,11 +91,11 @@ metadata:
|
||||
spec:
|
||||
...
|
||||
additionalOptions:
|
||||
- name: spi-connections-http-client-default-connection-pool-size
|
||||
- name: spi-connections-http-client--default--connection-pool-size
|
||||
secret: # Secret reference
|
||||
name: http-client-secret # name of the Secret
|
||||
key: poolSize # name of the Key in the Secret
|
||||
- name: spi-email-template-mycustomprovider-enabled
|
||||
- name: spi-email-template--mycustomprovider--enabled
|
||||
value: true # plain text value
|
||||
----
|
||||
NOTE: The name format of options defined in this way is identical to the key format of options specified in the configuration file.
|
||||
|
||||
@ -19,6 +19,12 @@ any provider, including those you have implemented to extend the server capabili
|
||||
Providers can be configured by using a specific configuration format. The format consists of:
|
||||
|
||||
[source]
|
||||
----
|
||||
spi-<spi-id>--<provider-id>--<property>=<value>
|
||||
----
|
||||
|
||||
Or if there is no possibility of ambiguity between multiple providers:
|
||||
|
||||
----
|
||||
spi-<spi-id>-<provider-id>-<property>=<value>
|
||||
----
|
||||
@ -27,7 +33,9 @@ The `<spi-id>` is the name of the SPI you want to configure.
|
||||
|
||||
The `<provider-id>` is the id of the provider you want to configure. This is the id set to the corresponding provider factory implementation.
|
||||
|
||||
The `<property>` is the actual name of the property you want to set for a given provider.
|
||||
The `<property>` is the actual name of the property you want to set for a given provider
|
||||
|
||||
NOTE: the property name `enabled` is effectively reserved for enabling / disabling a provider
|
||||
|
||||
All those names (for spi, provider, and property) should be in lower case and if the name is in camel-case such as `myKeycloakProvider`, it should include dashes (`-`) before upper-case letters as follows: `my-keycloak-provider`.
|
||||
|
||||
@ -35,31 +43,47 @@ Taking the `HttpClientSpi` SPI as an example, the name of the SPI is `connection
|
||||
|
||||
[source]
|
||||
----
|
||||
spi-connections-http-client-default-connection-pool-size=10
|
||||
spi-connections-http-client--default--connection-pool-size=10
|
||||
----
|
||||
|
||||
== Setting a provider configuration option
|
||||
=== Setting a provider configuration option
|
||||
|
||||
Provider configuration options are provided when starting the server. See all support configuration sources and formats for options in <@links.server id="configuration"/>. For example via a command line option:
|
||||
|
||||
.Setting the `connection-pool-size` for the `default` provider of the `connections-http-client` SPI
|
||||
<@kc.start parameters="--spi-connections-http-client-default-connection-pool-size=10"/>
|
||||
<@kc.start parameters="--spi-connections-http-client--default--connection-pool-size=10"/>
|
||||
|
||||
== Configuring a single provider for an SPI
|
||||
== Build time options
|
||||
|
||||
=== Configuring a single provider for an SPI
|
||||
|
||||
Depending on the SPI, multiple provider implementations can co-exist but only one of them is going to be used at runtime.
|
||||
For these SPIs, a specific provider is the primary implementation that is going to be active and used at runtime.
|
||||
For these SPIs, a specific provider is the primary implementation that is going to be active and used at runtime. The format consists of:
|
||||
|
||||
[source]
|
||||
----
|
||||
spi-<spi-id>--provider=<provider-id>
|
||||
----
|
||||
|
||||
NOTE: `spi-<spi-id>-provider=<provider-id>` may still be used, but the server will not properly detect when reaugmentation is needed.
|
||||
|
||||
To configure a provider as the single provider you should run the `build` command as follows:
|
||||
|
||||
.Marking the `mycustomprovider` provider as the single provider for the `email-template` SPI
|
||||
<@kc.build parameters="--spi-email-template-provider=mycustomprovider"/>
|
||||
<@kc.build parameters="--spi-email-template--provider=mycustomprovider"/>
|
||||
|
||||
== Configuring a default provider for an SPI
|
||||
=== Configuring a default provider for an SPI
|
||||
|
||||
Depending on the SPI, multiple provider implementations can co-exist and one is used by default.
|
||||
For these SPIs, a specific provider is the default implementation that is going to selected unless a specific provider
|
||||
is requested.
|
||||
is requested. The format consists of:
|
||||
|
||||
[source]
|
||||
----
|
||||
spi-<spi-id>--provider-default=<provider-id>
|
||||
----
|
||||
|
||||
NOTE: `spi-<spi-id>-provider-default=<provider-id>` may still be used, but the server will not properly detect when reaugmentation is needed.
|
||||
|
||||
The following logic is used to determine the default provider:
|
||||
|
||||
@ -70,15 +94,23 @@ The following logic is used to determine the default provider:
|
||||
To configure a provider as the default provider you should run the `build` command as follows:
|
||||
|
||||
.Marking the `mycustomhash` provider as the default provider for the `password-hashing` SPI
|
||||
<@kc.build parameters="--spi-password-hashing-provider-default=mycustomprovider"/>
|
||||
<@kc.build parameters="--spi-password-hashing--provider-default=mycustomprovider"/>
|
||||
|
||||
=== Enabling and disabling a provider
|
||||
|
||||
== Enabling and disabling a provider
|
||||
The format consists of:
|
||||
|
||||
[source]
|
||||
----
|
||||
spi-<spi-id>--<provider-id>--enabled=<boolean>
|
||||
----
|
||||
|
||||
NOTE: `spi-<spi-id>-<provider-id>-enabled=<boolean>` may still be used, but the server will not properly detect when reaugmentation is needed.
|
||||
|
||||
To enable or disable a provider you should run the `build` command as follows:
|
||||
|
||||
.Enabling a provider
|
||||
<@kc.build parameters="--spi-email-template-mycustomprovider-enabled=true"/>
|
||||
<@kc.build parameters="--spi-email-template--mycustomprovider--enabled=true"/>
|
||||
|
||||
To disable a provider, use the same command and set the `enabled` property to `false`.
|
||||
|
||||
|
||||
@ -293,12 +293,12 @@ You can achieve most optimizations to startup and runtime behavior by using the
|
||||
Some of the realm capabilities allow administrators to reference system variables such as environment variables and system properties when configuring
|
||||
the realm and its components.
|
||||
|
||||
By default, {project_name} disallow using system variables but only those explicitly specified through the `spi-admin-allowed-system-variables` configuration
|
||||
By default, {project_name} disallow using system variables but only those explicitly specified through the `+spi-admin--allowed-system-variables+` configuration
|
||||
option. This option allows you to specify a comma-separated list of keys that will eventually resolve to values from system variables with the same key.
|
||||
|
||||
. Start the server and expose a set of system variables to the server runtime
|
||||
+
|
||||
<@kc.start parameters="--spi-admin-allowed-system-variables=FOO,BAR"/>
|
||||
<@kc.start parameters="--spi-admin--allowed-system-variables=FOO,BAR"/>
|
||||
|
||||
In future releases, this capability will be removed in favor of preventing any usage of system variables in the realm configuration.
|
||||
|
||||
|
||||
@ -308,7 +308,7 @@ Because cluster nodes can boot concurrently, they take extra time for database a
|
||||
|
||||
The maximum timeout for this lock is 900 seconds. If a node waits on this lock for more than the timeout, the boot fails. The need to change the default value is unlikely, but you can change it by entering this command:
|
||||
|
||||
<@kc.start parameters="--spi-dblock-jpa-lock-wait-timeout 900"/>
|
||||
<@kc.start parameters="--spi-dblock--jpa--lock-wait-timeout 900"/>
|
||||
|
||||
== Using Database Vendors with XA transaction support
|
||||
{project_name} uses non-XA transactions and the appropriate database drivers by default.
|
||||
@ -330,17 +330,17 @@ NOTE: Enabling XA transactions in a containerized environment does not fully sup
|
||||
To setup the JPA migrationStrategy (manual/update/validate) you should setup JPA provider as follows:
|
||||
|
||||
.Setting the `migration-strategy` for the `quarkus` provider of the `connections-jpa` SPI
|
||||
<@kc.start parameters="--spi-connections-jpa-quarkus-migration-strategy=manual"/>
|
||||
<@kc.start parameters="--spi-connections--jpa--quarkus-migration-strategy=manual"/>
|
||||
|
||||
If you want to get a SQL file for DB initialization, too, you have to add this additional SPI initializeEmpty (true/false):
|
||||
|
||||
.Setting the `initialize-empty` for the `quarkus` provider of the `connections-jpa` SPI
|
||||
<@kc.start parameters="--spi-connections-jpa-quarkus-initialize-empty=false"/>
|
||||
<@kc.start parameters="--spi-connections--jpa--quarkus-initialize-empty=false"/>
|
||||
|
||||
In the same way the migrationExport to point to a specific file and location:
|
||||
|
||||
.Setting the `migration-export` for the `quarkus` provider of the `connections-jpa` SPI
|
||||
<@kc.start parameters="--spi-connections-jpa-quarkus-migration-export=<path>/<file.sql>"/>
|
||||
<@kc.start parameters="--spi-connections--jpa--quarkus-migration-export=<path>/<file.sql>"/>
|
||||
|
||||
For more information, check the link:{upgrading_guide_link}#_migrate_db[Migrating the database] documentation.
|
||||
|
||||
|
||||
@ -138,14 +138,14 @@ So effectively, you can use an option such as the following when starting the se
|
||||
|
||||
[source]
|
||||
----
|
||||
--spi-password-hashing-pbkdf2-sha512-max-padding-length=14
|
||||
--spi-password-hashing--pbkdf2-sha512--max-padding-length=14
|
||||
----
|
||||
|
||||
NOTE: Using the option above does not break FIPS compliance. However, note that longer passwords are good practice anyway. For example, passwords auto-generated by modern browsers match this
|
||||
requirement as they are longer than 14 characters. If you want to omit the option for max-padding-length, you can set the password policy to your realms to have passwords at least 14 characters long.
|
||||
|
||||
NOTE: When you are migrating from {project_name} older than 24, or if you explicitly set the password policy to override the default hashing algorithm, it is possible that some of your users use an older
|
||||
algorithm like `pbkdf2-sha256`. In this case, consider adding the `--spi-password-hashing-pbkdf2-sha256-max-padding-length=14` option to ensure that users having their passwords hashed with
|
||||
algorithm like `pbkdf2-sha256`. In this case, consider adding the `+--spi-password-hashing--pbkdf2-sha256--max-padding-length=14+` option to ensure that users having their passwords hashed with
|
||||
the older `pbkdf2-sha256` can log in because their passwords may be shorter than 14 characters.
|
||||
|
||||
* RSA keys of 1024 bits do not work (2048 is the minimum). This applies for keys used by the {project_name} realm itself (Realm keys from the `Keys` tab in the admin console), but also client keys and IDP keys
|
||||
|
||||
@ -17,7 +17,7 @@ to configure a {project_name} Truststore so that {project_name} is able to perfo
|
||||
== Client Configuration Command
|
||||
The HTTP client that {project_name} uses for outgoing communication is highly configurable. To configure the {project_name} outgoing HTTP client, enter this command:
|
||||
|
||||
<@kc.start parameters="--spi-connections-http-client-default-<configurationoption>=<value>"/>
|
||||
<@kc.start parameters="--spi-connections-http-client--default--<configurationoption>=<value>"/>
|
||||
|
||||
The following are the command options:
|
||||
|
||||
@ -90,7 +90,7 @@ For example, consider the following regex:
|
||||
|
||||
You apply a regex-based hostname pattern by entering this command:
|
||||
|
||||
<@kc.start parameters="--spi-connections-http-client-default-proxy-mappings=\'.*\\\\.(google|googleapis)\\\\.com;http://www-proxy.acme.com:8080\'"/>
|
||||
<@kc.start parameters="--spi-connections-http-client--default--proxy-mappings=\'.*\\\\.(google|googleapis)\\\\.com;http://www-proxy.acme.com:8080\'"/>
|
||||
|
||||
The backslash character `\` is escaped again because micro-profile config is used to parse the array of mappings.
|
||||
|
||||
|
||||
@ -84,12 +84,12 @@ From this point, it is beneficial if load balancer forwards all the next request
|
||||
|
||||
The sticky session is not mandatory for the cluster setup, however it is good for performance for the reasons mentioned above. You need to configure your loadbalancer to stick over the AUTH_SESSION_ID cookie. The appropriate procedure to make this change depends on your loadbalancer.
|
||||
|
||||
If your proxy supports session affinity without processing cookies from backend nodes, you should set the `spi-sticky-session-encoder-infinispan-should-attach-route` option
|
||||
If your proxy supports session affinity without processing cookies from backend nodes, you should set the `+spi-sticky-session-encoder--infinispan--should-attach-route+` option
|
||||
to `false` in order to avoid attaching the node to cookies and just rely on the reverse proxy capabilities.
|
||||
|
||||
<@kc.start parameters="--spi-sticky-session-encoder-infinispan-should-attach-route=false"/>
|
||||
<@kc.start parameters="--spi-sticky-session-encoder--infinispan--should-attach-route=false"/>
|
||||
|
||||
By default, the `spi-sticky-session-encoder-infinispan-should-attach-route` option value is `true` so that the node name is attached to
|
||||
By default, the `+spi-sticky-session-encoder--infinispan--should-attach-route+` option value is `true` so that the node name is attached to
|
||||
cookies to indicate to the reverse proxy the node that subsequent requests should be sent to.
|
||||
|
||||
== Exposed path recommendations
|
||||
@ -169,9 +169,9 @@ Client certificate lookup via a proxy header for X.509 authentication is conside
|
||||
and edge TLS termination.
|
||||
* If passthrough is not an option, implement the following security measures:
|
||||
** Configure your network so that {project_name} is isolated and can accept connections only from the proxy.
|
||||
** Make sure that the proxy overwrites the header that is configured in `spi-x509cert-lookup-<provider>-ssl-client-cert` option.
|
||||
** Pay extra attention to the `spi-x509cert-lookup-<provider>-trust-proxy-verification` setting. Make sure you enable it only if you can trust your proxy to verify the client certificate.
|
||||
Setting `spi-x509cert-lookup-<provider>-trust-proxy-verification=true` without the proxy verifying the client certificate chain will expose {project_name} to security vulnerability
|
||||
** Make sure that the proxy overwrites the header that is configured in `spi-x509cert-lookup--<provider>--ssl-client-cert` option.
|
||||
** Pay extra attention to the `spi-x509cert-lookup--<provider>--trust-proxy-verification` setting. Make sure you enable it only if you can trust your proxy to verify the client certificate.
|
||||
Setting `spi-x509cert-lookup--<provider>--trust-proxy-verification=true` without the proxy verifying the client certificate chain will expose {project_name} to security vulnerability
|
||||
when a forged client certificate can be used for authentication.
|
||||
====
|
||||
|
||||
@ -194,10 +194,10 @@ The server supports some of the most commons TLS termination proxies such as:
|
||||
To configure how client certificates are retrieved from the requests you need to:
|
||||
|
||||
.Enable the corresponding proxy provider
|
||||
<@kc.build parameters="--spi-x509cert-lookup-provider=<provider>"/>
|
||||
<@kc.build parameters="--spi-x509cert-lookup--provider=<provider>"/>
|
||||
|
||||
.Configure the HTTP headers
|
||||
<@kc.start parameters="--spi-x509cert-lookup-<provider>-ssl-client-cert=SSL_CLIENT_CERT --spi-x509cert-lookup-<provider>-ssl-cert-chain-prefix=CERT_CHAIN --spi-x509cert-lookup-<provider>-certificate-chain-length=10"/>
|
||||
<@kc.start parameters="--spi-x509cert-lookup--<provider>--ssl-client-cert=SSL_CLIENT_CERT --spi-x509cert-lookup--<provider>--ssl-cert-chain-prefix=CERT_CHAIN --spi-x509cert-lookup--<provider>-certificate-chain-length=10"/>
|
||||
|
||||
When configuring the HTTP headers, you need to make sure the values you are using correspond to the name of the headers
|
||||
forwarded by the proxy with the client certificate information.
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
|
||||
<#list options as option>
|
||||
|
|
||||
[.options-key]#``${option.key}``# <#if buildIcon><#if option.build>[.none]#icon:tools[role=options-build]#</#if></#if>
|
||||
[.options-key]#`+${option.key}+`# <#if buildIcon><#if option.build>[.none]#icon:tools[role=options-build]#</#if></#if>
|
||||
|
||||
[.options-description]#${option.description}#
|
||||
|
||||
@ -31,8 +31,8 @@
|
||||
--
|
||||
<#if option.descriptionExtended?has_content>[.options-description-extended]#${option.descriptionExtended!}#</#if>
|
||||
|
||||
*CLI:* `${option.keyCli}` +
|
||||
*Env:* `${option.keyEnv}`
|
||||
*CLI:* `+${option.keyCli}+` +
|
||||
*Env:* `+${option.keyEnv}+`
|
||||
--
|
||||
|
||||
<#if option.enabledWhen?has_content>
|
||||
|
||||
@ -36,13 +36,13 @@ NOTE: To set the theme for the `master` Admin Console you need to set the Admin
|
||||
+
|
||||
. To see the changes to the Admin Console refresh the page.
|
||||
|
||||
. Change the welcome theme by using the `spi-theme-welcome-theme` option.
|
||||
. Change the welcome theme by using the `spi-theme--welcome-theme` option.
|
||||
|
||||
. For example:
|
||||
+
|
||||
[source,bash]
|
||||
----
|
||||
bin/kc.[sh|bat] start --spi-theme-welcome-theme=custom-theme
|
||||
bin/kc.[sh|bat] start --spi-theme--welcome-theme=custom-theme
|
||||
----
|
||||
|
||||
[[_default-themes]]
|
||||
@ -79,7 +79,7 @@ restarting {project_name}.
|
||||
+
|
||||
[source,bash]
|
||||
----
|
||||
bin/kc.[sh|bat] start --spi-theme-static-max-age=-1 --spi-theme-cache-themes=false --spi-theme-cache-templates=false
|
||||
bin/kc.[sh|bat] start --spi-theme-static-max-age=-1 --spi-theme-cache-themes=false --spi-theme--cache-templates=false
|
||||
----
|
||||
|
||||
. Create a directory in the `themes` directory.
|
||||
|
||||
@ -12,9 +12,9 @@ By default, the welcome theme is only used to create the initial temporary admin
|
||||
|
||||
Since the welcome theme is not associated with a realm, it cannot be selected in the admin console like other themes.
|
||||
|
||||
To change the welcome theme, create and deploy a new welcome theme as described in <<_creating-a-theme,Creating a theme>>. Then, start the {project_name} server using the `spi-theme-welcome-theme` option.
|
||||
To change the welcome theme, create and deploy a new welcome theme as described in <<_creating-a-theme,Creating a theme>>. Then, start the {project_name} server using the `spi-theme--welcome-theme` option.
|
||||
[source,bash]
|
||||
----
|
||||
bin/kc.[sh|bat] start --spi-theme-welcome-theme=custom-theme
|
||||
bin/kc.[sh|bat] start --spi-theme--welcome-theme=custom-theme
|
||||
----
|
||||
</@tmpl.guide>
|
||||
@ -1,11 +1,9 @@
|
||||
package org.keycloak.guides.maven;
|
||||
|
||||
import static org.aesh.readline.terminal.Key.r;
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.OPTION_PART_SEPARATOR;
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.toDashCase;
|
||||
import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.keycloak.config.ConfigSupportLevel;
|
||||
import org.keycloak.config.DeprecatedMetadata;
|
||||
import org.keycloak.config.OptionCategory;
|
||||
@ -14,7 +12,6 @@ import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.ProviderManager;
|
||||
import org.keycloak.provider.Spi;
|
||||
import org.keycloak.quarkus.runtime.Providers;
|
||||
import org.keycloak.quarkus.runtime.configuration.Configuration;
|
||||
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
|
||||
import org.keycloak.utils.StringUtil;
|
||||
|
||||
@ -63,6 +60,10 @@ public class Options {
|
||||
.flatMap(Collection::stream)
|
||||
.forEach(option -> option.description = option.description.replaceAll("'([^ ]*)'", "`$1`"));
|
||||
|
||||
ArrayList<String> booleanValues = new ArrayList<>();
|
||||
booleanValues.add("true");
|
||||
booleanValues.add("false");
|
||||
|
||||
for (Spi loadSpi : providerManager.loadSpis().stream().sorted(Comparator.comparing(Spi::getName)).toList()) {
|
||||
for (ProviderFactory<?> providerFactory : providerManager.load(loadSpi).stream().sorted(Comparator.comparing(ProviderFactory::getId)).toList()) {
|
||||
List<ProviderConfigProperty> configMetadata = providerFactory.getConfigMetadata();
|
||||
@ -71,9 +72,11 @@ public class Options {
|
||||
continue;
|
||||
}
|
||||
|
||||
String optionPrefix = NS_KEYCLOAK_PREFIX + String.join(OPTION_PART_SEPARATOR, ArrayUtils.insert(0, new String[] {loadSpi.getName(), providerFactory.getId()}, "spi"));
|
||||
String spiKey = toDashCase(loadSpi.getName());
|
||||
String providerKey = toDashCase(providerFactory.getId());
|
||||
String optionPrefix = NS_KEYCLOAK_PREFIX + "spi" + OPTION_PART_SEPARATOR + spiKey + OPTION_PART_SEPARATOR + OPTION_PART_SEPARATOR + providerKey + OPTION_PART_SEPARATOR + OPTION_PART_SEPARATOR;
|
||||
List<Option> options = configMetadata.stream()
|
||||
.map(m -> new Option(Configuration.toDashCase(optionPrefix.concat("-") + m.getName()), OptionCategory.GENERAL, false,
|
||||
.map(m -> new Option(optionPrefix + toDashCase(m.getName()), OptionCategory.GENERAL, false,
|
||||
m.getType(),
|
||||
m.getHelpText(),
|
||||
m.getDefaultValue() == null ? null : m.getDefaultValue().toString(),
|
||||
@ -83,9 +86,6 @@ public class Options {
|
||||
null))
|
||||
.sorted(Comparator.comparing(Option::getKey)).collect(Collectors.toList());
|
||||
|
||||
ArrayList<String> booleanValues = new ArrayList<>();
|
||||
booleanValues.add("true");
|
||||
booleanValues.add("false");
|
||||
options.forEach(option -> {
|
||||
if (option.type.equals("boolean")) {
|
||||
option.expectedValues = booleanValues;
|
||||
@ -94,7 +94,7 @@ public class Options {
|
||||
});
|
||||
|
||||
if (!options.isEmpty()) {
|
||||
providerOptions.computeIfAbsent(toDashCase(loadSpi.getName()), k -> new LinkedHashMap<>()).put(toDashCase(providerFactory.getId()), options);
|
||||
providerOptions.computeIfAbsent(spiKey, k -> new LinkedHashMap<>()).put(providerKey, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -261,7 +261,9 @@ public class Options {
|
||||
}
|
||||
|
||||
public String getEnabledWhen() {
|
||||
if (StringUtil.isBlank(enabledWhen)) return null;
|
||||
if (StringUtil.isBlank(enabledWhen)) {
|
||||
return null;
|
||||
}
|
||||
return enabledWhen;
|
||||
}
|
||||
|
||||
|
||||
@ -38,6 +38,7 @@ import java.util.Arrays;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@ -377,10 +378,11 @@ public class Picocli {
|
||||
DisabledMappersInterceptor.disable(); // we want all properties, even disabled ones
|
||||
|
||||
final List<String> ignoredRunTime = new ArrayList<>();
|
||||
final Set<String> disabledBuildTime = new HashSet<>();
|
||||
final Set<String> disabledRunTime = new HashSet<>();
|
||||
final Set<String> deprecatedInUse = new HashSet<>();
|
||||
final Set<String> missingOption = new HashSet<>();
|
||||
final Set<String> disabledBuildTime = new LinkedHashSet<>();
|
||||
final Set<String> disabledRunTime = new LinkedHashSet<>();
|
||||
final Set<String> deprecatedInUse = new LinkedHashSet<>();
|
||||
final Set<String> missingOption = new LinkedHashSet<>();
|
||||
final Set<String> ambiguousSpi = new LinkedHashSet<>();
|
||||
final LinkedHashMap<String, String> secondClassOptions = new LinkedHashMap<>();
|
||||
|
||||
final Set<PropertyMapper<?>> disabledMappers = new HashSet<>();
|
||||
@ -402,6 +404,9 @@ public class Picocli {
|
||||
if (!options.includeRuntime) {
|
||||
checkRuntimeSpiOptions(name, ignoredRunTime);
|
||||
}
|
||||
if (PropertyMappers.isMaybeSpiBuildTimeProperty(name)) {
|
||||
ambiguousSpi.add(name);
|
||||
}
|
||||
PropertyMapper<?> mapper = PropertyMappers.getMapper(name);
|
||||
if (mapper == null) {
|
||||
return; // TODO: need to look for disabled Wildcard mappers
|
||||
@ -460,7 +465,9 @@ public class Picocli {
|
||||
if (!deprecatedInUse.isEmpty()) {
|
||||
warn("The following used options or option values are DEPRECATED and will be removed or their behaviour changed in a future release:\n" + String.join("\n", deprecatedInUse) + "\nConsult the Release Notes for details.", getOutWriter());
|
||||
}
|
||||
|
||||
if (!ambiguousSpi.isEmpty()) {
|
||||
warn("The following spi options are using the legacy format and are not being treated as build time options. Please use the new format with the appropriate -- separators to resolve this ambiguity: " + String.join("\n", ambiguousSpi));
|
||||
}
|
||||
secondClassOptions.forEach((key, firstClass) -> {
|
||||
warn("Please use the first-class option `%s` instead of `%s`".formatted(firstClass, key), getOutWriter());
|
||||
});
|
||||
|
||||
@ -176,11 +176,17 @@ public final class Configuration {
|
||||
}
|
||||
|
||||
public static String toDashCase(String key) {
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder(key.length());
|
||||
boolean l = false;
|
||||
|
||||
for (int i = 0; i < key.length(); i++) {
|
||||
char c = key.charAt(i);
|
||||
if (c == ',') {
|
||||
c = '-'; // should not happen, but was allowed by the previous logic
|
||||
}
|
||||
if (l && Character.isUpperCase(c)) {
|
||||
sb.append('-');
|
||||
c = Character.toLowerCase(c);
|
||||
|
||||
@ -19,12 +19,10 @@ package org.keycloak.quarkus.runtime.configuration;
|
||||
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.OPTION_PART_SEPARATOR;
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.toDashCase;
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.toEnvVarFormat;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.eclipse.microprofile.config.ConfigProvider;
|
||||
|
||||
import org.keycloak.Config;
|
||||
@ -33,6 +31,7 @@ public class MicroProfileConfigProvider implements Config.ConfigProvider {
|
||||
|
||||
public static final String NS_KEYCLOAK = "kc";
|
||||
public static final String NS_KEYCLOAK_PREFIX = NS_KEYCLOAK + ".";
|
||||
public static final String SPI_PREFIX = NS_KEYCLOAK_PREFIX + "spi" + OPTION_PART_SEPARATOR;
|
||||
public static final String NS_QUARKUS = "quarkus";
|
||||
public static final String NS_QUARKUS_PREFIX = "quarkus" + ".";
|
||||
|
||||
@ -58,21 +57,21 @@ public class MicroProfileConfigProvider implements Config.ConfigProvider {
|
||||
|
||||
@Override
|
||||
public Config.Scope scope(String... scope) {
|
||||
return new MicroProfileScope(scope);
|
||||
return new MicroProfileScope(SPI_PREFIX, scope);
|
||||
}
|
||||
|
||||
public class MicroProfileScope implements Config.Scope {
|
||||
|
||||
private final String[] scope;
|
||||
private final String prefix;
|
||||
private final String separatorPrefix;
|
||||
|
||||
public MicroProfileScope(String... scopes) {
|
||||
this.scope = scopes;
|
||||
StringBuilder prefix = new StringBuilder(NS_KEYCLOAK_PREFIX).append("spi");
|
||||
public MicroProfileScope(String prefix, String... scopes) {
|
||||
StringBuilder prefixBuilder = new StringBuilder(prefix);
|
||||
for (String scope : scopes) {
|
||||
prefix.append(OPTION_PART_SEPARATOR).append(scope);
|
||||
prefixBuilder.append(toDashCase(scope)).append(OPTION_PART_SEPARATOR + OPTION_PART_SEPARATOR);
|
||||
}
|
||||
this.prefix = prefix.toString();
|
||||
this.separatorPrefix = prefixBuilder.toString();
|
||||
this.prefix = separatorPrefix.replace(OPTION_PART_SEPARATOR + OPTION_PART_SEPARATOR, OPTION_PART_SEPARATOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -122,23 +121,22 @@ public class MicroProfileConfigProvider implements Config.ConfigProvider {
|
||||
|
||||
@Override
|
||||
public Config.Scope scope(String... scope) {
|
||||
return new MicroProfileScope(ArrayUtils.addAll(this.scope, scope));
|
||||
return new MicroProfileScope(prefix, scope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getPropertyNames() {
|
||||
return StreamSupport.stream(config.getPropertyNames().spliterator(), false)
|
||||
.filter(this::startWithPrefix)
|
||||
.filter(key -> key.startsWith(separatorPrefix))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private <T> T getValue(String key, Class<T> clazz, T defaultValue) {
|
||||
return config.getOptionalValue(toDashCase(prefix.concat(OPTION_PART_SEPARATOR).concat(key.replace('.', '-'))), clazz).orElse(defaultValue);
|
||||
String dashCase = toDashCase(key);
|
||||
return config.getOptionalValue(separatorPrefix.concat(dashCase), clazz)
|
||||
.or(() -> config.getOptionalValue(prefix.concat(dashCase), clazz)).orElse(defaultValue);
|
||||
}
|
||||
|
||||
private boolean startWithPrefix(String key) {
|
||||
return key.startsWith(toDashCase(prefix)) || key.startsWith(toDashCase(toEnvVarFormat(prefix)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ final class CachingPropertyMappers {
|
||||
.build(),
|
||||
fromOption(CachingOptions.CACHE_STACK)
|
||||
.isEnabled(CachingPropertyMappers::cacheSetToInfinispan, CACHE_STACK_SET_TO_ISPN)
|
||||
.to("kc.spi-cache-embedded-default-stack")
|
||||
.to("kc.spi-cache--embedded--default-stack")
|
||||
.paramLabel("stack")
|
||||
.build(),
|
||||
fromOption(CachingOptions.CACHE_CONFIG_FILE)
|
||||
@ -49,10 +49,11 @@ final class CachingPropertyMappers {
|
||||
return "cache-local.xml";
|
||||
} else if (CachingOptions.Mechanism.ispn.name().equals(value)) {
|
||||
return resolveConfigFile("cache-ispn.xml", null);
|
||||
} else
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.to("kc.spi-cache-embedded-default-config-file")
|
||||
.to("kc.spi-cache-embedded--default--config-file")
|
||||
.transformer(CachingPropertyMappers::resolveConfigFile)
|
||||
.validator(s -> {
|
||||
if (!Files.exists(Paths.get(resolveConfigFile(s, null)))) {
|
||||
@ -62,72 +63,72 @@ final class CachingPropertyMappers {
|
||||
.paramLabel("file")
|
||||
.build(),
|
||||
fromOption(CachingOptions.CACHE_EMBEDDED_MTLS_ENABLED)
|
||||
.to("kc.spi-jgroups-mtls-default-enabled")
|
||||
.to("kc.spi-jgroups-mtls--default--enabled")
|
||||
.isEnabled(CachingPropertyMappers::getDefaultMtlsEnabled, "a TCP based cache-stack is used")
|
||||
.build(),
|
||||
fromOption(CachingOptions.CACHE_EMBEDDED_MTLS_KEYSTORE.withRuntimeSpecificDefault(getDefaultKeystorePathValue()))
|
||||
.paramLabel("file")
|
||||
.to("kc.spi-jgroups-mtls-default-keystore-file")
|
||||
.to("kc.spi-jgroups-mtls--default--keystore-file")
|
||||
.isEnabled(() -> Configuration.isTrue(CachingOptions.CACHE_EMBEDDED_MTLS_ENABLED), "property '%s' is enabled".formatted(CachingOptions.CACHE_EMBEDDED_MTLS_ENABLED.getKey()))
|
||||
.validator(value -> checkValidKeystore(value, CachingOptions.CACHE_EMBEDDED_MTLS_KEYSTORE, CachingOptions.CACHE_EMBEDDED_MTLS_KEYSTORE_PASSWORD))
|
||||
.build(),
|
||||
fromOption(CachingOptions.CACHE_EMBEDDED_MTLS_KEYSTORE_PASSWORD)
|
||||
.paramLabel("password")
|
||||
.isMasked(true)
|
||||
.to("kc.spi-jgroups-mtls-default-keystore-password")
|
||||
.to("kc.spi-jgroups-mtls--default--keystore-password")
|
||||
.isEnabled(() -> Configuration.isTrue(CachingOptions.CACHE_EMBEDDED_MTLS_ENABLED), "property '%s' is enabled".formatted(CachingOptions.CACHE_EMBEDDED_MTLS_ENABLED.getKey()))
|
||||
.validator(value -> checkOptionPresent(CachingOptions.CACHE_EMBEDDED_MTLS_KEYSTORE_PASSWORD, CachingOptions.CACHE_EMBEDDED_MTLS_KEYSTORE))
|
||||
.build(),
|
||||
fromOption(CachingOptions.CACHE_EMBEDDED_MTLS_TRUSTSTORE.withRuntimeSpecificDefault(getDefaultTruststorePathValue()))
|
||||
.paramLabel("file")
|
||||
.to("kc.spi-jgroups-mtls-default-truststore-file")
|
||||
.to("kc.spi-jgroups-mtls--default--truststore-file")
|
||||
.isEnabled(() -> Configuration.isTrue(CachingOptions.CACHE_EMBEDDED_MTLS_ENABLED), "property '%s' is enabled".formatted(CachingOptions.CACHE_EMBEDDED_MTLS_ENABLED.getKey()))
|
||||
.validator(value -> checkValidKeystore(value, CachingOptions.CACHE_EMBEDDED_MTLS_TRUSTSTORE, CachingOptions.CACHE_EMBEDDED_MTLS_TRUSTSTORE_PASSWORD))
|
||||
.build(),
|
||||
fromOption(CachingOptions.CACHE_EMBEDDED_MTLS_TRUSTSTORE_PASSWORD)
|
||||
.paramLabel("password")
|
||||
.isMasked(true)
|
||||
.to("kc.spi-jgroups-mtls-default-truststore-password")
|
||||
.to("kc.spi-jgroups-mtls--default--truststore-password")
|
||||
.isEnabled(() -> Configuration.isTrue(CachingOptions.CACHE_EMBEDDED_MTLS_ENABLED), "property '%s' is enabled".formatted(CachingOptions.CACHE_EMBEDDED_MTLS_ENABLED.getKey()))
|
||||
.validator(value -> checkOptionPresent(CachingOptions.CACHE_EMBEDDED_MTLS_TRUSTSTORE_PASSWORD, CachingOptions.CACHE_EMBEDDED_MTLS_TRUSTSTORE))
|
||||
.build(),
|
||||
fromOption(CachingOptions.CACHE_EMBEDDED_MTLS_ROTATION)
|
||||
.paramLabel("days")
|
||||
.to("kc.spi-jgroups-mtls-default-rotation")
|
||||
.to("kc.spi-jgroups-mtls--default--rotation")
|
||||
.isEnabled(() -> Configuration.isTrue(CachingOptions.CACHE_EMBEDDED_MTLS_ENABLED), "property '%s' is enabled".formatted(CachingOptions.CACHE_EMBEDDED_MTLS_ENABLED.getKey()))
|
||||
.validator(CachingPropertyMappers::validateCertificateRotationIsPositive)
|
||||
.build(),
|
||||
fromOption(CachingOptions.CACHE_REMOTE_HOST)
|
||||
.paramLabel("hostname")
|
||||
.to("kc.spi-cache-remote-default-hostname")
|
||||
.to("kc.spi-cache-remote--default--hostname")
|
||||
.addValidateEnabled(CachingPropertyMappers::isRemoteCacheHostEnabled, MULTI_SITE_OR_EMBEDDED_REMOTE_FEATURE_SET)
|
||||
.isRequired(InfinispanUtils::isRemoteInfinispan, MULTI_SITE_FEATURE_SET)
|
||||
.build(),
|
||||
fromOption(CachingOptions.CACHE_REMOTE_PORT)
|
||||
.isEnabled(CachingPropertyMappers::remoteHostSet, CachingPropertyMappers.REMOTE_HOST_SET)
|
||||
.to("kc.spi-cache-remote-default-port")
|
||||
.to("kc.spi-cache-remote--default--port")
|
||||
.paramLabel("port")
|
||||
.build(),
|
||||
fromOption(CachingOptions.CACHE_REMOTE_TLS_ENABLED)
|
||||
.isEnabled(CachingPropertyMappers::remoteHostSet, CachingPropertyMappers.REMOTE_HOST_SET)
|
||||
.to("kc.spi-cache-remote-default-tls-enabled")
|
||||
.to("kc.spi-cache-remote--default--tls-enabled")
|
||||
.build(),
|
||||
fromOption(CachingOptions.CACHE_REMOTE_USERNAME)
|
||||
.isEnabled(CachingPropertyMappers::remoteHostSet, CachingPropertyMappers.REMOTE_HOST_SET)
|
||||
.to("kc.spi-cache-remote-default-username")
|
||||
.to("kc.spi-cache-remote--default--username")
|
||||
.validator((value) -> validateCachingOptionIsPresent(CachingOptions.CACHE_REMOTE_USERNAME, CachingOptions.CACHE_REMOTE_PASSWORD))
|
||||
.paramLabel("username")
|
||||
.build(),
|
||||
fromOption(CachingOptions.CACHE_REMOTE_PASSWORD)
|
||||
.isEnabled(CachingPropertyMappers::remoteHostSet, CachingPropertyMappers.REMOTE_HOST_SET)
|
||||
.to("kc.spi-cache-remote-default-password")
|
||||
.to("kc.spi-cache-remote--default--password")
|
||||
.validator((value) -> validateCachingOptionIsPresent(CachingOptions.CACHE_REMOTE_PASSWORD, CachingOptions.CACHE_REMOTE_USERNAME))
|
||||
.paramLabel("password")
|
||||
.isMasked(true)
|
||||
.build(),
|
||||
fromOption(CachingOptions.CACHE_METRICS_HISTOGRAMS_ENABLED)
|
||||
.isEnabled(MetricsPropertyMappers::metricsEnabled, MetricsPropertyMappers.METRICS_ENABLED_MSG)
|
||||
.to("kc.spi-cache-embedded-default-metrics-histograms-enabled")
|
||||
.to("kc.spi-cache-embedded--default--metrics-histograms-enabled")
|
||||
.build()
|
||||
);
|
||||
|
||||
@ -135,11 +136,13 @@ final class CachingPropertyMappers {
|
||||
List<PropertyMapper<?>> mappers = new ArrayList<>(numMappers);
|
||||
mappers.addAll(staticMappers);
|
||||
|
||||
for (String cache : CachingOptions.LOCAL_MAX_COUNT_CACHES)
|
||||
for (String cache : CachingOptions.LOCAL_MAX_COUNT_CACHES) {
|
||||
mappers.add(maxCountOpt(cache, () -> true, ""));
|
||||
}
|
||||
|
||||
for (String cache : CachingOptions.CLUSTERED_MAX_COUNT_CACHES)
|
||||
for (String cache : CachingOptions.CLUSTERED_MAX_COUNT_CACHES) {
|
||||
mappers.add(maxCountOpt(cache, InfinispanUtils::isEmbeddedInfinispan, "embedded Infinispan clusters configured"));
|
||||
}
|
||||
|
||||
return mappers.toArray(new PropertyMapper[0]);
|
||||
}
|
||||
@ -212,7 +215,7 @@ final class CachingPropertyMappers {
|
||||
return fromOption(CachingOptions.maxCountOption(cacheName))
|
||||
.isEnabled(isEnabled, enabledWhen)
|
||||
.paramLabel("max-count")
|
||||
.to("kc.spi-cache-embedded-default-%s-max-count".formatted(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, cacheName)))
|
||||
.to("kc.spi-cache-embedded--default--%s-max-count".formatted(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, cacheName)))
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -228,8 +231,9 @@ final class CachingPropertyMappers {
|
||||
|
||||
private static void checkValidKeystore(String store, Option<String> option, Option<String> requiredOption) {
|
||||
checkOptionPresent(option, requiredOption);
|
||||
if (!new File(store).exists())
|
||||
if (!new File(store).exists()) {
|
||||
throw new IllegalArgumentException("The '%s' file '%s' does not exist.".formatted(option.getKey(), store));
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkOptionPresent(Option<String> option, Option<String> requiredOption) {
|
||||
|
||||
@ -18,16 +18,16 @@ final class EventPropertyMappers {
|
||||
public static PropertyMapper<?>[] getMetricsPropertyMappers() {
|
||||
return new PropertyMapper[] {
|
||||
fromOption(USER_EVENT_METRICS_ENABLED)
|
||||
.to("kc.spi-events-listener-micrometer-user-event-metrics-enabled")
|
||||
.to("kc.spi-events-listener--micrometer-user-event-metrics--enabled")
|
||||
.isEnabled(EventPropertyMappers::userEventsMetricsEnabled, METRICS_ENABLED_MSG + " and feature " + Profile.Feature.USER_EVENT_METRICS.getKey() + " is enabled")
|
||||
.build(),
|
||||
fromOption(USER_EVENT_METRICS_TAGS)
|
||||
.to("kc.spi-events-listener-micrometer-user-event-metrics-tags")
|
||||
.to("kc.spi-events-listener--micrometer-user-event-metrics--tags")
|
||||
.paramLabel("tags")
|
||||
.isEnabled(EventPropertyMappers::userEventsMetricsTags, "user event metrics are enabled")
|
||||
.build(),
|
||||
fromOption(USER_EVENT_METRICS_EVENTS)
|
||||
.to("kc.spi-events-listener-micrometer-user-event-metrics-events")
|
||||
.to("kc.spi-events-listener--micrometer-user-event-metrics--events")
|
||||
.paramLabel("events")
|
||||
.isEnabled(EventPropertyMappers::userEventsMetricsTags, "user event metrics are enabled")
|
||||
.build(),
|
||||
|
||||
@ -33,7 +33,7 @@ import static org.keycloak.quarkus.runtime.configuration.Configuration.isBlank;
|
||||
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
|
||||
|
||||
public final class ExportPropertyMappers {
|
||||
private static final String EXPORTER_PROPERTY = "kc.spi-export-exporter";
|
||||
private static final String EXPORTER_PROPERTY = "kc.spi-export--exporter";
|
||||
private static final String SINGLE_FILE = "singleFile";
|
||||
private static final String DIR = "dir";
|
||||
|
||||
@ -48,30 +48,30 @@ public final class ExportPropertyMappers {
|
||||
.paramLabel("file")
|
||||
.build(),
|
||||
fromOption(ExportOptions.FILE)
|
||||
.to("kc.spi-export-single-file-file")
|
||||
.to("kc.spi-export--single-file--file")
|
||||
.paramLabel("file")
|
||||
.build(),
|
||||
fromOption(ExportOptions.DIR)
|
||||
.to("kc.spi-export-dir-dir")
|
||||
.to("kc.spi-export--dir--dir")
|
||||
.paramLabel("dir")
|
||||
.build(),
|
||||
fromOption(ExportOptions.REALM)
|
||||
.to("kc.spi-export-single-file-realm-name")
|
||||
.to("kc.spi-export--single-file--realm-name")
|
||||
.isEnabled(ExportPropertyMappers::isSingleFileProvider)
|
||||
.paramLabel("realm")
|
||||
.build(),
|
||||
fromOption(ExportOptions.REALM)
|
||||
.to("kc.spi-export-dir-realm-name")
|
||||
.to("kc.spi-export--dir--realm-name")
|
||||
.isEnabled(ExportPropertyMappers::isDirProvider)
|
||||
.paramLabel("realm")
|
||||
.build(),
|
||||
fromOption(ExportOptions.USERS)
|
||||
.to("kc.spi-export-dir-users-export-strategy")
|
||||
.to("kc.spi-export--dir--users-export-strategy")
|
||||
.addValidator(ExportPropertyMappers::validateUsersUsage)
|
||||
.paramLabel("strategy")
|
||||
.build(),
|
||||
fromOption(ExportOptions.USERS_PER_FILE)
|
||||
.to("kc.spi-export-dir-users-per-file")
|
||||
.to("kc.spi-export--dir--users-per-file")
|
||||
.isEnabled(ExportPropertyMappers::isDirProvider)
|
||||
.paramLabel("number")
|
||||
.build()
|
||||
@ -121,8 +121,8 @@ public final class ExportPropertyMappers {
|
||||
return exporter.getValue();
|
||||
}
|
||||
|
||||
var file = Configuration.getOptionalValue("kc.spi-export-single-file-file").map(f -> SINGLE_FILE);
|
||||
var dir = Configuration.getOptionalValue("kc.spi-export-dir-dir")
|
||||
var file = Configuration.getOptionalValue("kc.spi-export--single-file--file").map(f -> SINGLE_FILE);
|
||||
var dir = Configuration.getOptionalValue("kc.spi-export--dir--dir")
|
||||
.or(() -> Configuration.getOptionalValue("kc.dir"))
|
||||
.map(f -> DIR);
|
||||
|
||||
|
||||
@ -27,15 +27,15 @@ public final class HostnameV2PropertyMappers {
|
||||
public static PropertyMapper<?>[] getHostnamePropertyMappers() {
|
||||
return Stream.of(
|
||||
fromOption(HostnameV2Options.HOSTNAME)
|
||||
.to("kc.spi-hostname-v2-hostname")
|
||||
.to("kc.spi-hostname--v2--hostname")
|
||||
.paramLabel("hostname|URL"),
|
||||
fromOption(HostnameV2Options.HOSTNAME_ADMIN)
|
||||
.to("kc.spi-hostname-v2-hostname-admin")
|
||||
.to("kc.spi-hostname--v2--hostname-admin")
|
||||
.paramLabel("URL"),
|
||||
fromOption(HostnameV2Options.HOSTNAME_BACKCHANNEL_DYNAMIC)
|
||||
.to("kc.spi-hostname-v2-hostname-backchannel-dynamic"),
|
||||
.to("kc.spi-hostname--v2--hostname-backchannel-dynamic"),
|
||||
fromOption(HostnameV2Options.HOSTNAME_STRICT)
|
||||
.to("kc.spi-hostname-v2-hostname-strict"),
|
||||
.to("kc.spi-hostname--v2--hostname-strict"),
|
||||
fromOption(HostnameV2Options.HOSTNAME_DEBUG)
|
||||
)
|
||||
.map(b -> b.isEnabled(() -> Profile.isFeatureEnabled(Profile.Feature.HOSTNAME_V2), "hostname:v2 feature is enabled").build())
|
||||
|
||||
@ -31,7 +31,7 @@ import static org.keycloak.quarkus.runtime.configuration.Configuration.getOption
|
||||
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
|
||||
|
||||
public final class ImportPropertyMappers {
|
||||
private static final String IMPORTER_PROPERTY = "kc.spi-import-importer";
|
||||
private static final String IMPORTER_PROPERTY = "kc.spi-import--importer";
|
||||
private static final String SINGLE_FILE = "singleFile";
|
||||
private static final String DIR = "dir";
|
||||
|
||||
@ -46,20 +46,20 @@ public final class ImportPropertyMappers {
|
||||
.paramLabel("file")
|
||||
.build(),
|
||||
fromOption(ImportOptions.FILE)
|
||||
.to("kc.spi-import-single-file-file")
|
||||
.to("kc.spi-import--single-file--file")
|
||||
.paramLabel("file")
|
||||
.build(),
|
||||
fromOption(ImportOptions.DIR)
|
||||
.to("kc.spi-import-dir-dir")
|
||||
.to("kc.spi-import--dir--dir")
|
||||
.paramLabel("dir")
|
||||
.build(),
|
||||
fromOption(ImportOptions.OVERRIDE)
|
||||
.to("kc.spi-import-single-file-strategy")
|
||||
.to("kc.spi-import--single-file--strategy")
|
||||
.transformer(ImportPropertyMappers::transformOverride)
|
||||
.isEnabled(ImportPropertyMappers::isSingleFileProvider)
|
||||
.build(),
|
||||
fromOption(ImportOptions.OVERRIDE)
|
||||
.to("kc.spi-import-dir-strategy")
|
||||
.to("kc.spi-import--dir--strategy")
|
||||
.transformer(ImportPropertyMappers::transformOverride)
|
||||
.isEnabled(ImportPropertyMappers::isDirProvider)
|
||||
.build(),
|
||||
@ -107,8 +107,8 @@ public final class ImportPropertyMappers {
|
||||
return importer.getValue();
|
||||
}
|
||||
|
||||
var file = getOptionalValue("kc.spi-import-single-file-file").map(f -> SINGLE_FILE);
|
||||
var dir = getOptionalValue("kc.spi-import-dir-dir")
|
||||
var file = getOptionalValue("kc.spi-import--single-file--file").map(f -> SINGLE_FILE);
|
||||
var dir = getOptionalValue("kc.spi-import--dir--dir")
|
||||
.or(() -> getOptionalValue("kc.dir"))
|
||||
.map(f -> DIR);
|
||||
|
||||
|
||||
@ -18,12 +18,12 @@ final class MetricsPropertyMappers {
|
||||
.to("quarkus.micrometer.enabled")
|
||||
.build(),
|
||||
fromOption(MetricsOptions.PASSWORD_VALIDATION_COUNTER_ENABLED)
|
||||
.to("kc.spi-credential-keycloak-password-metrics-enabled")
|
||||
.to("kc.spi-credential--keycloak-password--metrics-enabled")
|
||||
.isEnabled(MetricsPropertyMappers::metricsEnabled, "metrics are enabled")
|
||||
.build(),
|
||||
fromOption(MetricsOptions.INFINISPAN_METRICS_ENABLED)
|
||||
.mapFrom(MetricsOptions.METRICS_ENABLED)
|
||||
.to("kc.spi-cache-embedded-default-metrics-enabled")
|
||||
.to("kc.spi-cache-embedded--default--metrics-enabled")
|
||||
.isEnabled(MetricsPropertyMappers::metricsEnabled, "metrics are enabled")
|
||||
.build()
|
||||
};
|
||||
|
||||
@ -97,7 +97,11 @@ public final class PropertyMappers {
|
||||
}
|
||||
|
||||
public static boolean isSpiBuildTimeProperty(String name) {
|
||||
return name.startsWith(KC_SPI_PREFIX) && (name.endsWith("-provider") || name.endsWith("-enabled") || name.endsWith("-provider-default"));
|
||||
return name.startsWith(KC_SPI_PREFIX) && (name.endsWith("--provider") || name.endsWith("--enabled") || name.endsWith("--provider-default"));
|
||||
}
|
||||
|
||||
public static boolean isMaybeSpiBuildTimeProperty(String name) {
|
||||
return name.startsWith(KC_SPI_PREFIX) && (name.endsWith("-provider") || name.endsWith("-enabled") || name.endsWith("-provider-default")) && !name.contains("--");
|
||||
}
|
||||
|
||||
private static boolean isKeycloakRuntime(String name, PropertyMapper<?> mapper) {
|
||||
@ -399,7 +403,15 @@ public final class PropertyMappers {
|
||||
private static void handleMapper(PropertyMapper<?> mapper, BiConsumer<String, PropertyMapper<?>> operation) {
|
||||
operation.accept(mapper.getFrom(), mapper);
|
||||
if (!mapper.getFrom().equals(mapper.getTo())) {
|
||||
operation.accept(mapper.getTo(), mapper);
|
||||
String to = mapper.getTo();
|
||||
operation.accept(to, mapper);
|
||||
if (to.startsWith(KC_SPI_PREFIX)) {
|
||||
if (!mapper.getTo().contains("--")) {
|
||||
throw new IllegalStateException("Mapper should use the new form of the SPI option with the `--` separator: " + to);
|
||||
}
|
||||
String legacyTo = mapper.getTo().replace("--", "-");
|
||||
operation.accept(legacyTo, mapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ public class TruststorePropertyMappers {
|
||||
.build(),
|
||||
fromOption(TruststoreOptions.HOSTNAME_VERIFICATION_POLICY)
|
||||
.paramLabel(TruststoreOptions.HOSTNAME_VERIFICATION_POLICY.getKey())
|
||||
.to("kc.spi-truststore-file-hostname-verification-policy")
|
||||
.to("kc.spi-truststore--file--hostname-verification-policy")
|
||||
.build(),
|
||||
};
|
||||
}
|
||||
|
||||
@ -15,19 +15,19 @@ final class VaultPropertyMappers {
|
||||
.paramLabel("provider")
|
||||
.build(),
|
||||
fromOption(VaultOptions.VAULT_DIR)
|
||||
.to("kc.spi-vault-file-dir")
|
||||
.to("kc.spi-vault--file--dir")
|
||||
.paramLabel("dir")
|
||||
.build(),
|
||||
fromOption(VaultOptions.VAULT_FILE)
|
||||
.to("kc.spi-vault-keystore-file")
|
||||
.to("kc.spi-vault--keystore--file")
|
||||
.paramLabel("file")
|
||||
.build(),
|
||||
fromOption(VaultOptions.VAULT_PASS)
|
||||
.to("kc.spi-vault-keystore-pass")
|
||||
.to("kc.spi-vault--keystore--pass")
|
||||
.paramLabel("pass")
|
||||
.build(),
|
||||
fromOption(VaultOptions.VAULT_TYPE)
|
||||
.to("kc.spi-vault-keystore-type")
|
||||
.to("kc.spi-vault--keystore--type")
|
||||
.paramLabel("type")
|
||||
.build()
|
||||
};
|
||||
|
||||
@ -71,9 +71,9 @@ quarkus.http.limits.max-header-size=65535
|
||||
%dev.kc.http-enabled=true
|
||||
%dev.kc.hostname-strict=false
|
||||
%dev.kc.cache=local
|
||||
%dev.kc.spi-theme-cache-themes=false
|
||||
%dev.kc.spi-theme-cache-templates=false
|
||||
%dev.kc.spi-theme-static-max-age=-1
|
||||
%dev.kc.spi-theme--cache-themes=false
|
||||
%dev.kc.spi-theme--cache-templates=false
|
||||
%dev.kc.spi-theme--static-max-age=-1
|
||||
|
||||
# The default configuration when running in import or export mode
|
||||
%nonserver.kc.http-enabled=true
|
||||
|
||||
@ -610,6 +610,20 @@ public class PicocliTest extends AbstractConfigurationTest {
|
||||
assertThat(nonRunningPicocli.getOutString(), containsString("Please use the first-class option `kc.hostname` instead of `kc.spi-hostname-v2-hostname`"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAmbiguousSpiOption() {
|
||||
NonRunningPicocli nonRunningPicocli = pseudoLaunch("start-dev", "--spi-x-y-enabled=true");
|
||||
assertEquals(CommandLine.ExitCode.OK, nonRunningPicocli.exitCode);
|
||||
assertThat(nonRunningPicocli.getOutString(), containsString("The following spi options are using the legacy format and are not being treated as build time options. Please use the new format with the appropriate -- separators to resolve this ambiguity: kc.spi-x-y-enabled"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAmbiguousSpiOptionBuild() {
|
||||
NonRunningPicocli nonRunningPicocli = pseudoLaunch("build", "--db=dev-file", "--spi-x-y-enabled=true");
|
||||
assertEquals(CommandLine.ExitCode.OK, nonRunningPicocli.exitCode);
|
||||
assertThat(nonRunningPicocli.getOutString(), containsString("The following spi options are using the legacy format and are not being treated as build time options. Please use the new format with the appropriate -- separators to resolve this ambiguity: kc.spi-x-y-enabled"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDerivedShowConfig() {
|
||||
NonRunningPicocli nonRunningPicocli = build("build", "--metrics-enabled=true", "--features=user-event-metrics", "--event-metrics-user-enabled=true", "--db=dev-file");
|
||||
@ -748,4 +762,16 @@ public class PicocliTest extends AbstractConfigurationTest {
|
||||
assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode);
|
||||
assertThat(nonRunningPicocli.getErrString(), containsString("Unknown option: '--db-kind-<default>'"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void errorSpiBuildtimeChanged() {
|
||||
putEnvVar("KC_SPI_EVENTS_LISTENER__PROVIDER", "jboss-logging");
|
||||
build("build", "--db=dev-file");
|
||||
|
||||
putEnvVar("KC_SPI_EVENTS_LISTENER__PROVIDER", "new-jboss-logging");
|
||||
|
||||
NonRunningPicocli nonRunningPicocli = pseudoLaunch("start", "--optimized", "--http-enabled=true", "--hostname-strict=false");
|
||||
assertEquals(CommandLine.ExitCode.USAGE, nonRunningPicocli.exitCode);
|
||||
assertThat(nonRunningPicocli.getErrString(), containsString("The following build time options have values that differ from what is persisted - the new values will NOT be used until another build is run: kc.spi-events-listener--provider"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,6 +65,14 @@ public class ConfigurationTest extends AbstractConfigurationTest {
|
||||
assertEquals(value, "foobar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCamelCaseNewFormat() {
|
||||
putEnvVar("KC_SPI_CAMEL_CASE_SCOPE__CAMEL_CASE_PROP", "foobar");
|
||||
initConfig();
|
||||
String value = Config.scope("camelCaseScope").get("camelCaseProp");
|
||||
assertEquals(value, "foobar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnvVarPriorityOverPropertiesFile() {
|
||||
putEnvVar("KC_SPI_HOSTNAME_DEFAULT_FRONTEND_URL", "http://envvar.unittest");
|
||||
@ -84,13 +92,13 @@ public class ConfigurationTest extends AbstractConfigurationTest {
|
||||
Config.Scope config = initConfig("vault", FilesPlainTextVaultProviderFactory.ID);
|
||||
assertEquals("/foo/bar", config.get("dir"));
|
||||
assertTrue(config.getPropertyNames()
|
||||
.contains("kc.spi-vault-".concat(FilesPlainTextVaultProviderFactory.ID).concat("-dir")));
|
||||
.contains("kc.spi-vault--".concat(FilesPlainTextVaultProviderFactory.ID).concat("--dir")));
|
||||
|
||||
putEnvVar("KC_VAULT_TYPE", "JKS");
|
||||
config = initConfig("vault", FilesKeystoreVaultProviderFactory.ID);
|
||||
assertEquals("JKS", config.get("type"));
|
||||
assertTrue(config.getPropertyNames()
|
||||
.contains("kc.spi-vault-".concat(FilesKeystoreVaultProviderFactory.ID).concat("-type")));
|
||||
.contains("kc.spi-vault--".concat(FilesKeystoreVaultProviderFactory.ID).concat("--type")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -183,7 +191,6 @@ public class ConfigurationTest extends AbstractConfigurationTest {
|
||||
public void testPropertyNamesFromConfig() {
|
||||
ConfigArgsConfigSource.setCliArgs("--spi-client-registration-openid-connect-static-jwk-url=http://c.jwk.url");
|
||||
Config.Scope config = initConfig("client-registration", "openid-connect");
|
||||
assertEquals(1, config.getPropertyNames().size());
|
||||
assertEquals("http://c.jwk.url", config.get("static-jwk-url"));
|
||||
|
||||
ConfigArgsConfigSource.setCliArgs("--vault-dir=secrets");
|
||||
@ -199,18 +206,15 @@ public class ConfigurationTest extends AbstractConfigurationTest {
|
||||
ConfigArgsConfigSource.setCliArgs();
|
||||
System.setProperty("kc.spi-client-registration-openid-connect-static-jwk-url", "http://c.jwk.url");
|
||||
config = initConfig("client-registration", "openid-connect");
|
||||
assertEquals(1, config.getPropertyNames().size());
|
||||
assertEquals("http://c.jwk.url", config.get("static-jwk-url"));
|
||||
|
||||
ConfigArgsConfigSource.setCliArgs();
|
||||
System.getProperties().remove("kc.spi-client-registration-openid-connect-static-jwk-url");
|
||||
putEnvVar("KC_SPI_CLIENT_REGISTRATION_OPENID_CONNECT_STATIC_JWK_URL", "http://c.jwk.url/from-env");
|
||||
config = initConfig("client-registration", "openid-connect");
|
||||
assertEquals(2, config.getPropertyNames().size()); // transformed name is coming from KcEnvVarConfigSource, raw env var name is coming from EnvVarConfigSource
|
||||
assertEquals("http://c.jwk.url/from-env", config.get("static-jwk-url"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPropertyMapping() {
|
||||
ConfigArgsConfigSource.setCliArgs("--db=mariadb", "--db-url=jdbc:mariadb://localhost/keycloak");
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2025 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.quarkus.runtime.configuration.mappers;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class PropertyMappersTest {
|
||||
|
||||
@Test
|
||||
public void testIsSpiBuildTimeProperty() {
|
||||
// Should return true for valid SPI build-time properties
|
||||
assertTrue(PropertyMappers.isSpiBuildTimeProperty("kc.spi.foo.bar--provider"));
|
||||
assertTrue(PropertyMappers.isSpiBuildTimeProperty("kc.spi.foo.bar--enabled"));
|
||||
assertTrue(PropertyMappers.isSpiBuildTimeProperty("kc.spi.foo.bar--provider-default"));
|
||||
|
||||
// return false for non-build time properties
|
||||
assertFalse(PropertyMappers.isSpiBuildTimeProperty("kc.spi.foo.bar-provider"));
|
||||
assertFalse(PropertyMappers.isSpiBuildTimeProperty("kc.spi.foo.bar-enabled"));
|
||||
assertFalse(PropertyMappers.isSpiBuildTimeProperty("kc.spi.foo.bar-provider-default"));
|
||||
assertFalse(PropertyMappers.isSpiBuildTimeProperty("some.other.property"));
|
||||
assertFalse(PropertyMappers.isSpiBuildTimeProperty("kc.spi.foo.bar"));
|
||||
}
|
||||
}
|
||||
@ -72,11 +72,13 @@ public class OptionsDistTest {
|
||||
|
||||
@Test
|
||||
@Order(5)
|
||||
@WithEnvVars({"KC_LOG", "console", "KC_LOG_FILE", "something-env", "KC_HTTP_ENABLED", "true", "KC_HOSTNAME_STRICT", "false"})
|
||||
@WithEnvVars({"KC_SPI_CONNECTIONS_HTTP_CLIENT__DEFAULT__EXPECT_CONTINUE_ENABLED", "true", "KC_LOG", "console", "KC_LOG_FILE", "something-env", "KC_HTTP_ENABLED", "true", "KC_HOSTNAME_STRICT", "false"})
|
||||
@Launch({"start", "--db=dev-file"})
|
||||
public void testSettingEnvVars(CLIResult cliResult) {
|
||||
cliResult.assertMessage("The following used run time options are UNAVAILABLE and will be ignored during build time:");
|
||||
cliResult.assertMessage("- log-file: Available only when File log handler is activated.");
|
||||
cliResult.assertNoMessage("kc.spi-connections-http-client"); // no info/warning expected
|
||||
cliResult.assertStarted();
|
||||
}
|
||||
|
||||
@DryRun
|
||||
|
||||
@ -76,12 +76,12 @@ public class StartCommandDistTest {
|
||||
CLIResult cliResult = dist.run("build", "--db=dev-file");
|
||||
cliResult.assertBuild();
|
||||
|
||||
cliResult = dist.run("start", "--optimized", "--http-enabled=true", "--hostname-strict=false", "--spi-events-listener-jboss-logging-enabled=false");
|
||||
cliResult.assertError("The following build time options have values that differ from what is persisted - the new values will NOT be used until another build is run: kc.spi-events-listener-jboss-logging-enabled");
|
||||
cliResult = dist.run("start", "--optimized", "--http-enabled=true", "--hostname-strict=false", "--spi-events-listener--jboss-logging--enabled=false");
|
||||
cliResult.assertError("The following build time options have values that differ from what is persisted - the new values will NOT be used until another build is run: kc.spi-events-listener--jboss-logging--enabled");
|
||||
}
|
||||
|
||||
@DryRun
|
||||
@WithEnvVars({"KC_SPI_EVENTS_LISTENER_JBOSS_LOGGING_ENABLED", "false"})
|
||||
@WithEnvVars({"KC_SPI_EVENTS_LISTENER__JBOSS_LOGGING__ENABLED", "false"})
|
||||
@Test
|
||||
@RawDistOnly(reason = "Containers are immutable")
|
||||
void noErrorSpiBuildtimeNotChanged(KeycloakDistribution dist) {
|
||||
|
||||
@ -69,14 +69,14 @@ public class SpiProvidersSwitchingUtils {
|
||||
@Override
|
||||
public void setDefaultProvider(Container container, String spiName, String providerId, String... config) {
|
||||
List<String> args = new LinkedList<>();
|
||||
args.add(KEYCLOAKX_ARG_SPI_PREFIX + toDashCase(spiName) + "-provider=" + providerId);
|
||||
args.add(KEYCLOAKX_ARG_SPI_PREFIX + toDashCase(spiName) + "--provider=" + providerId);
|
||||
if (config != null) {
|
||||
String optionName = null;
|
||||
for (String c : config) {
|
||||
if (optionName == null) {
|
||||
optionName = c;
|
||||
} else {
|
||||
args.add(KEYCLOAKX_ARG_SPI_PREFIX + toDashCase(spiName) + "-" + providerId + "-" + optionName + "=" + c);
|
||||
args.add(KEYCLOAKX_ARG_SPI_PREFIX + toDashCase(spiName) + "--" + providerId + "--" + optionName + "=" + c);
|
||||
optionName = null;
|
||||
}
|
||||
}
|
||||
@ -197,7 +197,7 @@ public class SpiProvidersSwitchingUtils {
|
||||
Container container = authServerInfo.getArquillianContainer();
|
||||
|
||||
log.infof("Removing default provider setting for %s", spi);
|
||||
|
||||
|
||||
if (annotation.onlyUpdateDefault()) {
|
||||
spiSwitcher.unsetDefaultProvider(container, spi);
|
||||
} else {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user