Git Product home page Git Product logo

keycloak-config-cli's Introduction

CI GitHub release (latest SemVer) GitHub All Releases Docker Pulls codecov GitHub license

keycloak-config-cli

keycloak-config-cli is a Keycloak utility to ensure the desired configuration state for a realm based on a JSON/YAML file. The format of the JSON/YAML file based on the export realm format. Store and handle the configuration files inside git just like normal code. A Keycloak restart isn't required to apply the configuration.

Config files

The config files are based on the keycloak export files. You can use them to re-import your settings. But keep your files as small as possible. Remove all UUIDs and all stuff which is default set by keycloak.

moped.json is a full working example file you can consider. Other examples are located in the test resources.

Variable Substitution

keycloak-config-cli supports variable substitution of config files. This could be enabled by import.var-substitution.enabled=true (disabled by default).

Variables exposed by spring boot (through configtree or external configuration) can be accessed by $(property.name).

In additional, the string substitution support multiple prefixes for different approaches

Base64 Decoder:        $(base64Decoder:SGVsbG9Xb3JsZCE=)
Base64 Encoder:        $(base64Encoder:HelloWorld!)
Java Constant:         $(const:java.awt.event.KeyEvent.VK_ESCAPE)
Date:                  $(date:yyyy-MM-dd)
DNS:                   $(dns:address|apache.org)
Environment Variable:  $(env:USERNAME)
File Content:          $(file:UTF-8:src/test/resources/document.properties)
Java:                  $(java:version)
Localhost:             $(localhost:canonical-name)
Properties File:       $(properties:src/test/resources/document.properties::mykey)
Resource Bundle:       $(resourceBundle:org.example.testResourceBundleLookup:mykey)
Script:                $(script:javascript:3 + 4)
System Property:       $(sys:user.dir)
URL Decoder:           $(urlDecoder:Hello%20World%21)
URL Encoder:           $(urlEncoder:Hello World!)
URL Content (HTTP):    $(url:UTF-8:http://www.apache.org)
URL Content (HTTPS):   $(url:UTF-8:https://www.apache.org)
URL Content (File):    $(url:UTF-8:file:///$(sys:user.dir)/src/test/resources/document.properties)
XML XPath:             $(xml:src/test/resources/document.xml:/root/path/to/node)

to replace the values with java system properties or environment variables. Recursive variable replacement like $(file:UTF-8:$(env:KEYCLOAK_PASSWORD_FILE)) is enabled by default if import.var-substitution.enabled is set to true.

The variable substitution is running before the json parser gets executed. This allows json structures or complex values.

See Apache Common StringSubstitutor documentation for more information and advanced usage.

Note: Since variable substitution is a part of the keycloak-config-cli, it's done locally. This means, the environment variables need to be available where keycloak-config-cli is executed.

If import.var-substitution.prefix=${ and import.var-substitution.suffix=} (default in keycloak-config-cli 3.x) is set, then keycloak builtin variables like ${role_uma_authorization} needs to be escaped by $${role_uma_authorization}.

Logging

JSON logging support

keycloak-config-cli supports logging in JSON format. To enable, set SPRING_PROFILES_ACTIVE=json-log.

Log level

CLI Option ENV Variable Description Default
--logging.level.root LOGGING_LEVEL_ROOT define the root log level info
--logging.level.keycloak-config-cli LOGGING_LEVEL_KEYCLOAKCONFIGCLI log level of keycloak-config-cli components value of logging.level.root
--logging.level.http LOGGING_LEVEL_HTTP log level http requests between keycloak-config-cli and Keycloak value of logging.level.root
--logging.level.realm-config LOGGING_LEVEL_REALMCONFIG if set to trace, the realm config including sensitive information will be logged value of logging.level.root

Supported features

See: docs/FEATURES.md

Compatibility with keycloak

Since keycloak-config-cli 4.0 will support the latest 4 releases of keycloak, if possible. There are some exceptions:

  • keycloak-config-cli will try the keep an extended support for RH-SSO
  • keycloak-config-cli will cut the support if keycloak introduces some breaking changes

Build this project

keycloak-config-cli using maven to build and test keycloak-config-cli. In case maven is not installed on your system, the mvnw command will download maven for you.

Further development requirements

  • Java Development Kit (JDK)
  • Docker Desktop or an alternative replacement (e.g Rancher Desktop)
./mvnw verify

# Windows only
mvnw.cmd verify

If your are working with a Docker Desktop replacement, some of the Integrationtests can fail due to internal DNS Lookups (host.docker.internal is not reachable). In this case the host can be replaced by a property.

mvn verify -DJUNIT_LDAP_HOST=an.alternate.host.or.ip

Run integration tests against real keycloak

We are using TestContainers in our integration tests. To run the integration tests a configured docker environment is required.

./mvnw verify

# Windows only
mvnw.cmd verify

Run this project

Start a local keycloak on port 8080:

docker-compose down --remove-orphans && docker-compose up keycloak

before performing following command:

java -jar ./target/keycloak-config-cli.jar \
    --keycloak.url=http://localhost:8080 \
    --keycloak.ssl-verify=true \
    --keycloak.user=admin \
    --keycloak.password=admin123 \
    --import.files.locations=./contrib/example-config/moped.json

Docker

A docker images is available at DockerHub (docker.io/adorsys/keycloak-config-cli) and quay.io (quay.io/adorsys/keycloak-config-cli)

Available docker tags

Tag Description
latest latest available release of keycloak-config-cli which is built against the latest supported Keycloak release.
latest-x.y.z latest available release of keycloak-config-cli which is built against the Keycloak version x.y.z.
edge latest commit on the main branch and which is built against the latest supported Keycloak release.
a.b.c keycloak-config-cli version a.b.c which is built against the latest supported Keycloak release.
a.b.c-x.y.z keycloak-config-cli version a.b.c which is built against the Keycloak version x.y.z.
maven See below

Additionally, the tag maven contains the source code and compile keycloak-config-cli at runtime. This has the advantage to keycloak-config-cli with Keycloak versions, that not official supported., e.g.:

docker run --rm -ti -v $PWD:/config/ -eKEYCLOAK_VERSION=23.0.1 -eMAVEN_CLI_OPTS="-B -ntp -q" adorsys/keycloak-config-cli:edge-build

Docker run

For docker -e you have to replace dots with underscores.

docker run \
    -e KEYCLOAK_URL="http://<your keycloak host>:8080/" \
    -e KEYCLOAK_USER="<keycloak admin username>" \
    -e KEYCLOAK_PASSWORD="<keycloak admin password>" \
    -e KEYCLOAK_AVAILABILITYCHECK_ENABLED=true \
    -e KEYCLOAK_AVAILABILITYCHECK_TIMEOUT=120s \
    -e IMPORT_FILES_LOCATIONS='/config/*' \
    -v <your config path>:/config \
    adorsys/keycloak-config-cli:latest

Docker build

You can build an own docker image by running

docker build -t keycloak-config-cli .

Helm

We provide a helm chart here.

Since it makes no sense to deploy keycloak-config-cli as standalone application, you could add it as dependency to your chart deployment.

Checkout helm docs about chart dependencies!

Configuration

CLI option / Environment Variables

Keycloak options

CLI Option ENV Variable Description Default Docs
--keycloak.url KEYCLOAK_URL Keycloak URL including web context. Format: scheme://hostname:port/web-context. -
--keycloak.user KEYCLOAK_USER login user name admin
--keycloak.password KEYCLOAK_PASSWORD login user password -
--keycloak.client-id KEYCLOAK_CLIENTID login clientId admin-cli
--keycloak.client-secret KEYCLOAK_CLIENTSECRET login client secret -
--keycloak.grant-type KEYCLOAK_GRANTTYPE login grant_type password
--keycloak.login-realm KEYCLOAK_LOGINREALM login realm master
--keycloak.ssl-verify KEYCLOAK_SSLVERIFY Verify ssl connection to keycloak true
--keycloak.http-proxy KEYCLOAK_HTTPPROXY Connect to Keycloak via HTTP Proxy. Format: scheme://hostname:port -
--keycloak.connect-timeout KEYCLOAK_CONNECTTIMEOUT Connection timeout 10s
--keycloak.read-timeout KEYCLOAK_READTIMEOUT Read timeout 10s configured as Java Duration
--keycloak.availability-check.enabled KEYCLOAK_AVAILABILITYCHECK_ENABLED Wait until Keycloak is available false configured as Java Duration
--keycloak.availability-check.timeout KEYCLOAK_AVAILABILITYCHECK_TIMEOUT Wait timeout for keycloak availability check 120s

Import options

CLI Option ENV Variable Description Default Docs
--import.validate IMPORT_VALIDATE Validate configuration settings false
--import.parallel IMPORT_PARALLEL Enable parallel import of certain resources false
--import.files.locations IMPORT_FILES_LOCATIONS Location of config files (URL, file path, or Ant-style pattern) - IMPORT.md
--import.files.include-hidden-files IMPORT_FILES_INCLUDE_HIDDEN_FILES Includes files that marked as hidden false
--import.files.excludes IMPORT_FILES_EXCLUDES Exclude files with Ant-style pattern -
--import.cache.enabled IMPORT_CACHE_ENABLED Enable caching of import file locations true
--import.cache.key IMPORT_CACHE_KEY Cache key for importing config. default
--import.remote-state.enabled IMPORT_REMOTESTATE_ENABLED Enable remote state management. Purge only resources managed by keycloak-config-cli. true MANAGED.md
--import.remote-state.encryption-key IMPORT_REMOTESTATE_ENCRYPTIONKEY Enables remote state in encrypted format. If unset, state will be stored in plain -
--import.var-substitution.enabled IMPORT_VARSUBSTITUTION_ENABLED Enable variable substitution config files false
--import.var-substitution.nested IMPORT_VARSUBSTITUTION_NESTED Expand variables in variables. true
--import.var-substitution.undefined-is-error IMPORT_VARSUBSTITUTION_UNDEFINEDISTERROR Raise exceptions, if variables are not defined. true
--import.var-substitution.prefix IMPORT_VARSUBSTITUTION_PREFIX Configure the variable prefix, if import.var-substitution.enabled is true. $(
--import.var-substitution.suffix IMPORT_VARSUBSTITUTION_SUFFIX Configure the variable suffix, if import.var-substitution.enabled is true. )
--import.behaviors.sync-user-federation IMPORT_BEHAVIORS_SYNC_USER_FEDERATION Enable the synchronization of user federation. false
--import.behaviors.remove-default-role-from-user IMPORT_BEHAVIORS_REMOVEDEFAULTROLEFROMUSER The default setting of this flag prevents keycloak-config-cli from removing default-roles-$REALM, even if its not defined in the import json. To make keycloak-config-cli able to remove the default-role-$REALM, import.remove-default-role-from-user must be set to true. In conclusion, you have to add the default-role-$REALM to the realm import on certain users, if you want not remove the default-role-$REALM. false
--import.behaviors.skip-attributes-for-federated-user IMPORT_BEHAVIORS_SKIP_ATTRIBUTESFORFEDERATEDUSER Set attributes to null for federated users to avoid read only conflicts false

Spring boot options

CLI Option ENV Variable Description Default Docs
--spring.profiles.active SPRING_PROFILES_ACTIVE enable spring profiles. comma separated - Set the Active Spring Profiles
--spring.config.import SPRING_CONFIG_IMPORT See below info Configure properties values through files
--logging.level.root LOGGING_LEVEL_ROOT define the root log level info Logging
--debug DEBUG enables debug mode of spring boot false

See application.properties for all available settings.

For docker -e you have to remove hyphens and replace dots with underscores.

Take a look at spring relax binding or binding from Environment Variables if you need alternative spellings.

Configure properties values through files

By define an environment variable SPRING_CONFIG_IMPORT=configtree:/run/secrets/, the values of properties can be provided via files instead of plain environment variable values.

Example: To configure the property keycloak.password in this case, the file should be in /run/secrets/keycloak.password.

The configuration and secret support in Docker Swarm is a perfect match for this use case.

Checkout the spring docs to get more information about the configuration trees feature in spring boot.

Perform release

Building releases requires gpg signing.

Example to create and add a key to yout git config on MacOS

brew install gnupg
gpg --version
gpg --full-generate-key
# follow instructions
gpg --list-keys
gpg --list-secret-keys --keyid-format=short
# check the 8 digit code eg "ssb   xxxxxxx/E51442F5 2022-01-01 [X]"
git config --global user.signingkey E51442F5

Finally add the key to your Github account under Settings -> SSH and GPG keys -> New GPG key

Create release via maven release plugin:

./mvnw -Dresume=false release:prepare release:clean
git push --follow-tags

Commercial support

Checkout https://adorsys.com/en/products/keycloak-config-cli/ for commercial support.

keycloak-config-cli's People

Contributors

antikalk avatar bertbaron avatar borisskert avatar breakbb avatar daniel-shuy avatar daviddavidgit avatar dependabot-preview[bot] avatar dependabot[bot] avatar francis-pouatcha avatar github-actions[bot] avatar henningwaack avatar jkroepke avatar jonasvoelcker avatar lme-atolcd avatar oh-tech avatar omega359 avatar pmorch avatar pvlbsk98 avatar razvan-tufisi avatar schmitzhermes avatar snyk-bot avatar spahrson avatar srose avatar st3v0rr avatar tbroyer avatar thomasdarimont avatar timadevelop avatar toilal avatar twiessner avatar woelkjulian avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

keycloak-config-cli's Issues

wrong user updated when creating user with username equal to identity provider alias or some other resolved data

I am using docker image adorsys/keycloak-config-cli:v0.8.0-7.0.0 and keycloak 7.0.0

This is the case:

I have an identity provider configured, which alias is example
I have user that is linked to this identityprovider, his username is linkeduser
Then I have a JSON file where I create a keycloak user whose name is example

The result is, that no user with the username example is created,
but linkeduser is updated so that he receives the e-mail, names and roles that were meant for the username example

How is the user matched from the realm when deciding for updating or creating?

...
2020-03-04 09:30:042020-03-04 08:30:04.709 DEBUG 177 --- [ main] d.a.k.config.service.UserImportService : Update user 'example' in realm 'my-realm'

JSON:

{
  "id": "my-realm",
  "realm": "my-realm",
  "users": [
    {
      "username": "example",
      "email": "[email protected]",
      "enabled": true,
      "firstName": "Example",
      "lastName": "User",
      "realmRoles": [
        "a_realm_role"
      ],
      "clientRoles": {
        "my-client": [
          "role_1",
          "role_2"
        ]
      },
      "credentials": [
        {
          "type": "password",
          "value": "password"
        }
      ]
    }

Update:

If i manually insert the user example and retry, I get
HTTP 409 Conflict:

07:48:49
2020-03-04 08:48:49.263 DEBUG 186 --- [ main] d.a.k.config.service.UserImportService : Update user 'example' in realm 'my-realm'
07:48:49
2020-03-04 08:48:49.745 INFO 186 --- [ main] ConditionEvaluationReportLoggingListener :
07:48:49
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
07:48:49
2020-03-04 08:48:49.750 ERROR 186 --- [ main] o.s.boot.SpringApplication : Application run failed
07:48:49
java.lang.IllegalStateException: Failed to execute CommandLineRunner
07:48:49
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:783) [spring-boot-2.1.7.RELEASE.jar!/:2.1.7.RELEASE]
07:48:49
at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:764) [spring-boot-2.1.7.RELEASE.jar!/:2.1.7.RELEASE]
07:48:49
at org.springframework.boot.SpringApplication.run(SpringApplication.java:319) [spring-boot-2.1.7.RELEASE.jar!/:2.1.7.RELEASE]
07:48:49
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1214) [spring-boot-2.1.7.RELEASE.jar!/:2.1.7.RELEASE]
๏…
07:48:49
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1203) [spring-boot-2.1.7.RELEASE.jar!/:2.1.7.RELEASE]
07:48:49
at de.adorsys.keycloak.config.Application.main(Application.java:9) [classes!/:0.8.0_2019-09-17 14:44:22]
07:48:49
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_222]
07:48:49
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_222]
07:48:49
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_222]
07:48:49
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_222]
07:48:49
at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48) [keycloak-config-cli.jar:0.8.0_2019-09-17 14:44:22]
07:48:49
at org.springframework.boot.loader.Launcher.launch(Launcher.java:87) [keycloak-config-cli.jar:0.8.0_2019-09-17 14:44:22]
07:48:49
at org.springframework.boot.loader.Launcher.launch(Launcher.java:51) [keycloak-config-cli.jar:0.8.0_2019-09-17 14:44:22]
07:48:49
at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:52) [keycloak-config-cli.jar:0.8.0_2019-09-17 14:44:22]
07:48:49
Caused by: javax.ws.rs.ClientErrorException: HTTP 409 Conflict
07:48:49
at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.handleErrorStatus(ClientInvocation.java:216) ~[resteasy-client-3.0.14.Final.jar!/:3.0.14.Final]
07:48:49
at org.jboss.resteasy.client.jaxrs.internal.proxy.extractors.DefaultEntityExtractorFactory$3.extractEntity(DefaultEntityExtractorFactory.java:50) ~[resteasy-client-3.0.14.Final.jar!/:3.0.14.Final]
07:48:49
at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invoke(ClientInvoker.java:104) ~[resteasy-client-3.0.14.Final.jar!/:3.0.14.Final]
07:48:49
at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientProxy.invoke(ClientProxy.java:64) ~[resteasy-client-3.0.14.Final.jar!/:3.0.14.Final]
07:48:49
at com.sun.proxy.$Proxy91.update(Unknown Source) ~[na:na]
07:48:49
at de.adorsys.keycloak.config.repository.UserRepository.updateUser(UserRepository.java:65) ~[classes!/:0.8.0_2019-09-17 14:44:22]
07:48:49
at de.adorsys.keycloak.config.service.UserImportService$UserImport.updateUser(UserImportService.java:72) ~[classes!/:0.8.0_2019-09-17 14:44:22]
07:48:49
at de.adorsys.keycloak.config.service.UserImportService$UserImport.importUser(UserImportService.java:57) ~[classes!/:0.8.0_2019-09-17 14:44:22]
07:48:49
at de.adorsys.keycloak.config.service.UserImportService.importUser(UserImportService.java:39) ~[classes!/:0.8.0_2019-09-17 14:44:22]
07:48:49
at de.adorsys.keycloak.config.service.RealmImportService.importUsers(RealmImportService.java:170) ~[classes!/:0.8.0_2019-09-17 14:44:22]
07:48:49
at de.adorsys.keycloak.config.service.RealmImportService.updateRealm(RealmImportService.java:151) ~[classes!/:0.8.0_2019-09-17 14:44:22]
07:48:49
at de.adorsys.keycloak.config.service.RealmImportService.updateRealmIfNecessary(RealmImportService.java:133) ~[classes!/:0.8.0_2019-09-17 14:44:22]
07:48:49
at de.adorsys.keycloak.config.service.RealmImportService.doImport(RealmImportService.java:104) ~[classes!/:0.8.0_2019-09-17 14:44:22]
07:48:49
at de.adorsys.keycloak.config.KeycloakImportRunner.run(KeycloakImportRunner.java:35) ~[classes!/:0.8.0_2019-09-17 14:44:22]
07:48:49
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:780) [spring-boot-2.1.7.RELEASE.jar!/:2.1.7.RELEASE]
07:48:49
... 13 common frames omitted

Manage state of subComponents

SubComponents like LDAPStorageMapper can have multiple group/attribute mappers.

keycloak-config-cll can create the mappers but existing mappers can't removed.

I can't import multiple realms

I am trying to build an automation for Keycloak in my development environment, but I keep failing to import multiple realms at once.

docker exec -it kc /opt/jboss/keycloak/bin/standalone.sh \
  -Djboss.socket.binding.port-offset=100 \
  -Dkeycloak.migration.action=export \
  -Dkeycloak.migration.provider=single file \
  -Dkeycloak.migration.file=/opt/tmp/realms.json

The above command always returns an array json.

[{"id": "Realm1", ...},
{"id": "Realm2", ...},
{"id": "Master", ...}]

However, this cannot be imported directly into Keycloak via a volume:

FATAL [org.keycloak.services] (ServerService Thread Pool -- 67) java.lang.RuntimeException: Failed to parse json

The documentations and discussions on the web didn't really help me, especially since the export that Keycloak created seems to be broken. If I only have one realm (eg. Realm1) config in the json, everything runs fine, but it's hard to automate it like this.

While searching for a solution I came across this project and hoped that it works. But again I get an error when I try to import all realms at once:

com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of 'de.adorsys.keycloak.config.model.RealmImport' out of START_ARRAY token at [Source: (File); line: 1, column: 1]

Somebody help me and tell me what I'm doing wrong.

Realm "Security Defenses" is cleaned up after import

Describe the bug
Running the import cleans my current security defenses configuration

To Reproduce

  • Create a realm "test-realm" from keycloak admin panel
  • Run the config with default flags and this simple configuration
{
    "realm": "test-realm",
    "enabled": true
}

Expected behavior
Security defenses should be kept with the keycloak default values

Environment (please complete the following information):

  • Keycloak Version: 9.0.3
  • keycloak-config-cli Version: v2.2.0-9.0.3
  • Java Version: Docker image v2.2.0-9.0.3

Please update the docker images

Could you please update also the docker images with 1.0? That would let me use it in our import routines.

Thanks in advance

Adding a caching option to keycloak-config-cli

Just an idea or concept.

A cache mode would be a good improvement in dev environment to reduce the deploy/configure times.
As I know Keycloak support custom realm attributes. (https://github.com/keycloak/keycloak/pull/3153/files)

A idea to cache the configuration:

  • Create a hash (e.g. sha256) for the import json file.
  • Create a custom attribute inside the realm with calculated hash
  • On next run the utility should recalculate the hash if the json with in saved attribute and if the hash is the same skip the whole reconfiguration process.

This behavior should be an opt-in mechanism.

Support for external RealmRepresentation

Is your feature request related to a problem? Please describe.
In some bigger projects Keycloak is customized using extensions. This produces a RealmRepresentation that is inconsistent with the "Vanilla code" ingested by keycloak-config-cli, which in turn produces import errors.

Describe the solution you'd like
It would greatly help to be able to ingest the RealmRepresentation from a jar file during start up, thereby enabling all custom elements in the RealmRepresentation. It would be great to specify an external JAR (the server side keycloak-admin-client.jar) as a project parameter, which maven would then pick up und the contents of that JAR would be used as source for the data model instead of the Vanilla ones.

Describe alternatives you've considered
The provided CustomImportService performs a similar task, however, all imports would have to be hard coded, making it a not so nice solution imho.

ENV and Java Props substitution while reading file

It's just an idea. Vote: ๐Ÿ‘ if you think this feature is useful. Then I'll investigative into it.

Is your feature request related to a problem? Please describe.
Handling multiple environments needs multiple json files each environment

Describe the solution you'd like
keycloak-config-cli should support a substitution of the json files.

Describe alternatives you've considered
multiple json files each environment

Additional context
FasterXML/jackson-dataformats-text#28

I would like to use a native/jackson way instead a new dependency if possible.

Guarantee order of import files?

There's no guarantee that KeycloakImport json files are executed in order?

Importing file '/config/03_init.json'
Importing file '/config/02_update.json'
Importing file '/config/01_create.json'

Maybe the imports could be sorted so that: 01.json is run before 02.json etc?

scopeMappings of clients are not imported correctly, when the corresponding realm already exists

keycloak.version:
keycloak_server_info

config-cli pom:
config-cli-pom

Hey,

I have an issue when importing scopeMapping changes to realms that are already existent.

Steps to recreate issue:

  1. Run keycloak via export.single-realm-files.docker-compose.yml

  2. Import the attached "moped-scope-mappings.json" with the config-cli, where I just added a new client (client-scope-test), a new role (client-scope-test) and a scopeMapping to connect both

moped-scope-mappings.json.zip

  1. Check the keycloak Admin GUI for the new client and its scope mappings, the import worked nicely as expected. see attached figure

scope_mapping

  1. now remove the scope mapping within the Admin GUI, see attached figure

removed_scope_mapping

  1. now reimport the moped-scope-mappings.json with the config-cli

  2. I would expect, that the scope mapping will be imported again and that the role is assigned correctly to the client, but it isn`t.

Note: It doesnt matter if the client already exists or not. The scope mappings only got imported correctly when the realm didnt exist. the parameter --import.force=true didnt change anything for me

Allow full override of keycloak_url i.e KEYCLOAK_FRONTEND_URL

You assume Keycloak is always listening at /auth endpoint i.e. keycloak.url + /auth.

Please let the user override this "/auth".

Frontend request do not have to have the same context-path as the Keycloak server. This means you can expose Keycloak on for example https://auth.example.org or https://example.org/keycloak while internally its URL could be https://10.0.0.10:8080/auth.

I have needed to set my own standalone-ha.xml with web-context auth changed.

Cannot Create Realm Unrecognized field"clientOfflineSessionIdleTimeout"

Upgraded to latest 2.3.0 release (from 1.4.0) and the keycloak import now fails with the message..
Cannot create realm 'xxxxxxxxxxt': Unrecognized field "clientOfflineSessionIdleTimeout" (class org.keycloak.representations.idm.RealmRepresentation), not marked as ignorable
This is against Keycloak 10.0.2.
Am I doing something wrong?

Authentication Flows with nested flows with multiple executions cannot be imported

Describe the bug
An authentication flow with nested flows with more than 1 execution cannot be imported

To Reproduce
pending

Expected behavior
Flow can be successfully imported

Environment (please complete the following information):

  • Keycloak Version: 8.0.2, 9.0.3, 10.0.2, 11.0.2
  • keycloak-config-cli Version: 2.5.0, 2.5.1-SNAPSHOT
  • Java Version: 8

Fail: Update authentication flow which is currently in use

Error log:
10:07:42,194 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (default task-1) Uncaught server error: org.keycloak.models.ModelException: Cannot remove authentication flow, it is currently in use at org.keycloak.models.jpa.RealmAdapter.removeAuthenticationFlow(RealmAdapter.java:1474) at org.keycloak.models.cache.infinispan.RealmAdapter.removeAuthenticationFlow(RealmAdapter.java:1144) at org.keycloak.services.resources.admin.AuthenticationManagementResource.deleteFlow(AuthenticationManagementResource.java:300) at org.keycloak.services.resources.admin.AuthenticationManagementResource.deleteFlow(AuthenticationManagementResource.java:282) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:140) at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:510) at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:401) at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$0(ResourceMethodInvoker.java:365) at org.jboss.resteasy.core.interception.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:361) at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:367) at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:339) at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:137) at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:106) at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:132) at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:106) at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:132) at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:100) at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:441) at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:231) at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:137) at org.jboss.resteasy.core.interception.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:361) at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:140) at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:217) at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:227) at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56) at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:51) at javax.servlet.http.HttpServlet.service(HttpServlet.java:790) at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74) at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129) at org.keycloak.services.filters.KeycloakSessionServletFilter.doFilter(KeycloakSessionServletFilter.java:90) at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84) at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62) at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68) at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36) at org.wildfly.extension.undertow.security.SecurityContextAssociationHandler.handleRequest(SecurityContextAssociationHandler.java:78) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132) at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46) at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64) at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60) at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77) at io.undertow.security.handlers.NotificationReceiverHandler.handleRequest(NotificationReceiverHandler.java:50) at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at org.wildfly.extension.undertow.deployment.GlobalRequestControllerHandler.handleRequest(GlobalRequestControllerHandler.java:68) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:292) at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:81) at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:138) at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135) at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48) at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43) at org.wildfly.extension.undertow.security.SecurityContextThreadSetupAction.lambda$create$0(SecurityContextThreadSetupAction.java:105) at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1514) at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1514) at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1514) at org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1514) at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:272) at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:81) at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:104) at io.undertow.server.Connectors.executeRootHandler(Connectors.java:360) at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830) at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35) at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1985) at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1487) at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1378) at java.lang.Thread.run(Thread.java:748)
Solution: use another flow temporary while updating an authentication flow

is it possible to update a built-in flow?

Hi and thanks for your great tool.

Sorry if this is written somewhere, but couldn't find help by searching so:

I am trying to update the built-in first broker flow automatically, example:

{
      "alias": "first broker login",
      "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
      "providerId": "basic-flow",
      "topLevel": true,
      "builtIn": true,
      "authenticationExecutions": [
        {
          "authenticatorConfig": "review profile config",
          "authenticator": "idp-review-profile",
          "requirement": "DISABLED",
          "priority": 10,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "authenticatorConfig": "create unique user config",
          "authenticator": "idp-create-user-if-unique",
          "requirement": "ALTERNATIVE",
          "priority": 20,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "requirement": "ALTERNATIVE",
          "priority": 30,
          "flowAlias": "Handle Existing Account",
          "userSetupAllowed": false,
          "autheticatorFlow": true
        }
      ]
    }

But I get an obvious exception:

Caused by: de.adorsys.keycloak.config.exception.InvalidImportException: Unable to recreate flow 'first broker login' in realm 'realmname': Deletion or creation of built-in flows is not possible

Question: Is it possible to run a script as update on startup?

Importing a realm with changes to authorization settings in realm-management client results in Internal Server Error

Summary: When using config-cli to create a realm which has modifications to the default authorization settings in the realm-management client there is an Internal Server Error. Using the same json file through the Admin Console works as expected.

Environment
Keycloak version: 11.0.2 (running on Docker 19.03.11 with the given docker-compose.yml, docker-compose version 1.25.4)
config-cli version: v2.5.0

Here is a simplified version of the import file. simple-realm_update-realm-management-client.json:

{
  "enabled": true,
  "realm": "simple",
  "clients": [
    {
      "clientId": "esb-token-exchange",
      "surrogateAuthRequired": false,
      "enabled": true,
      "alwaysDisplayInConsole": false,
      "clientAuthenticatorType": "client-secret",
      "secret": "esb-token-exchage-secret",
      "redirectUris": [],
      "webOrigins": [],
      "notBefore": 0,
      "bearerOnly": false,
      "consentRequired": false,
      "standardFlowEnabled": true,
      "implicitFlowEnabled": false,
      "directAccessGrantsEnabled": true,
      "serviceAccountsEnabled": false,
      "publicClient": false,
      "frontchannelLogout": false,
      "protocol": "openid-connect",
      "attributes": {
        "saml.assertion.signature": "false",
        "saml.force.post.binding": "false",
        "saml.multivalued.roles": "false",
        "saml.encrypt": "false",
        "saml.server.signature": "false",
        "saml.server.signature.keyinfo.ext": "false",
        "exclude.session.state.from.auth.response": "false",
        "saml_force_name_id_format": "false",
        "saml.client.signature": "false",
        "tls.client.certificate.bound.access.tokens": "false",
        "saml.authnstatement": "false",
        "display.on.consent.screen": "false",
        "saml.onetimeuse.condition": "false"
      },
      "authenticationFlowBindingOverrides": {},
      "fullScopeAllowed": true,
      "nodeReRegistrationTimeout": -1,
      "defaultClientScopes": [
        "web-origins",
        "role_list",
        "profile",
        "roles",
        "email"
      ],
      "optionalClientScopes": [
        "address",
        "phone",
        "offline_access",
        "microprofile-jwt"
      ]
    },
    {
      "clientId": "realm-management",
      "name": "${client_realm-management}",
      "enabled": true,
      "clientAuthenticatorType": "client-secret",
      "secret": "my-special-client-secret",
      "redirectUris": [],
      "webOrigins": [],
      "authorizationSettings": {
        "allowRemoteResourceManagement": false,
        "policyEnforcementMode": "ENFORCING",
        "resources": [
          {
            "name": "client.resource",
            "type": "Client",
            "ownerManagedAccess": false,
            "attributes": {},
            "uris": [],
            "scopes": [
              {
                "name": "view"
              },
              {
                "name": "map-roles-client-scope"
              },
              {
                "name": "configure"
              },
              {
                "name": "map-roles"
              },
              {
                "name": "manage"
              },
              {
                "name": "token-exchange"
              },
              {
                "name": "map-roles-composite"
              }
            ]
          }
        ],
        "policies": [
          {
            "name": "esb-token-exchange",
            "type": "client",
            "logic": "POSITIVE",
            "decisionStrategy": "UNANIMOUS",
            "config": {
              "clients": "[\"esb-token-exchange\"]"
            }
          },
          {
            "name": "manage.permission.client",
            "type": "scope",
            "logic": "POSITIVE",
            "decisionStrategy": "UNANIMOUS",
            "config": {
              "resources": "[\"client.resource\"]",
              "scopes": "[\"manage\"]"
            }
          },
          {
            "name": "configure.permission.client",
            "type": "scope",
            "logic": "POSITIVE",
            "decisionStrategy": "UNANIMOUS",
            "config": {
              "resources": "[\"client.resource\"]",
              "scopes": "[\"configure\"]"
            }
          },
          {
            "name": "view.permission.client",
            "type": "scope",
            "logic": "POSITIVE",
            "decisionStrategy": "UNANIMOUS",
            "config": {
              "resources": "[\"client.resource\"]",
              "scopes": "[\"view\"]"
            }
          },
          {
            "name": "map-roles.permission.client",
            "type": "scope",
            "logic": "POSITIVE",
            "decisionStrategy": "UNANIMOUS",
            "config": {
              "resources": "[\"client.resource\"]",
              "scopes": "[\"map-roles\"]"
            }
          },
          {
            "name": "map-roles-client-scope.permission.client",
            "type": "scope",
            "logic": "POSITIVE",
            "decisionStrategy": "UNANIMOUS",
            "config": {
              "resources": "[\"client.resource\"]",
              "scopes": "[\"map-roles-client-scope\"]"
            }
          },
          {
            "name": "map-roles-composite.permission.client",
            "type": "scope",
            "logic": "POSITIVE",
            "decisionStrategy": "UNANIMOUS",
            "config": {
              "resources": "[\"client.resource\"]",
              "scopes": "[\"map-roles-composite\"]"
            }
          },
          {
            "name": "token-exchange.permission.client",
            "type": "scope",
            "logic": "POSITIVE",
            "decisionStrategy": "UNANIMOUS",
            "config": {
              "resources": "[\"client.resource\"]",
              "scopes": "[\"token-exchange\"]",
              "applyPolicies": "[\"esb-token-exchange\"]"
            }
          }
        ],
        "scopes": [
          {
            "name": "manage"
          },
          {
            "name": "view"
          },
          {
            "name": "map-roles"
          },
          {
            "name": "map-roles-client-scope"
          },
          {
            "name": "map-roles-composite"
          },
          {
            "name": "configure"
          },
          {
            "name": "token-exchange"
          }
        ],
        "decisionStrategy": "UNANIMOUS"
      }
    }
  ]
}

Importing this json file to Keycloak using the Admin Console when creating the realm is successful. However, when trying to import it using the config-cli we get the following error.

command for config-cli:

java -jar ./target/keycloak-config-cli.jar --keycloak.url=http://localhost:8080 --keycloak.user=admin --keycloak.password=admin123 --import.path=./simple-realm_update-realm-management-client.json

output:

2020-10-22 15:11:44.903  INFO 9763 --- [           main] d.a.k.config.KeycloakConfigApplication   : Starting KeycloakConfigApplication v2.5.1-SNAPSHOT on h50lag0 with PID 9763 (/home/ito/Projects/ito/keycloak-config-cli/target/keycloak-config-cli.jar started by ito in /home/ito/Projects/ito/keycloak-config-cli)
2020-10-22 15:11:44.907  INFO 9763 --- [           main] d.a.k.config.KeycloakConfigApplication   : No active profile set, falling back to default profiles: default
2020-10-22 15:11:45.720  INFO 9763 --- [           main] d.a.k.config.KeycloakConfigApplication   : Started KeycloakConfigApplication in 1.355 seconds (JVM running for 1.89)
2020-10-22 15:11:46.118  INFO 9763 --- [           main] d.a.k.c.provider.KeycloakImportProvider  : Importing file '/home/ito/Projects/ito/keycloak-config-cli/./simple-realm_update-realm-management-client.json'
2020-10-22 15:11:47.041  WARN 9763 --- [           main] d.a.k.config.provider.KeycloakProvider   : DEPRECATION: Omit /auth/ at server url is deprecated!
2020-10-22 15:11:47.880 ERROR 9763 --- [           main] d.a.k.config.KeycloakConfigRunner        : HTTP 500 Internal Server Error
2020-10-22 15:11:47.880  INFO 9763 --- [           main] d.a.k.config.KeycloakConfigRunner        : keycloak-config-cli running in 00:01.789.

And the Keycloak server logs:

13:11:47,878 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (default task-9) Uncaught server error: javax.ws.rs.InternalServerErrorException: HTTP 500 Internal Server Error
       at [email protected]//org.keycloak.headers.DefaultSecurityHeadersProvider.addHeaders(DefaultSecurityHeadersProvider.java:75)
       at [email protected]//org.keycloak.services.filters.KeycloakSecurityHeadersFilter.filter(KeycloakSecurityHeadersFilter.java:39)
       at [email protected]//org.jboss.resteasy.core.interception.ContainerResponseContextImpl.filter(ContainerResponseContextImpl.java:357)
       at [email protected]//org.jboss.resteasy.core.ServerResponseWriter.executeFilters(ServerResponseWriter.java:219)
       at [email protected]//org.jboss.resteasy.core.ServerResponseWriter.writeNomapResponse(ServerResponseWriter.java:95)
       at [email protected]//org.jboss.resteasy.core.ServerResponseWriter.writeNomapResponse(ServerResponseWriter.java:69)
       at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.writeResponse(SynchronousDispatcher.java:530)
       at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:461)
       at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:229)
       at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:135)
       at [email protected]//org.jboss.resteasy.core.interception.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:358)
       at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:138)
       at [email protected]//org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:215)
       at [email protected]//org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:245)
       at [email protected]//org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:61)
       at [email protected]//org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56)
       at [email protected]//javax.servlet.http.HttpServlet.service(HttpServlet.java:590)
       at [email protected]//io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
       at [email protected]//io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)
       at [email protected]//org.keycloak.provider.wildfly.WildFlyRequestFilter.lambda$doFilter$0(WildFlyRequestFilter.java:41)
       at [email protected]//org.keycloak.services.filters.AbstractRequestFilter.filter(AbstractRequestFilter.java:43)
       at [email protected]//org.keycloak.provider.wildfly.WildFlyRequestFilter.doFilter(WildFlyRequestFilter.java:39)
       at [email protected]//io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
       at [email protected]//io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
       at [email protected]//io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)
       at [email protected]//io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
       at [email protected]//io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
       at [email protected]//io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
       at [email protected]//org.wildfly.extension.undertow.security.SecurityContextAssociationHandler.handleRequest(SecurityContextAssociationHandler.java:78)
       at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
       at [email protected]//io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68)
       at [email protected]//io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132)
       at [email protected]//io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
       at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
       at [email protected]//io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
       at [email protected]//io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
       at [email protected]//io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
       at [email protected]//io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
       at [email protected]//io.undertow.security.handlers.NotificationReceiverHandler.handleRequest(NotificationReceiverHandler.java:50)
       at [email protected]//io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
       at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
       at [email protected]//org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61)
       at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
       at [email protected]//org.wildfly.extension.undertow.deployment.GlobalRequestControllerHandler.handleRequest(GlobalRequestControllerHandler.java:68)
       at [email protected]//io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
       at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:269)
       at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:78)
       at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:133)
       at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:130)
       at [email protected]//io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
       at [email protected]//io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
       at [email protected]//org.wildfly.extension.undertow.security.SecurityContextThreadSetupAction.lambda$create$0(SecurityContextThreadSetupAction.java:105)
       at [email protected]//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1530)
       at [email protected]//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1530)
       at [email protected]//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1530)
       at [email protected]//org.wildfly.extension.undertow.deployment.UndertowDeploymentInfoService$UndertowThreadSetupAction.lambda$create$0(UndertowDeploymentInfoService.java:1530)
       at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:249)
       at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:78)
       at [email protected]//io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:99)
       at [email protected]//io.undertow.server.Connectors.executeRootHandler(Connectors.java:370)
       at [email protected]//io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830)
       at [email protected]//org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
       at [email protected]//org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1982)
       at [email protected]//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1486)
       at [email protected]//org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1377)
       at java.base/java.lang.Thread.run(Thread.java:834)

Note: Removing the authorizationSettings part (lines 64-205) results in a file that can be imported with config-cli without any problems.

Implement keycloak availability check inside java

Is your feature request related to a problem? Please describe.
The current keycloak availability check is written in shell. shell can't relay on functions like reading from properties or spring relax binding. shell can only read from predefined environment variables. This leads to projects like #132

Describe the solution you'd like
Implement keycloak availability check inside the java application

Describe alternatives you've considered
None

Additional context
Add any other context or screenshots about the feature request here. If possible provide full json import files that can be used for tests.

Import of realm with client scopes referencing clients let's keycloak fail with Exception

Describe the bug
When importing a realm with client scopes referencing clients KeyCloak goes throws an exception:
ERROR [org.keycloak.services.error.KeycloakErrorHandler] (default task-48) Uncaught server error: java.lang.RuntimeException: Unknown client specification in scope mappings: redacted-client This can be circumvented by adding clientScopeMappings to the ignoredPropertiesForRealmImport array.

To Reproduce
Unfortunately I cannot share the keycloak config for reproduction, but I will try to produce a simple repro scenario.

Expected behavior
No exception in Keycloak visible.

Environment (please complete the following information):

  • Keycloak Version: 9.0.0
  • keycloak-config-cli Version: 2.4.0
  • Java Version: 8

Additional context
Add any other context about the problem here.

Helm chart configs referencing "file" keyword fail

When providing configuration via .Values.config where a realm is configured via the file keyword (as opposed to inline), the chart fails to render.

To reproduce, specify a value for config with the file keyword as follows, and then attempt to helm install | template ... the chart:

config:
  myrealm:
    file: path/to/myrealm.json

This will produce an error similar to the following:

 executing "templates/realms.yaml" at <.Files.Get>: nil pointer evaluating interface {}.Get

This is likely due to .Files.Get not being in context while within {{- range $name, $config := .Values.config }}, and simply needs to be changed to $.Files.Get.

No proxy support

Describe the bug
The KeycloakProvider does not heed the proxy settings. This disables tools like fiddler.

To Reproduce

  1. Install fiddler and setup to capture/decode HTTPS traffic.
  2. Start keycloak-config-cli with -DproxySet=true -DproxyHost=127.0.0.1 -DproxyPort=8888
    Result: fiddler not able to capture traffic as Keycloak class creates RestEasy client without proxy support

Expected behavior
Traffic to Keycloak should be visible in Fiddler

Environment (please complete the following information):

  • Keycloak Version: [e.g. 9.0.0]
  • keycloak-config-cli Version: [1.4.0]
  • Java Version: [e.g. 8]

Additional context
The problem appears to be caused by the Keycloak implementation, which does not create the RestEasy client with proper proxy handling. A possible workaround is to use Reflection to force it like below:

public class KeycloakWithProxy {
    private static final Logger logger = LoggerFactory.getLogger(KeycloakImportProvider.class);
    public static Keycloak getInstance(String serverUrl, String realm, String username, String password, String clientId, String clientSecret, SSLContext sslContext, ResteasyJackson2Provider customJacksonProvider, boolean disableTrustManager, String authToken)  {
        Keycloak keycloak = null;
        try {
            Constructor<?>[] constructors = Keycloak.class.getDeclaredConstructors();
            for (Constructor<?> constructor : constructors) {
                logger.info(constructor.toGenericString());
                constructor.setAccessible(true);
                try {
                    keycloak = (Keycloak) constructor.newInstance(serverUrl, realm, username, password, clientId, clientSecret, "password", newRestEasyClient(customJacksonProvider, sslContext, disableTrustManager), authToken);
                } catch (Exception e)
                {
                    logger.error(e.getMessage());
                    continue;
                }
                break;
            }
        }
        catch (Exception e)
        {
            logger.error(e.getMessage());
        }
        return keycloak;
    }

    private static ResteasyClient newRestEasyClient(ResteasyJackson2Provider customJacksonProvider, SSLContext sslContext, boolean disableTrustManager) {
        ResteasyClientBuilder clientBuilder = (new ResteasyClientBuilder()).sslContext(sslContext).connectionPoolSize(10);
        if (disableTrustManager) {
            clientBuilder.disableTrustManager().hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY);
        }

        if (customJacksonProvider != null) {
            clientBuilder.register(customJacksonProvider, 100);
        }

        clientBuilder.defaultProxy("127.0.0.1", 8888, "http");

        return clientBuilder.build();
    }
}

Implement --dry-run

Is your feature request related to a problem? Please describe.
Implement a --dry-run that shows what would happens.

Describe the solution you'd like
Implement a --dry-run switch that shows what would be create, update or delete by keycloak-config-cli.

Describe alternatives you've considered
Try and destroy the own keycloak instance

Additional context

Variable substitution not working?

Describe the bug

Thank you for the great tool!

I'm trying to use variable substitution to script out a realm.json file for automated deployments, but the CLI doesn't substitute environment variables at runtime, submitting values to Keycloak like "${env:SOME_ENV_VARIABLE}". There aren't any warnings or errors, and the import succeeds. I can view the new realm in the Keycloak interface. I'm running Docker locally on Mac OS. I'm passing import_var-substitution=true to the docker run command.

I should note that passing other flags, like keycloak_availability-check_enabled=true and keycloak_availability-check_timeout=120s seems to have no effect. When I run docker-compose up, the CLI will fail after the first HTTP request fails, without retrying or waiting. Am I misunderstanding how to use the CLI?

To Reproduce

Run CLI via Docker:

docker run \
    -e keycloak_url=http://docker.for.mac.localhost:8080 \
    -e keycloak_user=admin \
    -e keycloak_password=admin \
    -e IMPORT_PATH=/realms/my-realm.json \
    -e import_var-substitution=true \
    -e API_CLIENT_SECRET=somesecret \
    -v /Users/me/Projects/keycloak-example/src/realms:/realms \
    adorsys/keycloak-config-cli:master

realm.json (some detail omitted):

{
  "id": "app",
  "realm": "My App",
  "enabled": true,
  "clients": [
    {
      "clientId": "api",
      "name": "API",
      "enabled": true,
      "clientAuthenticatorType": "client-secret",
      "secret": "${env:API_CLIENT_SECRET}",
      "webOrigins": ["+"],
      "standardFlowEnabled": true,
      "implicitFlowEnabled": false,
      "serviceAccountsEnabled": true
    }
  ]
}

Expected behavior

I would expect to the see the value specified for API_CLIENT_SECRET to be injected into Keycloak.

Environment (please complete the following information):

  • Keycloak Version: 11.0.2
  • keycloak-config-cli Version: I've tried both master and latest

Client Protocol Mapper config not being updated

After creating my client with a Mapper, I can't seem to update it again.
Specifically i'm trying to update the multivalued property to true in the protocolMapper config. I don't see any errors - it's just that the value is still not enabled in the UI. Here is a snapshot of the JSON file being uploaded...

"protocolMappers": [
        {
          "name": "BranchCodeMapper",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-attribute-mapper",
          "consentRequired": false,
          "config": {
            "aggregate.attrs": "false",
            "userinfo.token.claim": "true",
            "multivalued": "true",
            "user.attribute": "branch",
            "id.token.claim": "false",
            "access.token.claim": "true",
            "claim.name": "branch",
            "jsonType.label": "String"
          }
        }

Error when importing Realm with identityProviderMapper

Describe the bug
We have an in-house Mattermost App whose authentication goes through Kerberos (SSO) in another realm (central realm). When exporting the mattermost realm, deleting it and importing it using the config-cli (without any changes) we get a 404 Error message. Importing the realm through the Admin Console doesn't show any errors.

To Reproduce
I have not been able to create a minimal example to reproduce it, so here is the 'not-so-minimal' reproducible json file:

{
  "id": "mattermost",
  "realm": "mattermost",
  "notBefore": 0,
  "revokeRefreshToken": false,
  "refreshTokenMaxReuse": 0,
  "accessTokenLifespan": 300,
  "accessTokenLifespanForImplicitFlow": 900,
  "ssoSessionIdleTimeout": 1800,
  "ssoSessionMaxLifespan": 36000,
  "ssoSessionIdleTimeoutRememberMe": 0,
  "ssoSessionMaxLifespanRememberMe": 0,
  "offlineSessionIdleTimeout": 2592000,
  "offlineSessionMaxLifespanEnabled": false,
  "offlineSessionMaxLifespan": 5184000,
  "accessCodeLifespan": 60,
  "accessCodeLifespanUserAction": 300,
  "accessCodeLifespanLogin": 1800,
  "actionTokenGeneratedByAdminLifespan": 43200,
  "actionTokenGeneratedByUserLifespan": 300,
  "enabled": true,
  "sslRequired": "external",
  "registrationAllowed": false,
  "registrationEmailAsUsername": false,
  "rememberMe": false,
  "verifyEmail": false,
  "loginWithEmailAllowed": true,
  "duplicateEmailsAllowed": false,
  "resetPasswordAllowed": false,
  "editUsernameAllowed": false,
  "bruteForceProtected": false,
  "permanentLockout": false,
  "maxFailureWaitSeconds": 900,
  "minimumQuickLoginWaitSeconds": 60,
  "waitIncrementSeconds": 60,
  "quickLoginCheckMilliSeconds": 1000,
  "maxDeltaTimeSeconds": 43200,
  "failureFactor": 30,
  "defaultRoles": [
    "offline_access",
    "uma_authorization"
  ],
  "requiredCredentials": [
    "password"
  ],
  "otpPolicyType": "totp",
  "otpPolicyAlgorithm": "HmacSHA1",
  "otpPolicyInitialCounter": 0,
  "otpPolicyDigits": 6,
  "otpPolicyLookAheadWindow": 1,
  "otpPolicyPeriod": 30,
  "otpSupportedApplications": [
    "FreeOTP",
    "Google Authenticator"
  ],
  "webAuthnPolicyRpEntityName": "keycloak",
  "webAuthnPolicySignatureAlgorithms": [
    "ES256"
  ],
  "webAuthnPolicyRpId": "",
  "webAuthnPolicyAttestationConveyancePreference": "not specified",
  "webAuthnPolicyAuthenticatorAttachment": "not specified",
  "webAuthnPolicyRequireResidentKey": "not specified",
  "webAuthnPolicyUserVerificationRequirement": "not specified",
  "webAuthnPolicyCreateTimeout": 0,
  "webAuthnPolicyAvoidSameAuthenticatorRegister": false,
  "webAuthnPolicyAcceptableAaguids": [],
  "scopeMappings": [
    {
      "clientScope": "offline_access",
      "roles": [
        "offline_access"
      ]
    }
  ],
  "clients": [
    {
      "id": "c0ce9f0b-2aad-4a67-817e-e7717951edcd",
      "clientId": "account",
      "name": "${client_account}",
      "rootUrl": "${authBaseUrl}",
      "baseUrl": "/realms/mattermost/account/",
      "surrogateAuthRequired": false,
      "enabled": true,
      "clientAuthenticatorType": "client-secret",
      "secret": "**********",
      "defaultRoles": [
        "view-profile",
        "manage-account"
      ],
      "redirectUris": [
        "/realms/mattermost/account/*"
      ],
      "webOrigins": [],
      "notBefore": 0,
      "bearerOnly": false,
      "consentRequired": false,
      "standardFlowEnabled": true,
      "implicitFlowEnabled": false,
      "directAccessGrantsEnabled": false,
      "serviceAccountsEnabled": false,
      "publicClient": false,
      "frontchannelLogout": false,
      "protocol": "openid-connect",
      "attributes": {},
      "authenticationFlowBindingOverrides": {},
      "fullScopeAllowed": false,
      "nodeReRegistrationTimeout": 0,
      "defaultClientScopes": [
        "web-origins",
        "role_list",
        "profile",
        "roles",
        "email"
      ],
      "optionalClientScopes": [
        "address",
        "phone",
        "offline_access",
        "microprofile-jwt"
      ]
    },
    {
      "id": "819b253f-f470-478f-8e23-924d2b6be3a7",
      "clientId": "security-admin-console",
      "name": "${client_security-admin-console}",
      "rootUrl": "${authAdminUrl}",
      "baseUrl": "/admin/mattermost/console/",
      "surrogateAuthRequired": false,
      "enabled": true,
      "clientAuthenticatorType": "client-secret",
      "secret": "**********",
      "redirectUris": [
        "/admin/mattermost/console/*"
      ],
      "webOrigins": [
        "+"
      ],
      "notBefore": 0,
      "bearerOnly": false,
      "consentRequired": false,
      "standardFlowEnabled": true,
      "implicitFlowEnabled": false,
      "directAccessGrantsEnabled": false,
      "serviceAccountsEnabled": false,
      "publicClient": true,
      "frontchannelLogout": false,
      "protocol": "openid-connect",
      "attributes": {},
      "authenticationFlowBindingOverrides": {},
      "fullScopeAllowed": false,
      "nodeReRegistrationTimeout": 0,
      "protocolMappers": [
        {
          "id": "eb0bce71-bee9-4da4-9472-0d3bd9f0da73",
          "name": "locale",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-attribute-mapper",
          "consentRequired": false,
          "config": {
            "userinfo.token.claim": "true",
            "user.attribute": "locale",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "locale",
            "jsonType.label": "String"
          }
        }
      ],
      "defaultClientScopes": [
        "web-origins",
        "role_list",
        "profile",
        "roles",
        "email"
      ],
      "optionalClientScopes": [
        "address",
        "phone",
        "offline_access",
        "microprofile-jwt"
      ]
    },
    {
      "id": "b37c6c12-bcce-4db7-a323-51e585551edf",
      "clientId": "admin-cli",
      "name": "${client_admin-cli}",
      "surrogateAuthRequired": false,
      "enabled": true,
      "clientAuthenticatorType": "client-secret",
      "secret": "**********",
      "redirectUris": [],
      "webOrigins": [],
      "notBefore": 0,
      "bearerOnly": false,
      "consentRequired": false,
      "standardFlowEnabled": false,
      "implicitFlowEnabled": false,
      "directAccessGrantsEnabled": true,
      "serviceAccountsEnabled": false,
      "publicClient": true,
      "frontchannelLogout": false,
      "protocol": "openid-connect",
      "attributes": {},
      "authenticationFlowBindingOverrides": {},
      "fullScopeAllowed": false,
      "nodeReRegistrationTimeout": 0,
      "defaultClientScopes": [
        "web-origins",
        "role_list",
        "profile",
        "roles",
        "email"
      ],
      "optionalClientScopes": [
        "address",
        "phone",
        "offline_access",
        "microprofile-jwt"
      ]
    },
    {
      "id": "45d1da0c-aab3-479f-9ad9-c4bb372856be",
      "clientId": "realm-management",
      "name": "${client_realm-management}",
      "surrogateAuthRequired": false,
      "enabled": true,
      "clientAuthenticatorType": "client-secret",
      "secret": "**********",
      "redirectUris": [],
      "webOrigins": [],
      "notBefore": 0,
      "bearerOnly": true,
      "consentRequired": false,
      "standardFlowEnabled": true,
      "implicitFlowEnabled": false,
      "directAccessGrantsEnabled": false,
      "serviceAccountsEnabled": false,
      "publicClient": false,
      "frontchannelLogout": false,
      "protocol": "openid-connect",
      "attributes": {},
      "authenticationFlowBindingOverrides": {},
      "fullScopeAllowed": false,
      "nodeReRegistrationTimeout": 0,
      "defaultClientScopes": [
        "web-origins",
        "role_list",
        "profile",
        "roles",
        "email"
      ],
      "optionalClientScopes": [
        "address",
        "phone",
        "offline_access",
        "microprofile-jwt"
      ]
    },
    {
      "id": "3ecf5b78-4b79-44ed-a37b-322e6870be82",
      "clientId": "broker",
      "name": "${client_broker}",
      "surrogateAuthRequired": false,
      "enabled": true,
      "clientAuthenticatorType": "client-secret",
      "secret": "**********",
      "redirectUris": [],
      "webOrigins": [],
      "notBefore": 0,
      "bearerOnly": false,
      "consentRequired": false,
      "standardFlowEnabled": true,
      "implicitFlowEnabled": false,
      "directAccessGrantsEnabled": false,
      "serviceAccountsEnabled": false,
      "publicClient": false,
      "frontchannelLogout": false,
      "protocol": "openid-connect",
      "attributes": {},
      "authenticationFlowBindingOverrides": {},
      "fullScopeAllowed": false,
      "nodeReRegistrationTimeout": 0,
      "defaultClientScopes": [
        "web-origins",
        "role_list",
        "profile",
        "roles",
        "email"
      ],
      "optionalClientScopes": [
        "address",
        "phone",
        "offline_access",
        "microprofile-jwt"
      ]
    },
    {
      "id": "3e7f2585-cbec-48be-b20b-cd92badbf656",
      "clientId": "mattermost",
      "surrogateAuthRequired": false,
      "enabled": true,
      "clientAuthenticatorType": "client-secret",
      "secret": "**********",
      "redirectUris": [
        "https://mattermost-example.ch/signup/gitlab/complete"
      ],
      "webOrigins": [],
      "notBefore": 0,
      "bearerOnly": false,
      "consentRequired": false,
      "standardFlowEnabled": true,
      "implicitFlowEnabled": false,
      "directAccessGrantsEnabled": false,
      "serviceAccountsEnabled": false,
      "publicClient": false,
      "frontchannelLogout": false,
      "protocol": "openid-connect",
      "attributes": {
        "saml.assertion.signature": "false",
        "saml.multivalued.roles": "false",
        "saml.force.post.binding": "false",
        "saml.encrypt": "false",
        "saml.server.signature": "false",
        "saml.server.signature.keyinfo.ext": "false",
        "exclude.session.state.from.auth.response": "false",
        "saml_force_name_id_format": "false",
        "saml.client.signature": "false",
        "tls.client.certificate.bound.access.tokens": "false",
        "saml.authnstatement": "false",
        "display.on.consent.screen": "false",
        "saml.onetimeuse.condition": "false"
      },
      "authenticationFlowBindingOverrides": {},
      "fullScopeAllowed": true,
      "nodeReRegistrationTimeout": -1,
      "protocolMappers": [
        {
          "id": "efd40ac1-1956-4f59-ac32-dec2e6ffc360",
          "name": "Client Host",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usersessionmodel-note-mapper",
          "consentRequired": false,
          "config": {
            "user.session.note": "clientHost",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "clientHost",
            "jsonType.label": "String"
          }
        },
        {
          "id": "7d7d701b-5dce-4e49-94bd-56540bd087ee",
          "name": "Client IP Address",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usersessionmodel-note-mapper",
          "consentRequired": false,
          "config": {
            "user.session.note": "clientAddress",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "clientAddress",
            "jsonType.label": "String"
          }
        },
        {
          "id": "1a60474e-c779-4d6e-97cb-a4e06fa620dc",
          "name": "mattermostId",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-attribute-mapper",
          "consentRequired": false,
          "config": {
            "userinfo.token.claim": "true",
            "user.attribute": "mattermostId",
            "id.token.claim": "false",
            "access.token.claim": "false",
            "claim.name": "id",
            "jsonType.label": "long"
          }
        },
        {
          "id": "3ecfa97b-1b69-4e3b-b898-dfebe37bc8a4",
          "name": "username",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-property-mapper",
          "consentRequired": false,
          "config": {
            "userinfo.token.claim": "true",
            "user.attribute": "username",
            "id.token.claim": "false",
            "access.token.claim": "false",
            "claim.name": "username",
            "jsonType.label": "String"
          }
        },
        {
          "id": "e250f686-64b4-44a4-9e1b-4d8388768e4e",
          "name": "Client ID",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usersessionmodel-note-mapper",
          "consentRequired": false,
          "config": {
            "user.session.note": "clientId",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "clientId",
            "jsonType.label": "String"
          }
        }
      ],
      "defaultClientScopes": [
        "web-origins",
        "role_list",
        "profile",
        "roles",
        "email"
      ],
      "optionalClientScopes": [
        "address",
        "phone",
        "offline_access",
        "microprofile-jwt"
      ]
    }
  ],
  "clientScopes": [
    {
      "id": "7041ad3e-8555-4181-9097-7dedecf39903",
      "name": "profile",
      "description": "OpenID Connect built-in scope: profile",
      "protocol": "openid-connect",
      "attributes": {
        "include.in.token.scope": "true",
        "display.on.consent.screen": "true",
        "consent.screen.text": "${profileScopeConsentText}"
      },
      "protocolMappers": [
        {
          "id": "0c56b9ee-c342-408f-b889-6e2e0c23c0b3",
          "name": "birthdate",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-attribute-mapper",
          "consentRequired": false,
          "config": {
            "userinfo.token.claim": "true",
            "user.attribute": "birthdate",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "birthdate",
            "jsonType.label": "String"
          }
        },
        {
          "id": "a14fcd71-932b-4308-9eb7-7da99e154fbe",
          "name": "locale",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-attribute-mapper",
          "consentRequired": false,
          "config": {
            "userinfo.token.claim": "true",
            "user.attribute": "locale",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "locale",
            "jsonType.label": "String"
          }
        },
        {
          "id": "6b81bb49-900f-4d92-be78-f68004876920",
          "name": "middle name",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-attribute-mapper",
          "consentRequired": false,
          "config": {
            "userinfo.token.claim": "true",
            "user.attribute": "middleName",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "middle_name",
            "jsonType.label": "String"
          }
        },
        {
          "id": "95aee2b2-80d7-4d59-a091-43590d18aca6",
          "name": "website",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-attribute-mapper",
          "consentRequired": false,
          "config": {
            "userinfo.token.claim": "true",
            "user.attribute": "website",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "website",
            "jsonType.label": "String"
          }
        },
        {
          "id": "15b40ff2-6586-4131-a920-94f6117d4907",
          "name": "gender",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-attribute-mapper",
          "consentRequired": false,
          "config": {
            "userinfo.token.claim": "true",
            "user.attribute": "gender",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "gender",
            "jsonType.label": "String"
          }
        },
        {
          "id": "5893a3d6-b5d9-42f8-9c4b-5c49a5e8f9b5",
          "name": "updated at",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-attribute-mapper",
          "consentRequired": false,
          "config": {
            "userinfo.token.claim": "true",
            "user.attribute": "updatedAt",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "updated_at",
            "jsonType.label": "String"
          }
        },
        {
          "id": "d2af34b6-590e-4668-8588-6ce5fa296100",
          "name": "full name",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-full-name-mapper",
          "consentRequired": false,
          "config": {
            "id.token.claim": "true",
            "access.token.claim": "true",
            "userinfo.token.claim": "true"
          }
        },
        {
          "id": "9a723139-fbc4-464e-a1cc-fb106b87c13c",
          "name": "given name",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-property-mapper",
          "consentRequired": false,
          "config": {
            "userinfo.token.claim": "true",
            "user.attribute": "firstName",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "given_name",
            "jsonType.label": "String"
          }
        },
        {
          "id": "2da88f93-ed24-4fa0-ab6a-77eb733b3cfe",
          "name": "username",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-property-mapper",
          "consentRequired": false,
          "config": {
            "userinfo.token.claim": "true",
            "user.attribute": "username",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "preferred_username",
            "jsonType.label": "String"
          }
        },
        {
          "id": "dd4ed4eb-056f-48c5-8147-41451bbe08b2",
          "name": "family name",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-property-mapper",
          "consentRequired": false,
          "config": {
            "userinfo.token.claim": "true",
            "user.attribute": "lastName",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "family_name",
            "jsonType.label": "String"
          }
        },
        {
          "id": "56ff3ad0-1acc-4b88-8ed9-781c1623b954",
          "name": "zoneinfo",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-attribute-mapper",
          "consentRequired": false,
          "config": {
            "userinfo.token.claim": "true",
            "user.attribute": "zoneinfo",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "zoneinfo",
            "jsonType.label": "String"
          }
        },
        {
          "id": "145db863-e156-44e5-89ad-0ad465cb3151",
          "name": "nickname",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-attribute-mapper",
          "consentRequired": false,
          "config": {
            "userinfo.token.claim": "true",
            "user.attribute": "nickname",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "nickname",
            "jsonType.label": "String"
          }
        },
        {
          "id": "970aacff-e1ff-4285-9f30-355e955e603b",
          "name": "profile",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-attribute-mapper",
          "consentRequired": false,
          "config": {
            "userinfo.token.claim": "true",
            "user.attribute": "profile",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "profile",
            "jsonType.label": "String"
          }
        },
        {
          "id": "449fb2a8-7e06-485c-881a-2e2ba7f17ce1",
          "name": "picture",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-attribute-mapper",
          "consentRequired": false,
          "config": {
            "userinfo.token.claim": "true",
            "user.attribute": "picture",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "picture",
            "jsonType.label": "String"
          }
        }
      ]
    },
    {
      "id": "233b96e7-d44d-467d-ab01-b1fcc5f423b8",
      "name": "offline_access",
      "description": "OpenID Connect built-in scope: offline_access",
      "protocol": "openid-connect",
      "attributes": {
        "consent.screen.text": "${offlineAccessScopeConsentText}",
        "display.on.consent.screen": "true"
      }
    },
    {
      "id": "b534510d-fc9e-4b7c-a2b9-f24bb5a0546b",
      "name": "role_list",
      "description": "SAML role list",
      "protocol": "saml",
      "attributes": {
        "consent.screen.text": "${samlRoleListScopeConsentText}",
        "display.on.consent.screen": "true"
      },
      "protocolMappers": [
        {
          "id": "a6eeb8b7-2507-45c6-8bdf-aaab8a03f93a",
          "name": "role list",
          "protocol": "saml",
          "protocolMapper": "saml-role-list-mapper",
          "consentRequired": false,
          "config": {
            "single": "false",
            "attribute.nameformat": "Basic",
            "attribute.name": "Role"
          }
        }
      ]
    },
    {
      "id": "6a4bc858-edcf-4681-a4b8-ac43b333f924",
      "name": "email",
      "description": "OpenID Connect built-in scope: email",
      "protocol": "openid-connect",
      "attributes": {
        "include.in.token.scope": "true",
        "display.on.consent.screen": "true",
        "consent.screen.text": "${emailScopeConsentText}"
      },
      "protocolMappers": [
        {
          "id": "b7a35f3d-a571-4626-b26d-bae54347e9e7",
          "name": "email",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-property-mapper",
          "consentRequired": false,
          "config": {
            "userinfo.token.claim": "true",
            "user.attribute": "email",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "email",
            "jsonType.label": "String"
          }
        },
        {
          "id": "a1a62154-c0f2-4cbf-b518-dc20b69af140",
          "name": "email verified",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-property-mapper",
          "consentRequired": false,
          "config": {
            "userinfo.token.claim": "true",
            "user.attribute": "emailVerified",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "email_verified",
            "jsonType.label": "boolean"
          }
        }
      ]
    },
    {
      "id": "542c3988-48bb-42d2-91ee-0dde69df896d",
      "name": "address",
      "description": "OpenID Connect built-in scope: address",
      "protocol": "openid-connect",
      "attributes": {
        "include.in.token.scope": "true",
        "display.on.consent.screen": "true",
        "consent.screen.text": "${addressScopeConsentText}"
      },
      "protocolMappers": [
        {
          "id": "516d6e59-1cfe-4d02-bda5-b47576cbc3b9",
          "name": "address",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-address-mapper",
          "consentRequired": false,
          "config": {
            "user.attribute.formatted": "formatted",
            "user.attribute.country": "country",
            "user.attribute.postal_code": "postal_code",
            "userinfo.token.claim": "true",
            "user.attribute.street": "street",
            "id.token.claim": "true",
            "user.attribute.region": "region",
            "access.token.claim": "true",
            "user.attribute.locality": "locality"
          }
        }
      ]
    },
    {
      "id": "5003f695-aff1-4040-8b0d-b1c1881252bf",
      "name": "phone",
      "description": "OpenID Connect built-in scope: phone",
      "protocol": "openid-connect",
      "attributes": {
        "include.in.token.scope": "true",
        "display.on.consent.screen": "true",
        "consent.screen.text": "${phoneScopeConsentText}"
      },
      "protocolMappers": [
        {
          "id": "7468c03d-5e99-4158-a86c-8aa25fe3ee9c",
          "name": "phone number verified",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-attribute-mapper",
          "consentRequired": false,
          "config": {
            "userinfo.token.claim": "true",
            "user.attribute": "phoneNumberVerified",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "phone_number_verified",
            "jsonType.label": "boolean"
          }
        },
        {
          "id": "0f1c20e0-09d1-4da2-bc40-a54f1dad905a",
          "name": "phone number",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-attribute-mapper",
          "consentRequired": false,
          "config": {
            "userinfo.token.claim": "true",
            "user.attribute": "phoneNumber",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "phone_number",
            "jsonType.label": "String"
          }
        }
      ]
    },
    {
      "id": "8c1b6a48-f70c-4429-896e-76aa363a9e80",
      "name": "roles",
      "description": "OpenID Connect scope for add user roles to the access token",
      "protocol": "openid-connect",
      "attributes": {
        "include.in.token.scope": "false",
        "display.on.consent.screen": "true",
        "consent.screen.text": "${rolesScopeConsentText}"
      },
      "protocolMappers": [
        {
          "id": "da69fa4e-311d-48c3-88ca-362f01b88cb7",
          "name": "client roles",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-client-role-mapper",
          "consentRequired": false,
          "config": {
            "user.attribute": "foo",
            "access.token.claim": "true",
            "claim.name": "resource_access.${client_id}.roles",
            "jsonType.label": "String",
            "multivalued": "true"
          }
        },
        {
          "id": "2937857f-71fc-4641-8e19-105a00004d50",
          "name": "realm roles",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-realm-role-mapper",
          "consentRequired": false,
          "config": {
            "user.attribute": "foo",
            "access.token.claim": "true",
            "claim.name": "realm_access.roles",
            "jsonType.label": "String",
            "multivalued": "true"
          }
        },
        {
          "id": "bca3fe79-d7ef-4688-95bb-474a68dbabc1",
          "name": "audience resolve",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-audience-resolve-mapper",
          "consentRequired": false,
          "config": {}
        }
      ]
    },
    {
      "id": "efcde36f-7872-4023-8799-6ef93626a38c",
      "name": "web-origins",
      "description": "OpenID Connect scope for add allowed web origins to the access token",
      "protocol": "openid-connect",
      "attributes": {
        "include.in.token.scope": "false",
        "display.on.consent.screen": "false",
        "consent.screen.text": ""
      },
      "protocolMappers": [
        {
          "id": "0aaa29d4-bdd3-4a42-851e-621166333d10",
          "name": "allowed web origins",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-allowed-origins-mapper",
          "consentRequired": false,
          "config": {}
        }
      ]
    },
    {
      "id": "f3b52c94-552f-41af-b24b-a85748fe4e91",
      "name": "microprofile-jwt",
      "description": "Microprofile - JWT built-in scope",
      "protocol": "openid-connect",
      "attributes": {
        "include.in.token.scope": "true",
        "display.on.consent.screen": "false"
      },
      "protocolMappers": [
        {
          "id": "b7b0745c-0a39-43f3-98f0-65d8f2da7135",
          "name": "groups",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-realm-role-mapper",
          "consentRequired": false,
          "config": {
            "multivalued": "true",
            "user.attribute": "foo",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "groups",
            "jsonType.label": "String"
          }
        },
        {
          "id": "1891f26a-8744-4727-aeaa-edf519d5e8ca",
          "name": "upn",
          "protocol": "openid-connect",
          "protocolMapper": "oidc-usermodel-property-mapper",
          "consentRequired": false,
          "config": {
            "userinfo.token.claim": "true",
            "user.attribute": "username",
            "id.token.claim": "true",
            "access.token.claim": "true",
            "claim.name": "upn",
            "jsonType.label": "String"
          }
        }
      ]
    }
  ],
  "defaultDefaultClientScopes": [
    "role_list",
    "profile",
    "email",
    "roles",
    "web-origins"
  ],
  "defaultOptionalClientScopes": [
    "offline_access",
    "address",
    "phone",
    "microprofile-jwt"
  ],
  "browserSecurityHeaders": {
    "contentSecurityPolicyReportOnly": "",
    "xContentTypeOptions": "nosniff",
    "xRobotsTag": "none",
    "xFrameOptions": "SAMEORIGIN",
    "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';",
    "xXSSProtection": "1; mode=block",
    "strictTransportSecurity": "max-age=31536000; includeSubDomains"
  },
  "smtpServer": {},
  "eventsEnabled": false,
  "eventsListeners": [
    "jboss-logging"
  ],
  "enabledEventTypes": [],
  "adminEventsEnabled": false,
  "adminEventsDetailsEnabled": false,
  "identityProviders": [
    {
      "alias": "mattermost",
      "internalId": "49882ef8-3904-4f35-84ca-bd3eb5a07b47",
      "providerId": "keycloak-oidc",
      "enabled": true,
      "updateProfileFirstLoginMode": "on",
      "trustEmail": true,
      "storeToken": true,
      "addReadTokenRoleOnCreate": true,
      "authenticateByDefault": false,
      "linkOnly": false,
      "firstBrokerLoginFlowAlias": "central-autologin",
      "config": {
        "hideOnLoginPage": "",
        "validateSignature": "true",
        "userInfoUrl": "https://example.com/auth/realms/central/protocol/openid-connect/userinfo",
        "uiLocales": "",
        "acceptsPromptNoneForwardFromClient": "true",
        "tokenUrl": "https://example.com/auth/realms/central/protocol/openid-connect/token",
        "clientId": "slave",
        "jwksUrl": "https://example.com/auth/realms/central/protocol/openid-connect/certs",
        "backchannelSupported": "",
        "issuer": "https://example.com/auth/realms/central",
        "useJwksUrl": "true",
        "loginHint": "",
        "authorizationUrl": "https://example.com/auth/realms/central/protocol/openid-connect/auth",
        "clientAuthMethod": "client_secret_post",
        "disableUserInfo": "",
        "logoutUrl": "https://example.com/auth/realms/central/protocol/openid-connect/logout",
        "clientSecret": "**********"
      }
    }
  ],
  "identityProviderMappers": [
    {
      "id": "ed8e0624-2cb8-4cac-9def-08ff70f31aac",
      "name": "mattermostUserAttributeMapper",
      "identityProviderAlias": "mattermost",
      "identityProviderMapper": "oidc-mattermost-user-attribute-idp-mapper",
      "config": {
        "user.attribute": "mattermostId",
        "claim": "employeeNumber"
      }
    }
  ],
  "components": {
    "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [
      {
        "id": "1f5866c5-5ba9-42d6-a5b5-c25d6a7fe1ee",
        "name": "Consent Required",
        "providerId": "consent-required",
        "subType": "anonymous",
        "subComponents": {},
        "config": {}
      },
      {
        "id": "2bdbe151-1941-4f52-bb9b-64748a99fd56",
        "name": "Max Clients Limit",
        "providerId": "max-clients",
        "subType": "anonymous",
        "subComponents": {},
        "config": {
          "max-clients": [
            "200"
          ]
        }
      },
      {
        "id": "7d89b073-a807-4c57-81ca-dc1ec89f89dd",
        "name": "Allowed Protocol Mapper Types",
        "providerId": "allowed-protocol-mappers",
        "subType": "authenticated",
        "subComponents": {},
        "config": {
          "allowed-protocol-mapper-types": [
            "oidc-usermodel-attribute-mapper",
            "oidc-sha256-pairwise-sub-mapper",
            "oidc-address-mapper",
            "saml-user-attribute-mapper",
            "saml-user-property-mapper",
            "oidc-usermodel-property-mapper",
            "oidc-full-name-mapper",
            "saml-role-list-mapper"
          ]
        }
      },
      {
        "id": "598dc131-d570-424f-bfad-fcad0a1fa071",
        "name": "Allowed Client Scopes",
        "providerId": "allowed-client-templates",
        "subType": "authenticated",
        "subComponents": {},
        "config": {
          "allow-default-scopes": [
            "true"
          ]
        }
      },
      {
        "id": "8897ca8a-d1f0-4165-9a6b-6760cbe6308c",
        "name": "Trusted Hosts",
        "providerId": "trusted-hosts",
        "subType": "anonymous",
        "subComponents": {},
        "config": {
          "host-sending-registration-request-must-match": [
            "true"
          ],
          "client-uris-must-match": [
            "true"
          ]
        }
      },
      {
        "id": "cb0672a2-3d37-4904-bc12-9d77bbe1c747",
        "name": "Allowed Protocol Mapper Types",
        "providerId": "allowed-protocol-mappers",
        "subType": "anonymous",
        "subComponents": {},
        "config": {
          "allowed-protocol-mapper-types": [
            "oidc-address-mapper",
            "oidc-usermodel-attribute-mapper",
            "oidc-full-name-mapper",
            "saml-role-list-mapper",
            "saml-user-property-mapper",
            "saml-user-attribute-mapper",
            "oidc-usermodel-property-mapper",
            "oidc-sha256-pairwise-sub-mapper"
          ]
        }
      },
      {
        "id": "28653278-4d04-4b18-9ad9-30a7bb62d6a5",
        "name": "Allowed Client Scopes",
        "providerId": "allowed-client-templates",
        "subType": "anonymous",
        "subComponents": {},
        "config": {
          "allow-default-scopes": [
            "true"
          ]
        }
      },
      {
        "id": "c65b77d4-227d-4ee5-b992-c983ca81a71c",
        "name": "Full Scope Disabled",
        "providerId": "scope",
        "subType": "anonymous",
        "subComponents": {},
        "config": {}
      }
    ],
    "org.keycloak.keys.KeyProvider": [
      {
        "id": "f61976d7-bf14-434f-b73d-8789b24d2c39",
        "name": "hmac-generated",
        "providerId": "hmac-generated",
        "subComponents": {},
        "config": {
          "priority": [
            "100"
          ],
          "algorithm": [
            "HS256"
          ]
        }
      },
      {
        "id": "06b8c7ea-1eb4-4c5a-837b-09a954121e11",
        "name": "rsa-generated",
        "providerId": "rsa-generated",
        "subComponents": {},
        "config": {
          "priority": [
            "100"
          ]
        }
      },
      {
        "id": "2ef3a05b-b5a8-4120-a6c3-c44a8e41838d",
        "name": "aes-generated",
        "providerId": "aes-generated",
        "subComponents": {},
        "config": {
          "priority": [
            "100"
          ]
        }
      }
    ]
  },
  "internationalizationEnabled": false,
  "supportedLocales": [],
  "authenticationFlows": [
    {
      "id": "6ebef0ee-61b1-4966-8627-69de6422d7b9",
      "alias": "Account verification options",
      "description": "Method with which to verity the existing account",
      "providerId": "basic-flow",
      "topLevel": false,
      "builtIn": true,
      "authenticationExecutions": [
        {
          "authenticator": "idp-email-verification",
          "requirement": "ALTERNATIVE",
          "priority": 10,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "requirement": "ALTERNATIVE",
          "priority": 20,
          "flowAlias": "Verify Existing Account by Re-authentication",
          "userSetupAllowed": false,
          "autheticatorFlow": true
        }
      ]
    },
    {
      "id": "b279b18d-f441-4f0a-a8c5-d5962b797fd7",
      "alias": "Authentication Options",
      "description": "Authentication options.",
      "providerId": "basic-flow",
      "topLevel": false,
      "builtIn": true,
      "authenticationExecutions": [
        {
          "authenticator": "basic-auth",
          "requirement": "REQUIRED",
          "priority": 10,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "authenticator": "basic-auth-otp",
          "requirement": "DISABLED",
          "priority": 20,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "authenticator": "auth-spnego",
          "requirement": "DISABLED",
          "priority": 30,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        }
      ]
    },
    {
      "id": "a2af2b25-3ad8-4e62-9397-a5ad4feef6ca",
      "alias": "Browser - Conditional OTP",
      "description": "Flow to determine if the OTP is required for the authentication",
      "providerId": "basic-flow",
      "topLevel": false,
      "builtIn": true,
      "authenticationExecutions": [
        {
          "authenticator": "conditional-user-configured",
          "requirement": "REQUIRED",
          "priority": 10,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "authenticator": "auth-otp-form",
          "requirement": "REQUIRED",
          "priority": 20,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        }
      ]
    },
    {
      "id": "967df3b7-2789-429e-a5ab-95feee9de256",
      "alias": "Direct Grant - Conditional OTP",
      "description": "Flow to determine if the OTP is required for the authentication",
      "providerId": "basic-flow",
      "topLevel": false,
      "builtIn": true,
      "authenticationExecutions": [
        {
          "authenticator": "conditional-user-configured",
          "requirement": "REQUIRED",
          "priority": 10,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "authenticator": "direct-grant-validate-otp",
          "requirement": "REQUIRED",
          "priority": 20,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        }
      ]
    },
    {
      "id": "e7ff00a5-cfde-4065-9473-de04402c679c",
      "alias": "First broker login - Conditional OTP",
      "description": "Flow to determine if the OTP is required for the authentication",
      "providerId": "basic-flow",
      "topLevel": false,
      "builtIn": true,
      "authenticationExecutions": [
        {
          "authenticator": "conditional-user-configured",
          "requirement": "REQUIRED",
          "priority": 10,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "authenticator": "auth-otp-form",
          "requirement": "REQUIRED",
          "priority": 20,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        }
      ]
    },
    {
      "id": "ad7c86a5-238b-444c-8b7a-69616181126b",
      "alias": "Handle Existing Account",
      "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
      "providerId": "basic-flow",
      "topLevel": false,
      "builtIn": true,
      "authenticationExecutions": [
        {
          "authenticator": "idp-confirm-link",
          "requirement": "REQUIRED",
          "priority": 10,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "requirement": "REQUIRED",
          "priority": 20,
          "flowAlias": "Account verification options",
          "userSetupAllowed": false,
          "autheticatorFlow": true
        }
      ]
    },
    {
      "id": "a4f2d3e9-95c3-4dd4-baa8-283d95dbf2a0",
      "alias": "Reset - Conditional OTP",
      "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
      "providerId": "basic-flow",
      "topLevel": false,
      "builtIn": true,
      "authenticationExecutions": [
        {
          "authenticator": "conditional-user-configured",
          "requirement": "REQUIRED",
          "priority": 10,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "authenticator": "reset-otp",
          "requirement": "REQUIRED",
          "priority": 20,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        }
      ]
    },
    {
      "id": "fbd55133-1905-485e-9895-98090a4bed55",
      "alias": "User creation or linking",
      "description": "Flow for the existing/non-existing user alternatives",
      "providerId": "basic-flow",
      "topLevel": false,
      "builtIn": true,
      "authenticationExecutions": [
        {
          "authenticatorConfig": "create unique user config",
          "authenticator": "idp-create-user-if-unique",
          "requirement": "ALTERNATIVE",
          "priority": 10,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "requirement": "ALTERNATIVE",
          "priority": 20,
          "flowAlias": "Handle Existing Account",
          "userSetupAllowed": false,
          "autheticatorFlow": true
        }
      ]
    },
    {
      "id": "e37459bd-501b-406d-8bdd-a3d8deadbefd",
      "alias": "Verify Existing Account by Re-authentication",
      "description": "Reauthentication of existing account",
      "providerId": "basic-flow",
      "topLevel": false,
      "builtIn": true,
      "authenticationExecutions": [
        {
          "authenticator": "idp-username-password-form",
          "requirement": "REQUIRED",
          "priority": 10,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "requirement": "CONDITIONAL",
          "priority": 20,
          "flowAlias": "First broker login - Conditional OTP",
          "userSetupAllowed": false,
          "autheticatorFlow": true
        }
      ]
    },
    {
      "id": "0127aa8d-7654-42dc-a214-ee916f6ddc52",
      "alias": "browser",
      "description": "browser based authentication",
      "providerId": "basic-flow",
      "topLevel": true,
      "builtIn": true,
      "authenticationExecutions": [
        {
          "authenticator": "auth-cookie",
          "requirement": "ALTERNATIVE",
          "priority": 10,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "authenticator": "auth-spnego",
          "requirement": "ALTERNATIVE",
          "priority": 20,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "authenticatorConfig": "mattermost",
          "authenticator": "identity-provider-redirector",
          "requirement": "ALTERNATIVE",
          "priority": 25,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "requirement": "ALTERNATIVE",
          "priority": 30,
          "flowAlias": "forms",
          "userSetupAllowed": false,
          "autheticatorFlow": true
        }
      ]
    },
    {
      "id": "79054742-8f62-4e40-acbe-89f82293f4ad",
      "alias": "central-autologin",
      "description": "",
      "providerId": "basic-flow",
      "topLevel": true,
      "builtIn": false,
      "authenticationExecutions": [
        {
          "authenticatorConfig": "mattermost",
          "authenticator": "idp-create-user-if-unique",
          "requirement": "ALTERNATIVE",
          "priority": 0,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "authenticator": "idp-auto-link",
          "requirement": "ALTERNATIVE",
          "priority": 1,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        }
      ]
    },
    {
      "id": "b90b505d-e87e-472e-9852-b5272c231719",
      "alias": "clients",
      "description": "Base authentication for clients",
      "providerId": "client-flow",
      "topLevel": true,
      "builtIn": true,
      "authenticationExecutions": [
        {
          "authenticator": "client-secret",
          "requirement": "ALTERNATIVE",
          "priority": 10,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "authenticator": "client-jwt",
          "requirement": "ALTERNATIVE",
          "priority": 20,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "authenticator": "client-secret-jwt",
          "requirement": "ALTERNATIVE",
          "priority": 30,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "authenticator": "client-x509",
          "requirement": "ALTERNATIVE",
          "priority": 40,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        }
      ]
    },
    {
      "id": "8c5d12a3-8709-4f0a-88ca-5a91dc29f075",
      "alias": "direct grant",
      "description": "OpenID Connect Resource Owner Grant",
      "providerId": "basic-flow",
      "topLevel": true,
      "builtIn": true,
      "authenticationExecutions": [
        {
          "authenticator": "direct-grant-validate-username",
          "requirement": "REQUIRED",
          "priority": 10,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "authenticator": "direct-grant-validate-password",
          "requirement": "REQUIRED",
          "priority": 20,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "requirement": "CONDITIONAL",
          "priority": 30,
          "flowAlias": "Direct Grant - Conditional OTP",
          "userSetupAllowed": false,
          "autheticatorFlow": true
        }
      ]
    },
    {
      "id": "103536aa-0bc0-4b77-ab68-82d5f4e71db6",
      "alias": "docker auth",
      "description": "Used by Docker clients to authenticate against the IDP",
      "providerId": "basic-flow",
      "topLevel": true,
      "builtIn": true,
      "authenticationExecutions": [
        {
          "authenticator": "docker-http-basic-authenticator",
          "requirement": "REQUIRED",
          "priority": 10,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        }
      ]
    },
    {
      "id": "5cb577ff-eeed-4b90-b2f4-1d3dc6fc7ebf",
      "alias": "first broker login",
      "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
      "providerId": "basic-flow",
      "topLevel": true,
      "builtIn": true,
      "authenticationExecutions": [
        {
          "authenticatorConfig": "review profile config",
          "authenticator": "idp-review-profile",
          "requirement": "REQUIRED",
          "priority": 10,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "requirement": "REQUIRED",
          "priority": 20,
          "flowAlias": "User creation or linking",
          "userSetupAllowed": false,
          "autheticatorFlow": true
        }
      ]
    },
    {
      "id": "471b14ad-0052-4adf-bbc6-fdc2b924b5b0",
      "alias": "forms",
      "description": "Username, password, otp and other auth forms.",
      "providerId": "basic-flow",
      "topLevel": false,
      "builtIn": true,
      "authenticationExecutions": [
        {
          "authenticator": "auth-username-password-form",
          "requirement": "REQUIRED",
          "priority": 10,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "requirement": "CONDITIONAL",
          "priority": 20,
          "flowAlias": "Browser - Conditional OTP",
          "userSetupAllowed": false,
          "autheticatorFlow": true
        }
      ]
    },
    {
      "id": "2677cd86-3323-4da0-89b4-ce6236db7b0e",
      "alias": "http challenge",
      "description": "An authentication flow based on challenge-response HTTP Authentication Schemes",
      "providerId": "basic-flow",
      "topLevel": true,
      "builtIn": true,
      "authenticationExecutions": [
        {
          "authenticator": "no-cookie-redirect",
          "requirement": "REQUIRED",
          "priority": 10,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "requirement": "REQUIRED",
          "priority": 20,
          "flowAlias": "Authentication Options",
          "userSetupAllowed": false,
          "autheticatorFlow": true
        }
      ]
    },
    {
      "id": "229ccc32-2a19-4648-9710-2c5eb69a01fa",
      "alias": "registration",
      "description": "registration flow",
      "providerId": "basic-flow",
      "topLevel": true,
      "builtIn": true,
      "authenticationExecutions": [
        {
          "authenticator": "registration-page-form",
          "requirement": "REQUIRED",
          "priority": 10,
          "flowAlias": "registration form",
          "userSetupAllowed": false,
          "autheticatorFlow": true
        }
      ]
    },
    {
      "id": "9046a7ce-4735-40f9-b2b8-15304b9d60a5",
      "alias": "registration form",
      "description": "registration form",
      "providerId": "form-flow",
      "topLevel": false,
      "builtIn": true,
      "authenticationExecutions": [
        {
          "authenticator": "registration-user-creation",
          "requirement": "REQUIRED",
          "priority": 20,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "authenticator": "registration-profile-action",
          "requirement": "REQUIRED",
          "priority": 40,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "authenticator": "registration-password-action",
          "requirement": "REQUIRED",
          "priority": 50,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "authenticator": "registration-recaptcha-action",
          "requirement": "DISABLED",
          "priority": 60,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        }
      ]
    },
    {
      "id": "72b20395-d5ef-4012-b74e-91e50edf1d26",
      "alias": "reset credentials",
      "description": "Reset credentials for a user if they forgot their password or something",
      "providerId": "basic-flow",
      "topLevel": true,
      "builtIn": true,
      "authenticationExecutions": [
        {
          "authenticator": "reset-credentials-choose-user",
          "requirement": "REQUIRED",
          "priority": 10,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "authenticator": "reset-credential-email",
          "requirement": "REQUIRED",
          "priority": 20,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "authenticator": "reset-password",
          "requirement": "REQUIRED",
          "priority": 30,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        },
        {
          "requirement": "CONDITIONAL",
          "priority": 40,
          "flowAlias": "Reset - Conditional OTP",
          "userSetupAllowed": false,
          "autheticatorFlow": true
        }
      ]
    },
    {
      "id": "8134a6d8-7fda-4f3a-9495-a94404e8fa75",
      "alias": "saml ecp",
      "description": "SAML ECP Profile Authentication Flow",
      "providerId": "basic-flow",
      "topLevel": true,
      "builtIn": true,
      "authenticationExecutions": [
        {
          "authenticator": "http-basic-authenticator",
          "requirement": "REQUIRED",
          "priority": 10,
          "userSetupAllowed": false,
          "autheticatorFlow": false
        }
      ]
    }
  ],
  "authenticatorConfig": [
    {
      "id": "315a4275-d4e0-46c9-b74c-9e42dc9b0b57",
      "alias": "create unique user config",
      "config": {
        "require.password.update.after.registration": "false"
      }
    },
    {
      "id": "f7cfac64-23d1-418a-9968-d1da89e4c4fd",
      "alias": "mattermost",
      "config": {
        "defaultProvider": "mattermost"
      }
    },
    {
      "id": "4c450533-4fba-4cd9-b978-312dc780f0f1",
      "alias": "mattermost",
      "config": {}
    },
    {
      "id": "f355d607-218a-4a34-9510-16d32b5dd5fb",
      "alias": "review profile config",
      "config": {
        "update.profile.on.first.login": "missing"
      }
    }
  ],
  "requiredActions": [
    {
      "alias": "CONFIGURE_TOTP",
      "name": "Configure OTP",
      "providerId": "CONFIGURE_TOTP",
      "enabled": true,
      "defaultAction": false,
      "priority": 10,
      "config": {}
    },
    {
      "alias": "terms_and_conditions",
      "name": "Terms and Conditions",
      "providerId": "terms_and_conditions",
      "enabled": false,
      "defaultAction": false,
      "priority": 20,
      "config": {}
    },
    {
      "alias": "UPDATE_PASSWORD",
      "name": "Update Password",
      "providerId": "UPDATE_PASSWORD",
      "enabled": true,
      "defaultAction": false,
      "priority": 30,
      "config": {}
    },
    {
      "alias": "UPDATE_PROFILE",
      "name": "Update Profile",
      "providerId": "UPDATE_PROFILE",
      "enabled": true,
      "defaultAction": false,
      "priority": 40,
      "config": {}
    },
    {
      "alias": "VERIFY_EMAIL",
      "name": "Verify Email",
      "providerId": "VERIFY_EMAIL",
      "enabled": true,
      "defaultAction": false,
      "priority": 50,
      "config": {}
    }
  ],
  "browserFlow": "browser",
  "registrationFlow": "registration",
  "directGrantFlow": "direct grant",
  "resetCredentialsFlow": "reset credentials",
  "clientAuthenticationFlow": "clients",
  "dockerAuthenticationFlow": "docker auth",
  "attributes": {
    "webAuthnPolicyAuthenticatorAttachment": "not specified",
    "_browser_header.xRobotsTag": "none",
    "webAuthnPolicyRpEntityName": "keycloak",
    "failureFactor": "30",
    "actionTokenGeneratedByUserLifespan": "300",
    "maxDeltaTimeSeconds": "43200",
    "webAuthnPolicySignatureAlgorithms": "ES256",
    "offlineSessionMaxLifespan": "5184000",
    "_browser_header.contentSecurityPolicyReportOnly": "",
    "bruteForceProtected": "false",
    "_browser_header.contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';",
    "_browser_header.xXSSProtection": "1; mode=block",
    "_browser_header.xFrameOptions": "SAMEORIGIN",
    "_browser_header.strictTransportSecurity": "max-age=31536000; includeSubDomains",
    "webAuthnPolicyUserVerificationRequirement": "not specified",
    "permanentLockout": "false",
    "quickLoginCheckMilliSeconds": "1000",
    "webAuthnPolicyCreateTimeout": "0",
    "webAuthnPolicyRequireResidentKey": "not specified",
    "webAuthnPolicyRpId": "",
    "webAuthnPolicyAttestationConveyancePreference": "not specified",
    "maxFailureWaitSeconds": "900",
    "minimumQuickLoginWaitSeconds": "60",
    "webAuthnPolicyAvoidSameAuthenticatorRegister": "false",
    "_browser_header.xContentTypeOptions": "nosniff",
    "actionTokenGeneratedByAdminLifespan": "43200",
    "waitIncrementSeconds": "60",
    "offlineSessionMaxLifespanEnabled": "false"
  },
  "keycloakVersion": "9.0.3",
  "userManagedAccessAllowed": false
}

Here is the output of config-cli:

2020-11-03 14:40:40.845  INFO 18404 --- [           main] d.a.k.config.KeycloakConfigApplication   : Starting KeycloakConfigApplication v2.5.0 on h50lag0 with PID 18404 (/home/ito/Projects/ito/keycloak-config-cli/keycloak-config-cli-9.0.3.jar started by ito in /home/ito/Projects/ito/keycloak-config-cli)
2020-11-03 14:40:40.864  INFO 18404 --- [           main] d.a.k.config.KeycloakConfigApplication   : No active profile set, falling back to default profiles: default
2020-11-03 14:40:41.757  INFO 18404 --- [           main] d.a.k.config.KeycloakConfigApplication   : Started KeycloakConfigApplication in 1.424 seconds (JVM running for 2.079)
2020-11-03 14:40:42.241  INFO 18404 --- [           main] d.a.k.c.provider.KeycloakImportProvider  : Importing file '/home/ito/Projects/ito/keycloak-config-cli/realm-export-mattermost-mod1.json'
2020-11-03 14:40:43.596  WARN 18404 --- [           main] d.a.k.config.provider.KeycloakProvider   : DEPRECATION: Omit /auth/ at server url is deprecated!
2020-11-03 14:40:48.130 ERROR 18404 --- [           main] d.a.k.config.KeycloakConfigRunner        : HTTP 404 Not Found
2020-11-03 14:40:48.131  INFO 18404 --- [           main] d.a.k.config.KeycloakConfigRunner        : keycloak-config-cli running in 00:05.926.

Environment (please complete the following information):

  • Keycloak Version: 9.0.3
  • keycloak-config-cli Version: 2.5
  • Java Version: 8

Fails on 4.6.0.Final of Keycloak due to unknown properties and Jackson not ignoring them

Found this image, as a great and simple way to set up a test environment with keycloak.

I tried the latest version (at the time) of Keycloak and received errors, but 4.4.0.Final worked.

Just wondering if any plans to try and handle these issues with future versions?

I thought about making a fork and attempting to make work myself, but I saw in an existing PR that it has been rewritten recently.

So opening this Issue first, to hopefully find the current state :)

bug: alphabetical order of resolving users when fixing duplicate issues

Thanks for the latests updates you released.
I've found some more information about the possible bug regarding conflicts when updating resolved users.

I am using v1.3.0 (docker) and Keycloak 9.0.2

I have a users.json like this:

{
  "realm": "my-realm",
  "users": [
    {
      "username": "[email protected]",
      "email": "[email protected]",
      "enabled": true,
      "firstName": "Abc",
      "lastName": "Stakeholder",
      "credentials": [
        {
          "type": "password",
          "value": "password"
        }
      ]
    },
    {
      "username": "company",
      "email": "[email protected]",
      "enabled": true,
      "firstName": "Company",
      "lastName": "Stakeholder",
      "credentials": [
        {
          "type": "password",
          "value": "password"
        }
      ]
    }
  ]
}

Guess: What happens when you use keycloak search for user company, it will get list of these users in alphabetical order.

In this case, I already had the company user and added a [email protected]
So mr. abc gets created, but when trying to update company, it's HTTP 409 conflict.

Is it possible that your new filter operation still returns the full list, gets the first item which is wrong in this case somehow?

stack trace:

2020-04-01 10:52:50.347  INFO 27 --- [           main] de.adorsys.keycloak.config.Application   : Starting Application v1.3.0_2020-03-27T20:40:28Z on a09b077849de with PID 27 (/opt/keycloak-config-cli.jar started by root in /)
... (omitted) ...
 2020-04-01 10:52:53.806 DEBUG 27 --- [           main] d.a.k.config.service.UserImportService   : Create user '[email protected]' in realm 'company-application'
 2020-04-01 10:52:53.949 DEBUG 27 --- [           main] d.a.k.config.service.UserImportService   : Remove realm-level roles [uma_authorization,offline_access] from user '[email protected]' in realm 'company-application'
 2020-04-01 10:52:54.019 DEBUG 27 --- [           main] d.a.k.config.service.UserImportService   : Update user 'company' in realm 'company-application'
 2020-04-01 10:52:54.138  INFO 27 --- [           main] ConditionEvaluationReportLoggingListener :

 Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
 2020-04-01 10:52:54.147 ERROR 27 --- [           main] o.s.boot.SpringApplication               : Application run failed

 java.lang.IllegalStateException: Failed to execute CommandLineRunner
 	at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:787) ~[spring-boot-2.2.5.RELEASE.jar!/:2.2.5.RELEASE]
 	at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:768) ~[spring-boot-2.2.5.RELEASE.jar!/:2.2.5.RELEASE]
 	at org.springframework.boot.SpringApplication.run(SpringApplication.java:322) ~[spring-boot-2.2.5.RELEASE.jar!/:2.2.5.RELEASE]
 	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226) ~[spring-boot-2.2.5.RELEASE.jar!/:2.2.5.RELEASE]
 	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215) ~[spring-boot-2.2.5.RELEASE.jar!/:2.2.5.RELEASE]
 	at de.adorsys.keycloak.config.Application.main(Application.java:47) ~[classes!/:1.3.0_2020-03-27T20:40:28Z]
 	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
 	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[na:na]
 	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[na:na]
 	at java.base/java.lang.reflect.Method.invoke(Unknown Source) ~[na:na]
 	at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48) ~[keycloak-config-cli.jar:1.3.0_2020-03-27T20:40:28Z]
 	at org.springframework.boot.loader.Launcher.launch(Launcher.java:87) ~[keycloak-config-cli.jar:1.3.0_2020-03-27T20:40:28Z]
 	at org.springframework.boot.loader.Launcher.launch(Launcher.java:51) ~[keycloak-config-cli.jar:1.3.0_2020-03-27T20:40:28Z]
 	at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:52) ~[keycloak-config-cli.jar:1.3.0_2020-03-27T20:40:28Z]
 Caused by: javax.ws.rs.ClientErrorException: HTTP 409 Conflict
 	at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.handleErrorStatus(ClientInvocation.java:241) ~[resteasy-client-3.9.1.Final.jar!/:3.9.1.Final]
 	at org.jboss.resteasy.client.jaxrs.internal.proxy.extractors.DefaultEntityExtractorFactory$3.extractEntity(DefaultEntityExtractorFactory.java:50) ~[resteasy-client-3.9.1.Final.jar!/:3.9.1.Final]
 	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invokeSync(ClientInvoker.java:151) ~[resteasy-client-3.9.1.Final.jar!/:3.9.1.Final]
 	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientInvoker.invoke(ClientInvoker.java:112) ~[resteasy-client-3.9.1.Final.jar!/:3.9.1.Final]
 	at org.jboss.resteasy.client.jaxrs.internal.proxy.ClientProxy.invoke(ClientProxy.java:76) ~[resteasy-client-3.9.1.Final.jar!/:3.9.1.Final]
 	at com.sun.proxy.$Proxy93.update(Unknown Source) ~[na:na]
 	at de.adorsys.keycloak.config.repository.UserRepository.updateUser(UserRepository.java:86) ~[classes!/:1.3.0_2020-03-27T20:40:28Z]
 	at de.adorsys.keycloak.config.service.UserImportService$UserImport.updateUser(UserImportService.java:101) ~[classes!/:1.3.0_2020-03-27T20:40:28Z]
 	at de.adorsys.keycloak.config.service.UserImportService$UserImport.importUser(UserImportService.java:86) ~[classes!/:1.3.0_2020-03-27T20:40:28Z]
 	at de.adorsys.keycloak.config.service.UserImportService.importUser(UserImportService.java:68) ~[classes!/:1.3.0_2020-03-27T20:40:28Z]
 	at de.adorsys.keycloak.config.service.UserImportService.doImport(UserImportService.java:61) ~[classes!/:1.3.0_2020-03-27T20:40:28Z]
 	at de.adorsys.keycloak.config.service.RealmImportService.updateRealm(RealmImportService.java:176) ~[classes!/:1.3.0_2020-03-27T20:40:28Z]
 	at de.adorsys.keycloak.config.service.RealmImportService.updateRealmIfNecessary(RealmImportService.java:157) ~[classes!/:1.3.0_2020-03-27T20:40:28Z]
 	at de.adorsys.keycloak.config.service.RealmImportService.doImport(RealmImportService.java:132) ~[classes!/:1.3.0_2020-03-27T20:40:28Z]
 	at de.adorsys.keycloak.config.Application.run(Application.java:57) ~[classes!/:1.3.0_2020-03-27T20:40:28Z]
 	at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:784) ~[spring-boot-2.2.5.RELEASE.jar!/:2.2.5.RELEASE]
 	... 13 common frames omitted

company-application-keycloak-config exited with code 1

New release

Hi, can I ask you to do release with latest changes ?

Thank you,
Artem

documentation / help for running idempodent configs - checksum question

Hi,

is there any help or documentation if I want to achieve idempodent configuration provision of the cli tool?

For example, it detects that my realms are existing and no actions are done,
but it always says it updates my realm clients, roles and users I present in the JSONs.

What is the checksum used for?

For example, the cli tool logs:

2020-03-04 07:40:48.273 DEBUG 178 --- [ main] d.a.k.config.service.RealmImportService : No need to update realm 'master', import checksum same: '324a31eb1912f70114550e958134e667391266facd618769646d799372bc3e58931d93f020d88e60aed9fd28be65490fb5800d43ae7aee6d0ff993a19b765a61'

Question:

Does the script names have any semantics or processing order?

Would it be possible that script names could have semantics, for example like in flyway (except that you don't store the state anywhere)

So that you could define

  • Repeatable scripts
  • Policy (Create if not exists, Update, Create once)
  • Processing order with number

I am using docker image adorsys/keycloak-config-cli:v0.8.0-7.0.0

Add option to remove client

Hi,

We have an idea to add feature for removing clients, as we do it quite often manually.
So, we will be happy if it become possible with that cli.

Big configuration files results into HTTP 500 Exceptions

Describe the bug
If I have a lot of clients defined in my import config, I got a HTTP 500 Exception.

To Reproduce
User keycloak-config-cli with an huge count of clients.

Expected behavior
No error should appear.

Environment (please complete the following information):

  • Keycloak Version: [e.g. 10.0.1] 10.0.1
  • keycloak-config-cli Version: [e.g. 1.4.0] 2.0.0-rc1
  • Java Version: [e.g. 11] 8

Additional context
Keycloak error:

19:24:57,006 WARN  [org.hibernate.engine.jdbc.spi.SqlExceptionHelper] (default task-1) SQL Error: 22001, SQLState: 22001
19:24:57,007 ERROR [org.hibernate.engine.jdbc.spi.SqlExceptionHelper] (default task-1) Value too long for column "VALUE NVARCHAR(255)": "'[""test1"",""test2"",""test3"",""test4"",""test5"",null,""test6"",""test7"",""test8"",""test9"",""test10"",""t... (285)"; SQL statement:
insert into REALM_ATTRIBUTE (VALUE, NAME, REALM_ID) values (?, ?, ?) [22001-193]

Configure authenticationFlowBindingOverrides for a client

Describe the bug
It seems not possible to configure authenticationFlowBindingOverrides for a client.
Keycloak uses authentication flow ID in their client configuration instead of alias.

To Reproduce
I tried to force the authentication flow id and use this id in the client authenticationFlowBindingOverrides configuration but it does not seems to work at least if the auth flow already exists.

{
  "enabled": true,
  "realm": "realmWithFlow",
  "authenticationFlows": [
    {
      "alias": "my auth flow",
      "id": "ad7d518c-4129-483a-8351-e1223cb8eead",
      "description": "My auth flow for testing",
      "providerId": "basic-flow",
      "topLevel": true,
      "builtIn": false,
      "authenticationExecutions": [
        {
          "authenticator": "docker-http-basic-authenticator",
          "requirement": "DISABLED",
          "priority": 0,
          "userSetupAllowed": true,
          "autheticatorFlow": false
        }
      ]
    }
  ],
  "clients": [
    {
      "clientId": "moped-client",
      "authenticationFlowBindingOverrides": {
        "browser": "ad7d518c-4129-483a-8351-e1223cb8eead"
      },
      "name": "moped-client",
      "description": "Moped-Client",
      "enabled": true,
      "clientAuthenticatorType": "client-secret",
      "secret": "changed-special-client-secret",
      "redirectUris": [
        "https://moped-client.org/redirect"
      ],
      "webOrigins": [
        "https://moped-client.org/webOrigin"
      ]
    }
  ]
}

Expected behavior
The configuration should probably use authentication flow aliases and resolve them to ids before making the calls to Keycloak.

{
  "enabled": true,
  "realm": "realmWithFlow",
  "authenticationFlows": [
    {
      "alias": "my auth flow",
      "description": "My auth flow for testing",
      "providerId": "basic-flow",
      "topLevel": true,
      "builtIn": false,
      "authenticationExecutions": [
        {
          "authenticator": "docker-http-basic-authenticator",
          "requirement": "DISABLED",
          "priority": 0,
          "userSetupAllowed": true,
          "autheticatorFlow": false
        }
      ]
    }
  ],
  "clients": [
    {
      "clientId": "moped-client",
      "authenticationFlowBindingOverrides": {
        "browser": "my auth flow"
      },
      "name": "moped-client",
      "description": "Moped-Client",
      "enabled": true,
      "clientAuthenticatorType": "client-secret",
      "secret": "changed-special-client-secret",
      "redirectUris": [
        "https://moped-client.org/redirect"
      ],
      "webOrigins": [
        "https://moped-client.org/webOrigin"
      ]
    }
  ]
}

Environment (please complete the following information)

  • Keycloak Version: 11.0.2
  • keycloak-config-cli Version: v2.1.0-11.0.0
  • Java Version: 11

Updating of ClientScope doen't include creating new ProtocolMappers

Describe the bug
I need to update std "profile" client scope with new protocol mapper. According to supported features this thing should work.

To Reproduce
Trying on keycloak 8 with latest version of your cli.
After importing empty realm, kc creates default clientScopes, one of them is profile. Then I import realm with next clientScopes

"clientScopes": [
        {
            "name": "profile",
            "protocolMappers": [
                {
                    "id": "tmp",
                    "name": "tmp",
                    "protocol": "openid-connect",
                    "protocolMapper": "oidc-usermodel-attribute-mapper",
                    "config": {
                        "user.attribute": "name",
                        "claim.name":  "tmp",
                        "access.token.claim":  "true",
                        "id.token.claim": "true",
                        "userinfo.token.claim": "true",
                        "jsonType.label":"String"
                    }
                }
            ]
        }
    ], 

also, if i will add id of clientScope it will ignore that.
Also I tried to create new clientScope with same configs as default profile and got 409 from server.

Expected behavior
It should insert/update protocol mapper into clientScope.

Environment (please complete the following information):

  • Keycloak Version: 8
  • keycloak-config-cli Version: latest
  • Java Version: 11

Import checksums need to be calculated after substitution

Describe the bug
If substitution is enabled, keycloak-config-cli will skip the import no matter if the substitution variables has been changed or not.

To Reproduce

Realm:

{
  "id": "app",
  "realm": "My App",
  "enabled": true,
  "clients": [
    {
      "clientId": "api",
      "name": "API",
      "enabled": true,
      "clientAuthenticatorType": "client-secret",
      "secret": "${env:API_CLIENT_SECRET}",
      "webOrigins": ["+"],
      "standardFlowEnabled": true,
      "implicitFlowEnabled": false,
      "serviceAccountsEnabled": true
    }
  ]
}

Run keycloak-config-cli with --import.var-substitution=true After first import, change API_CLIENT_SECRET. Since the file has not been changed, no import happens.

Expected behavior
Do an import if external variables has been changed

Environment (please complete the following information):

  • Keycloak Version: 11
  • keycloak-config-cli Version: 2.5.0
  • Java Version: 11

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.