diff --git a/REFERENCE.md b/REFERENCE.md index fe6960a..44df63a 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -1,3075 +1,3087 @@ # Reference ## Table of Contents **Classes** _Public Classes_ * [`keycloak`](#keycloak): Manage Keycloak * [`keycloak::config`](#keycloakconfig): Private class. * [`keycloak::datasource::h2`](#keycloakdatasourceh2): Private class. * [`keycloak::install`](#keycloakinstall): Private class. * [`keycloak::service`](#keycloakservice): Private class. * [`keycloak::sssd`](#keycloaksssd): Private class. _Private Classes_ * `keycloak::datasource::mysql`: Manage MySQL datasource * `keycloak::datasource::oracle`: Manage Oracle datasource * `keycloak::datasource::postgresql`: Manage postgresql datasource * `keycloak::resources`: Define Keycloak resources **Defined types** * [`keycloak::client_scope::oidc`](#keycloakclient_scopeoidc): Manage Keycloak OpenID Connect client scope using built-in mappers * [`keycloak::client_scope::saml`](#keycloakclient_scopesaml): Manage Keycloak SAML client scope using built-in mappers * [`keycloak::spi_deployment`](#keycloakspi_deployment): Manage Keycloak SPI deployment * [`keycloak::truststore::host`](#keycloaktruststorehost): Add host to Keycloak truststore **Resource types** * [`keycloak_api`](#keycloak_api): Type that configures API connection parameters for other keycloak types that use the Keycloak API. * [`keycloak_client`](#keycloak_client): Manage Keycloak clients * [`keycloak_client_protocol_mapper`](#keycloak_client_protocol_mapper): Manage Keycloak protocol mappers * [`keycloak_client_scope`](#keycloak_client_scope): Manage Keycloak client scopes * [`keycloak_conn_validator`](#keycloak_conn_validator): Verify that a connection can be successfully established between a node and the keycloak server. Its primary use is as a precondition to pre * [`keycloak_flow`](#keycloak_flow): Manage a Keycloak flow **Autorequires** * `keycloak_realm` defined for `realm` parameter * `keycloak_flow` of `flow_alias` if `top_level=fals * [`keycloak_flow_execution`](#keycloak_flow_execution): Manage a Keycloak flow **Autorequires** * `keycloak_realm` defined for `realm` parameter * `keycloak_flow` of value defined for `flow_alias` * [`keycloak_identity_provider`](#keycloak_identity_provider): Manage Keycloak identity providers * [`keycloak_ldap_mapper`](#keycloak_ldap_mapper): Manage Keycloak LDAP attribute mappers * [`keycloak_ldap_user_provider`](#keycloak_ldap_user_provider): Manage Keycloak LDAP user providers * [`keycloak_protocol_mapper`](#keycloak_protocol_mapper): Manage Keycloak client scope protocol mappers * [`keycloak_realm`](#keycloak_realm): Manage Keycloak realms * [`keycloak_required_action`](#keycloak_required_action): Manage Keycloak required actions * [`keycloak_resource_validator`](#keycloak_resource_validator): Verify that a specific Keycloak resource is available * [`keycloak_sssd_user_provider`](#keycloak_sssd_user_provider): Manage Keycloak SSSD user providers ## Classes ### keycloak Manage Keycloak #### Examples ##### ```puppet include ::keycloak ``` #### Parameters The following parameters are available in the `keycloak` class. ##### `manage_install` Data type: `Boolean` Install Keycloak from upstream Keycloak tarball. Set to false to manage installation of Keycloak outside this module and set $install_dir to match. Defaults to true. Default value: `true` ##### `version` Data type: `String` Version of Keycloak to install and manage. Default value: '8.0.1' ##### `package_url` Data type: `Optional[Variant[Stdlib::HTTPUrl, Stdlib::HTTPSUrl]]` URL of the Keycloak download. Default is based on version. Default value: `undef` ##### `install_dir` Data type: `Optional[Stdlib::Absolutepath]` The directory of where to install Keycloak. Default is `/opt/keycloak-${version}`. Default value: `undef` ##### `service_name` Data type: `String` Keycloak service name. Default is `keycloak`. Default value: 'keycloak' ##### `service_ensure` Data type: `String` Keycloak service ensure property. Default is `running`. Default value: 'running' ##### `service_enable` Data type: `Boolean` Keycloak service enable property. Default is `true`. Default value: `true` ##### `service_hasstatus` Data type: `Boolean` Keycloak service hasstatus parameter. Default is `true`. Default value: `true` ##### `service_hasrestart` Data type: `Boolean` Keycloak service hasrestart parameter. Default is `true`. Default value: `true` ##### `service_bind_address` Data type: `Stdlib::IP::Address` Bind address for Keycloak service. Default is '0.0.0.0'. Default value: '0.0.0.0' ##### `java_opts` Data type: `Optional[Variant[String, Array]]` Sets additional options to Java virtual machine environment variable. Default value: `undef` ##### `java_opts_append` Data type: `Boolean` Determine if $JAVA_OPTS should be appended to when setting `java_opts` parameter Default value: `true` ##### `service_extra_opts` Data type: `Optional[String]` Additional options added to the end of the service command-line. Default value: `undef` ##### `manage_user` Data type: `Boolean` Defines if the module should manage the Linux user for Keycloak installation Default value: `true` ##### `user` Data type: `String` Keycloak user name. Default is `keycloak`. Default value: 'keycloak' ##### `user_shell` Data type: `Stdlib::Absolutepath` Keycloak user shell. Default value: '/sbin/nologin' ##### `group` Data type: `String` Keycloak user group name. Default is `keycloak`. Default value: 'keycloak' ##### `user_uid` Data type: `Optional[Integer]` Keycloak user UID. Default is `undef`. Default value: `undef` ##### `group_gid` Data type: `Optional[Integer]` Keycloak user group GID. Default is `undef`. Default value: `undef` ##### `system_user` Data type: `Boolean` If keycloak user should be a system user with lower uid and gid. Default is `true` Default value: `true` ##### `admin_user` Data type: `String` Keycloak administrative username. Default is `admin`. Default value: 'admin' ##### `admin_user_password` Data type: `String` Keycloak administrative user password. Default is `changeme`. Default value: 'changeme' ##### `manage_datasource` Data type: `Boolean` Boolean that determines if configured datasource will be managed. Default is `true`. Default value: `true` ##### `datasource_driver` Data type: `Enum['h2', 'mysql', 'oracle', 'postgresql']` Datasource driver to use for Keycloak. Valid values are `h2`, `mysql`, 'oracle' and 'postgresql' Default is `h2`. Default value: 'h2' ##### `datasource_host` Data type: `Optional[String]` Datasource host. Only used when datasource_driver is `mysql`, 'oracle' or 'postgresql' Default is `localhost` for MySQL. Default value: `undef` ##### `datasource_port` Data type: `Optional[Integer]` Datasource port. Only used when datasource_driver is `mysql`, 'oracle' or 'postgresql' Default is `3306` for MySQL. Default value: `undef` ##### `datasource_url` Data type: `Optional[String]` Datasource url. Default datasource URLs are defined in init class. Default value: `undef` ##### `datasource_dbname` Data type: `String` Datasource database name. Default is `keycloak`. Default value: 'keycloak' ##### `datasource_username` Data type: `String` Datasource user name. Default is `sa`. Default value: 'sa' ##### `datasource_password` Data type: `String` Datasource user password. Default is `sa`. Default value: 'sa' ##### `datasource_package` Data type: `Optional[String]` Package to add specified datasource support Default value: `undef` ##### `datasource_jar_source` Data type: `Optional[String]` Source for datasource JDBC driver - could be puppet link or local file on the node. Default is dependent on value for `datasource_driver`. This parameter is required if `datasource_driver` is `oracle`. Default value: `undef` ##### `datasource_module_source` Data type: `Optional[String]` Source for datasource module.xml. Default depends on `datasource_driver`. Default value: `undef` ##### `datasource_xa_class` Data type: `Optional[String]` MySQL Connector/J JDBC driver xa-datasource class name Default value: `undef` ##### `proxy_https` Data type: `Boolean` Boolean that sets if HTTPS proxy should be enabled. Set to `true` if proxying traffic through Apache. Default is `false`. Default value: `false` ##### `truststore` Data type: `Boolean` Boolean that sets if truststore should be used. Default is `false`. Default value: `false` ##### `truststore_hosts` Data type: `Hash` Hash that is used to define `keycloak::turststore::host` resources. Default is `{}`. Default value: {} ##### `truststore_password` Data type: `String` Truststore password. Default is `keycloak`. Default value: 'keycloak' ##### `truststore_hostname_verification_policy` Data type: `Enum['WILDCARD', 'STRICT', 'ANY']` Valid values are `WILDCARD`, `STRICT`, and `ANY`. Default is `WILDCARD`. Default value: 'WILDCARD' ##### `http_port` Data type: `Integer` HTTP port used by Keycloak. Default is `8080`. Default value: 8080 ##### `theme_static_max_age` Data type: `Integer` Max cache age in seconds of static content. Default is `2592000`. Default value: 2592000 ##### `theme_cache_themes` Data type: `Boolean` Boolean that sets if themes should be cached. Default is `true`. Default value: `true` ##### `theme_cache_templates` Data type: `Boolean` Boolean that sets if templates should be cached. Default is `true`. Default value: `true` ##### `realms` Data type: `Hash` Hash that is used to define keycloak_realm resources. Default is `{}`. Default value: {} ##### `realms_merge` Data type: `Boolean` Boolean that sets if `realms` should be merged from Hiera. Default value: `false` ##### `oidc_client_scopes` Data type: `Hash` Hash that is used to define keycloak::client_scope::oidc resources. Default is `{}`. Default value: {} ##### `oidc_client_scopes_merge` Data type: `Boolean` Boolean that sets if `oidc_client_scopes` should be merged from Hiera. Default value: `false` ##### `saml_client_scopes` Data type: `Hash` Hash that is used to define keycloak::client_scope::saml resources. Default is `{}`. Default value: {} ##### `saml_client_scopes_merge` Data type: `Boolean` Boolean that sets if `saml_client_scopes` should be merged from Hiera. Default value: `false` ##### `identity_providers` Data type: `Hash` Hash that is used to define keycloak_identity_provider resources. Default value: {} ##### `identity_providers_merge` Data type: `Boolean` Boolean that sets if `identity_providers` should be merged from Hiera. Default value: `false` ##### `client_scopes` Data type: `Hash` Hash that is used to define keycloak_client_scope resources. Default value: {} ##### `client_scopes_merge` Data type: `Boolean` Boolean that sets if `client_scopes` should be merged from Hiera. Default value: `false` ##### `protocol_mappers` Data type: `Hash` Hash that is used to define keycloak_protocol_mapper resources. Default value: {} ##### `protocol_mappers_merge` Data type: `Boolean` Boolean that sets if `protocol_mappers` should be merged from Hiera. Default value: `false` ##### `clients` Data type: `Hash` Hash that is used to define keycloak_client resources. Default value: {} ##### `clients_merge` Data type: `Boolean` Boolean that sets if `clients` should be merged from Hiera. Default value: `false` ##### `flows` Data type: `Hash` Hash taht is used to define keycloak_flow resources. Default value: {} ##### `flows_merge` Data type: `Boolean` Boolean that sets if `flows` should be merged from Hiera. Default value: `false` ##### `flow_executions` Data type: `Hash` Hash taht is used to define keycloak_flow resources. Default value: {} ##### `flow_executions_merge` Data type: `Boolean` Boolean that sets if `flows` should be merged from Hiera. Default value: `false` ##### `required_actions` Data type: `Hash` Hash that is used to define keycloak_required_action resources. Default value: {} ##### `required_actions_merge` Data type: `Boolean` Boolean that sets if `required_actions` should be merged from Hiera. Default value: `false` ##### `ldap_mappers` Data type: `Hash` Hash that is used to define keycloak_ldap_mapper resources. Default value: {} ##### `ldap_mappers_merge` Data type: `Boolean` Boolean that sets if `ldap_mappers` should be merged from Hiera. Default value: `false` ##### `ldap_user_providers` Data type: `Hash` Hash that is used to define keycloak_ldap_user_provider resources. Default value: {} ##### `ldap_user_providers_merge` Data type: `Boolean` Boolean that sets if `ldap_user_providers` should be merged from Hiera. Default value: `false` ##### `with_sssd_support` Data type: `Boolean` Boolean that determines if SSSD user provider support should be available Default value: `false` ##### `libunix_dbus_java_source` Data type: `Variant[Stdlib::HTTPUrl, Stdlib::HTTPSUrl]` Source URL of libunix-dbus-java Default value: 'https://github.com/keycloak/libunix-dbus-java/archive/libunix-dbus-java-0.8.0.tar.gz' ##### `install_libunix_dbus_java_build_dependencies` Data type: `Boolean` Boolean that determines of libunix-dbus-java build dependencies are managed by this module Default value: `true` ##### `libunix_dbus_java_build_dependencies` Data type: `Array` Packages needed to build libunix-dbus-java Default value: [] ##### `libunix_dbus_java_libdir` Data type: `Stdlib::Absolutepath` Path to directory to install libunix-dbus-java libraries Default value: '/usr/lib64' ##### `jna_package_name` Data type: `String` Package name for jna Default value: 'jna' ##### `manage_sssd_config` Data type: `Boolean` Boolean that determines if SSSD ifp config for Keycloak is managed Default value: `true` ##### `sssd_ifp_user_attributes` Data type: `Array` user_attributes to define for SSSD ifp service Default value: [] ##### `restart_sssd` Data type: `Boolean` Boolean that determines if SSSD should be restarted Default value: `true` ##### `service_environment_file` Data type: `Optional[Stdlib::Absolutepath]` Path to the file with environment variables for the systemd service Default value: `undef` ##### `operating_mode` Data type: `Enum['standalone', 'clustered']` Keycloak operating mode deployment Default value: 'standalone' ##### `enable_jdbc_ping` Data type: `Boolean` Use JDBC_PING to discover the nodes and manage the replication of data More info: http://jgroups.org/manual/#_jdbc_ping Only applies when `operating_mode` is `clustered` JDBC_PING uses port 7600 to ensure cluster members are discoverable by each other This module does not manage firewall changes Default value: `false` ##### `jboss_bind_public_address` Data type: `Stdlib::IP::Address` JBoss bind public IP address Default value: $facts['networking']['ip'] ##### `jboss_bind_private_address` Data type: `Stdlib::IP::Address` JBoss bind private IP address Default value: $facts['networking']['ip'] ##### `user_cache` Data type: `Boolean` Boolean that determines if userCache is enabled Default value: `true` ##### `tech_preview_features` Data type: `Array` List of technology Preview features to enable Default value: [] ##### `auto_deploy_exploded` Data type: `Boolean` Set if exploded deployements will be auto deployed Default value: `false` ##### `auto_deploy_zipped` Data type: `Boolean` Set if zipped deployments will be auto deployed Default value: `true` ##### `spi_deployments` Data type: `Hash` Hash used to define keycloak::spi_deployment resources Default value: {} ##### `custom_config_content` Data type: `Optional[String]` Custom configuration content to be added to config.cli Default value: `undef` ##### `custom_config_source` Data type: `Optional[Variant[String, Array]]` Custom configuration source file to be added to config.cli Default value: `undef` ### keycloak::config Private class. ### keycloak::datasource::h2 Private class. ### keycloak::install Private class. ### keycloak::service Private class. ### keycloak::sssd Private class. ## Defined types ### keycloak::client_scope::oidc Manage Keycloak OpenID Connect client scope using built-in mappers #### Examples ##### ```puppet keycloak::client_scope::oidc { 'oidc-clients': realm => 'test', } ``` #### Parameters The following parameters are available in the `keycloak::client_scope::oidc` defined type. ##### `realm` Data type: `String` Realm of the client scope. ##### `resource_name` Data type: `String` Name of the client scope resource Default value: $name ### keycloak::client_scope::saml Manage Keycloak SAML client scope using built-in mappers #### Examples ##### ```puppet keycloak::client_scope::saml { 'saml-clients': realm => 'test', } ``` #### Parameters The following parameters are available in the `keycloak::client_scope::saml` defined type. ##### `realm` Data type: `String` Realm of the client scope. ##### `resource_name` Data type: `String` Name of the client scope resource Default value: $name ### keycloak::spi_deployment } #### Examples ##### Add Duo SPI ```puppet keycloak::spi_deployment { 'duo-spi': ensure => 'present', deployed_name => 'keycloak-duo-spi-jar-with-dependencies.jar', source => 'file:///path/to/source/keycloak-duo-spi-jar-with-dependencies.jar', } ``` ##### Add Duo SPI and check API for existance of resources before going onto dependenct resources ```puppet keycloak::spi_deployment { 'duo-spi': deployed_name => 'keycloak-duo-spi-jar-with-dependencies.jar', source => 'file:///path/to/source/keycloak-duo-spi-jar-with-dependencies.jar', test_url => 'authentication/authenticator-providers', test_key => 'id', test_value => 'duo-mfa-authenticator', test_realm => 'test', before => Keycloak_flow_execution['duo-mfa-authenticator under form-browser-with-duo on test'], ``` #### Parameters The following parameters are available in the `keycloak::spi_deployment` defined type. ##### `ensure` Data type: `Enum['present', 'absent']` State of the deployment Default value: 'present' ##### `deployed_name` Data type: `String[1]` Name of the file to be deployed. Defaults to `$name`. Default value: $name ##### `source` Data type: `Variant[Stdlib::Filesource, Stdlib::HTTPSUrl]` Source of the deployment, supports 'file://', 'puppet://', 'https://' or 'http://' ##### `test_url` Data type: `Optional[String]` URL to test for existance of resources created by this SPI Default value: `undef` ##### `test_key` Data type: `Optional[String]` Key of resource when testing for resource created by this SPI Default value: `undef` ##### `test_value` Data type: `Optional[String]` Value of the `test_key` when testing for resources created by this SPI Default value: `undef` ##### `test_realm` Data type: `Optional[String]` Realm to query when looking for resources created by this SPI Default value: `undef` ### keycloak::truststore::host Add host to Keycloak truststore #### Examples ##### ```puppet keycloak::truststore::host { 'ldap1.example.com': certificate => '/etc/openldap/certs/0a00000.0', } ``` #### Parameters The following parameters are available in the `keycloak::truststore::host` defined type. ##### `certificate` Data type: `String` Path to host certificate ##### `ensure` Data type: `Enum['latest', 'present', 'absent']` Host ensure value passed to `java_ks` resource. Default value: 'latest' ## Resource types ### keycloak_api Type that configures API connection parameters for other keycloak types that use the Keycloak API. #### Examples ##### Define API access ```puppet keycloak_api { 'keycloak' install_dir => '/opt/keycloak', server => 'http://localhost:8080/auth', realm => 'master', user => 'admin', password => 'changeme', } ``` #### Parameters The following parameters are available in the `keycloak_api` type. ##### `name` namevar Keycloak API config ##### `install_dir` Install location of Keycloak Default value: /opt/keycloak ##### `server` Auth URL for Keycloak server Default value: http://localhost:8080/auth ##### `realm` Realm for authentication Default value: master ##### `user` User for authentication Default value: admin ##### `password` Password for authentication Default value: changeme ##### `use_wrapper` Valid values: `true`, `false` Boolean that determines if kcadm_wrapper.sh should be used Default value: `false` ### keycloak_client Manage Keycloak clients #### Examples ##### Add a OpenID Connect client ```puppet keycloak_client { 'www.example.com': ensure => 'present', realm => 'test', redirect_uris => [ "https://www.example.com/oidc", "https://www.example.com", ], default_client_scopes => ['profile','email'], secret => 'supersecret', } ``` #### Properties The following properties are available in the `keycloak_client` type. ##### `ensure` Valid values: present, absent The basic property that the resource should be in. Default value: present ##### `protocol` Valid values: openid-connect, saml protocol Default value: openid-connect ##### `client_authenticator_type` clientAuthenticatorType Default value: client-secret ##### `default_client_scopes` defaultClientScopes Default value: [] ##### `optional_client_scopes` optionalClientScopes Default value: [] ##### `full_scope_allowed` Valid values: `true`, `false` fullScopeAllowed Default value: true ##### `enabled` Valid values: `true`, `false` enabled Default value: true ##### `standard_flow_enabled` Valid values: `true`, `false` standardFlowEnabled Default value: true ##### `implicit_flow_enabled` Valid values: `true`, `false` implicitFlowEnabled Default value: false ##### `direct_access_grants_enabled` Valid values: `true`, `false` enabled Default value: true ##### `service_accounts_enabled` Valid values: `true`, `false` serviceAccountsEnabled Default value: false ##### `authorization_services_enabled` Valid values: `true`, `false` authorizationServicesEnabled Default value: false ##### `public_client` Valid values: `true`, `false` enabled Default value: false ##### `root_url` rootUrl ##### `redirect_uris` redirectUris Default value: [] ##### `base_url` baseUrl ##### `web_origins` webOrigins Default value: [] ##### `login_theme` login_theme Default value: absent ##### `access_token_lifespan` access.token.lifespan ##### `browser_flow` authenticationFlowBindingOverrides.browser (Use flow alias, not ID) Default value: absent ##### `direct_grant_flow` authenticationFlowBindingOverrides.direct_grant (Use flow alias, not ID) Default value: absent +##### `roles` + +List of non composite client roles to define. + +Default value: [] + #### Parameters The following parameters are available in the `keycloak_client` type. ##### `name` namevar The client name ##### `client_id` clientId. Defaults to `name`. ##### `id` Id. Defaults to `client_id` ##### `realm` realm ##### `secret` secret ### keycloak_client_protocol_mapper Manage Keycloak protocol mappers #### Examples ##### Add email protocol mapper to test.example.com client in realm test ```puppet keycloak_client_protocol_mapper { "email for test.example.com on test": claim_name => 'email', user_attribute => 'email', } ``` #### Properties The following properties are available in the `keycloak_client_protocol_mapper` type. ##### `ensure` Valid values: present, absent The basic property that the resource should be in. Default value: present ##### `protocol` Valid values: openid-connect, saml protocol Default value: openid-connect ##### `user_attribute` user.attribute. Default to `resource_name` for `type` `oidc-usermodel-property-mapper` or `saml-user-property-mapper` ##### `json_type_label` json.type.label. Default to `String` for `type` `oidc-usermodel-property-mapper` and `oidc-group-membership-mapper`. ##### `full_path` Valid values: `true`, `false` full.path. Default to `false` for `type` `oidc-group-membership-mapper`. ##### `friendly_name` friendly.name. Default to `resource_name` for `type` `saml-user-property-mapper`. ##### `attribute_name` attribute.name Default to `resource_name` for `type` `saml-user-property-mapper`. ##### `claim_name` claim.name ##### `id_token_claim` Valid values: `true`, `false` id.token.claim. Default to `true` for `protocol` `openid-connect`. ##### `access_token_claim` Valid values: `true`, `false` access.token.claim. Default to `true` for `protocol` `openid-connect`. ##### `userinfo_token_claim` Valid values: `true`, `false` userinfo.token.claim. Default to `true` for `protocol` `openid-connect` except `type` of `oidc-audience-mapper`. ##### `attribute_nameformat` attribute.nameformat ##### `single` Valid values: `true`, `false` single. Default to `false` for `type` `saml-role-list-mapper`. ##### `script` Script, only valid for `type` of `saml-javascript-mapper`' Array values will be joined with newlines. Strings will be kept unchanged. ##### `included_client_audience` included.client.audience Required for `type` of `oidc-audience-mapper` #### Parameters The following parameters are available in the `keycloak_client_protocol_mapper` type. ##### `name` namevar The protocol mapper name ##### `id` Id. ##### `resource_name` The protocol mapper name. Defaults to `name`. ##### `client` client ##### `realm` realm ##### `type` Valid values: oidc-usermodel-property-mapper, oidc-full-name-mapper, oidc-group-membership-mapper, oidc-audience-mapper, saml-user-property-mapper, saml-role-list-mapper protocolMapper. Default is `oidc-usermodel-property-mapper` for `protocol` `openid-connect` and `saml-user-property-mapper` for `protocol` `saml`. ### keycloak_client_scope Manage Keycloak client scopes #### Examples ##### Define a OpenID Connect client scope in the test realm ```puppet keycloak_client_scope { 'email on test': protocol => 'openid-connect', } ``` #### Properties The following properties are available in the `keycloak_client_scope` type. ##### `ensure` Valid values: present, absent The basic property that the resource should be in. Default value: present ##### `protocol` Valid values: openid-connect, saml protocol Default value: openid-connect ##### `consent_screen_text` consent.screen.text ##### `display_on_consent_screen` Valid values: `true`, `false` display.on.consent.screen Default value: true #### Parameters The following parameters are available in the `keycloak_client_scope` type. ##### `name` namevar The client scope name ##### `resource_name` The client scope name. Defaults to `name`. ##### `id` Id. Defaults to `resource_name`. ##### `realm` realm ### keycloak_conn_validator Verify that a connection can be successfully established between a node and the keycloak server. Its primary use is as a precondition to prevent configuration changes from being applied if the keycloak server cannot be reached, but it could potentially be used for other purposes such as monitoring. #### Properties The following properties are available in the `keycloak_conn_validator` type. ##### `ensure` Valid values: present, absent The basic property that the resource should be in. Default value: present #### Parameters The following parameters are available in the `keycloak_conn_validator` type. ##### `name` namevar An arbitrary name used as the identity of the resource. ##### `keycloak_server` The DNS name or IP address of the server where keycloak should be running. Default value: localhost ##### `keycloak_port` The port that the keycloak server should be listening on. Default value: 8080 ##### `use_ssl` Whether the connection will be attemped using https Default value: `false` ##### `test_url` URL to use for testing if the Keycloak database is up Default value: /auth/admin/serverinfo ##### `timeout` The max number of seconds that the validator should wait before giving up and deciding that keycloak is not running; defaults to 15 seconds. Default value: 30 ### keycloak_flow Manage a Keycloak flow **Autorequires** * `keycloak_realm` defined for `realm` parameter * `keycloak_flow` of `flow_alias` if `top_level=false` * `keycloak_flow` of `flow_alias` if other `index` is lower and if `top_level=false` * `keycloak_flow_execution` if `flow_alias` is the same and other `index` is lower and if `top_level=false` #### Examples ##### Add custom flow ```puppet keycloak_flow { 'browser-with-duo': ensure => 'present', realm => 'test', } ``` ##### Add a flow execution to existing browser-with-duo flow ```puppet keycloak_flow { 'form-browser-with-duo under browser-with-duo on test': ensure => 'present', index => 2, requirement => 'ALTERNATIVE', top_level => false, } ``` #### Properties The following properties are available in the `keycloak_flow` type. ##### `ensure` Valid values: present, absent The basic property that the resource should be in. Default value: present ##### `index` execution index, only applied to top_level=false, required for top_level=false ##### `description` description ##### `requirement` Valid values: DISABLED, ALTERNATIVE, REQUIRED, CONDITIONAL, disabled, alternative, required, conditional requirement, only applied to top_level=false and defaults to DISABLED #### Parameters The following parameters are available in the `keycloak_flow` type. ##### `name` namevar The flow name ##### `id` Id. Default to `$alias-$realm` when top_level is true. Only applies to top_level=true ##### `alias` Alias. Default to `name`. ##### `flow_alias` flowAlias, required for top_level=false ##### `realm` realm ##### `provider_id` Valid values: basic-flow, form-flow providerId Default value: basic-flow ##### `type` sub-flow execution provider, default to `registration-page-form` for top_level=false and does not apply to top_level=true ##### `top_level` Valid values: `true`, `false` topLevel Default value: `true` ### keycloak_flow_execution Manage a Keycloak flow **Autorequires** * `keycloak_realm` defined for `realm` parameter * `keycloak_flow` of value defined for `flow_alias` * `keycloak_flow` if they share same `flow_alias` value and the other resource `index` is lower * `keycloak_flow_execution` if `flow_alias` is the same and other `index` is lower #### Examples ##### Add an execution to a flow ```puppet keycloak_flow_execution { 'auth-cookie under browser-with-duo on test': ensure => 'present', configurable => false, display_name => 'Cookie', index => 0, requirement => 'ALTERNATIVE', } ``` ##### Add an execution to a execution flow that is one level deeper than top level ```puppet keycloak_flow_execution { 'auth-username-password-form under form-browser-with-duo on test': ensure => 'present', configurable => false, display_name => 'Username Password Form', index => 0, requirement => 'REQUIRED', } ``` ##### Add an execution with a configuration ```puppet keycloak_flow_execution { 'duo-mfa-authenticator under form-browser-with-duo on test': ensure => 'present', configurable => true, display_name => 'Duo MFA', alias => 'Duo', config => { "duomfa.akey" => "foo-akey", "duomfa.apihost" => "api-foo.duosecurity.com", "duomfa.skey" => "secret", "duomfa.ikey" => "foo-ikey", "duomfa.groups" => "duo" }, requirement => 'REQUIRED', index => 1, } ``` #### Properties The following properties are available in the `keycloak_flow_execution` type. ##### `ensure` Valid values: present, absent The basic property that the resource should be in. Default value: present ##### `index` execution index ##### `configurable` Valid values: `true`, `false` configurable ##### `requirement` Valid values: DISABLED, ALTERNATIVE, REQUIRED, CONDITIONAL, disabled, alternative, required, conditional requirement Default value: DISABLED ##### `config` execution config #### Parameters The following parameters are available in the `keycloak_flow_execution` type. ##### `name` namevar The flow execution name ##### `id` read-only Id ##### `provider_id` provider ##### `flow_alias` flowAlias ##### `realm` realm ##### `display_name` displayName ##### `alias` alias ##### `config_id` read-only config ID ### keycloak_identity_provider Manage Keycloak identity providers #### Examples ##### Add CILogon identity provider to test realm ```puppet keycloak_identity_provider { 'cilogon on test': ensure => 'present', display_name => 'CILogon', provider_id => 'oidc', first_broker_login_flow_alias => 'browser', client_id => 'cilogon:/client_id/foobar', client_secret => 'supersecret', user_info_url => 'https://cilogon.org/oauth2/userinfo', token_url => 'https://cilogon.org/oauth2/token', authorization_url => 'https://cilogon.org/authorize', } ``` #### Properties The following properties are available in the `keycloak_identity_provider` type. ##### `ensure` Valid values: present, absent The basic property that the resource should be in. Default value: present ##### `display_name` displayName ##### `enabled` Valid values: `true`, `false` enabled Default value: true ##### `update_profile_first_login_mode` Valid values: on, off updateProfileFirstLoginMode Default value: on ##### `trust_email` Valid values: `true`, `false` trustEmail Default value: false ##### `store_token` Valid values: `true`, `false` storeToken Default value: false ##### `add_read_token_role_on_create` Valid values: `true`, `false` addReadTokenRoleOnCreate Default value: false ##### `authenticate_by_default` Valid values: `true`, `false` authenticateByDefault Default value: false ##### `link_only` Valid values: `true`, `false` linkOnly Default value: false ##### `gui_order` guiOrder ##### `first_broker_login_flow_alias` firstBrokerLoginFlowAlias Default value: first broker login ##### `post_broker_login_flow_alias` postBrokerLoginFlowAlias ##### `sync_mode` Valid values: IMPORT, LEGACY, FORCE syncMode Default value: IMPORT ##### `hide_on_login_page` Valid values: `true`, `false` hideOnLoginPage Default value: false ##### `user_info_url` userInfoUrl ##### `validate_signature` Valid values: `true`, `false` validateSignature Default value: false ##### `client_id` clientId ##### `client_secret` clientSecret ##### `client_auth_method` Valid values: client_secret_post, client_secret_basic, client_secret_jwt, private_key_jwt clientAuthMethod Default value: client_secret_post ##### `token_url` tokenUrl ##### `ui_locales` Valid values: `true`, `false` uiLocales Default value: false ##### `backchannel_supported` Valid values: `true`, `false` backchannelSupported Default value: false ##### `use_jwks_url` Valid values: `true`, `false` useJwksUrl Default value: true ##### `jwks_url` jwksUrl ##### `login_hint` Valid values: `true`, `false` loginHint Default value: false ##### `authorization_url` authorizationUrl ##### `disable_user_info` Valid values: `true`, `false` disableUserInfo Default value: false ##### `logout_url` logoutUrl ##### `issuer` issuer ##### `default_scope` default_scope ##### `prompt` Valid values: none, consent, login, select_account prompt ##### `allowed_clock_skew` allowedClockSkew ##### `forward_parameters` forwardParameters #### Parameters The following parameters are available in the `keycloak_identity_provider` type. ##### `name` namevar The identity provider name ##### `alias` The identity provider name. Defaults to `name`. ##### `internal_id` internalId. Defaults to "`alias`-`realm`" ##### `realm` realm ##### `provider_id` Valid values: oidc, keycloak-oidc providerId Default value: oidc ### keycloak_ldap_mapper Manage Keycloak LDAP attribute mappers #### Examples ##### Add full name attribute mapping ```puppet keycloak_ldap_mapper { 'full name for LDAP-test on test: ensure => 'present', type => 'full-name-ldap-mapper', ldap_attribute => 'gecos', } ``` #### Properties The following properties are available in the `keycloak_ldap_mapper` type. ##### `ensure` Valid values: present, absent The basic property that the resource should be in. Default value: present ##### `ldap_attribute` ldap.attribute ##### `user_model_attribute` user.model.attribute ##### `is_mandatory_in_ldap` is.mandatory.in.ldap. Defaults to `false` unless `type` is `full-name-ldap-mapper`. ##### `always_read_value_from_ldap` Valid values: `true`, `false` always.read.value.from.ldap. Defaults to `true` if `type` is `user-attribute-ldap-mapper`. ##### `read_only` Valid values: `true`, `false` read.only ##### `write_only` Valid values: `true`, `false` write.only. Defaults to `false` if `type` is `full-name-ldap-mapper`. ##### `mode` Valid values: READ_ONLY, LDAP_ONLY mode, only for `type` of `group-ldap-mapper` and `role-ldap-mapper` ##### `membership_attribute_type` Valid values: DN, UID membership.attribute.type, only for `type` of `group-ldap-mapper` and `role-ldap-mapper` ##### `user_roles_retrieve_strategy` Valid values: LOAD_GROUPS_BY_MEMBER_ATTRIBUTE, GET_GROUPS_FROM_USER_MEMBEROF_ATTRIBUTE, LOAD_GROUPS_BY_MEMBER_ATTRIBUTE_RECURSIVELY, LOAD_ROLES_BY_MEMBER_ATTRIBUTE, GET_ROLES_FROM_USER_MEMBEROF_ATTRIBUTE, LOAD_ROLES_BY_MEMBER_ATTRIBUTE_RECURSIVELY user.roles.retrieve.strategy, only for `type` of `group-ldap-mapper` and `role-ldap-mapper` ##### `group_name_ldap_attribute` group.name.ldap.attribute, only for `type` of `group-ldap-mapper` ##### `ignore_missing_groups` Valid values: `true`, `false` ignore.missing.groups, only for `type` of `group-ldap-mapper` ##### `membership_user_ldap_attribute` membership.user.ldap.attribute, only for `type` of `group-ldap-mapper` and `role-ldap-mapper` ##### `membership_ldap_attribute` membership.ldap.attribute, only for `type` of `group-ldap-mapper` and `role-ldap-mapper` ##### `preserve_group_inheritance` Valid values: `true`, `false` preserve.group.inheritance, only for `type` of `group-ldap-mapper` ##### `groups_dn` groups.dn, only for `type` of `group-ldap-mapper` ##### `mapped_group_attributes` mapped.group.attributes, only for `type` of `group-ldap-mapper` ##### `groups_ldap_filter` groups.ldap.filter, only for `type` of `group-ldap-mapper` ##### `memberof_ldap_attribute` memberof.ldap.attribute, only for `type` of `group-ldap-mapper` and `role-ldap-mapper` ##### `group_object_classes` group.object.classes, only for `type` of `group-ldap-mapper` ##### `drop_non_existing_groups_during_sync` Valid values: `true`, `false` drop.non.existing.groups.during.sync, only for `type` of `group-ldap-mapper` ##### `roles_dn` roles.dn, only for `type` of `role-ldap-mapper` ##### `role_name_ldap_attribute` role.name.ldap.attribute, only for `type` of `role-ldap-mapper` ##### `role_object_classes` role.object.classes, only for `type` of `role-ldap-mapper` ##### `roles_ldap_filter` roles.ldap.filter, only for `type` of `role-ldap-mapper` ##### `use_realm_roles_mapping` Valid values: `true`, `false` use.realm.roles.mapping, only for `type` of `role-ldap-mapper` ##### `client_id` client.id, only for `type` of `role-ldap-mapper` #### Parameters The following parameters are available in the `keycloak_ldap_mapper` type. ##### `name` namevar The LDAP mapper name ##### `id` Id. ##### `resource_name` The LDAP mapper name. Defaults to `name` ##### `type` Valid values: user-attribute-ldap-mapper, full-name-ldap-mapper, group-ldap-mapper, role-ldap-mapper providerId Default value: user-attribute-ldap-mapper ##### `realm` realm ##### `ldap` parentId ### keycloak_ldap_user_provider Manage Keycloak LDAP user providers #### Examples ##### Add LDAP user provider to test realm ```puppet keycloak_ldap_user_provider { 'LDAP on test': ensure => 'present', users_dn => 'ou=People,dc=example,dc=com', connection_url => 'ldaps://ldap1.example.com:636 ldaps://ldap2.example.com:636', import_enabled => false, use_truststore_spi => 'never', } ``` #### Properties The following properties are available in the `keycloak_ldap_user_provider` type. ##### `ensure` Valid values: present, absent The basic property that the resource should be in. Default value: present ##### `enabled` Valid values: `true`, `false` enabled Default value: true ##### `auth_type` Valid values: none, simple authType Default value: none ##### `edit_mode` Valid values: READ_ONLY, WRITABLE, UNSYNCED editMode Default value: READ_ONLY ##### `vendor` Valid values: ad, rhds, tivoli, eDirectory, other vendor Default value: other ##### `use_truststore_spi` Valid values: always, ldapsOnly, never useTruststoreSpi Default value: ldapsOnly ##### `users_dn` usersDn ##### `connection_url` connectionUrl ##### `priority` priority Default value: 0 ##### `batch_size_for_sync` batchSizeForSync Default value: 1000 ##### `username_ldap_attribute` usernameLdapAttribute Default value: uid ##### `rdn_ldap_attribute` rdnLdapAttribute Default value: uid ##### `uuid_ldap_attribute` uuidLdapAttribute Default value: entryUUID ##### `bind_dn` bindDn ##### `bind_credential` bindCredential ##### `import_enabled` Valid values: `true`, `false` importEnabled Default value: true ##### `use_kerberos_for_password_authentication` Valid values: `true`, `false` useKerberosForPasswordAuthentication ##### `user_object_classes` userObjectClasses Default value: ['inetOrgPerson', 'organizationalPerson'] ##### `search_scope` Valid values: one, one_level, subtree, 1, 2, 1, 2 searchScope ##### `custom_user_search_filter` Valid values: %r{.*}, absent customUserSearchFilter Default value: absent ##### `trust_email` Valid values: `true`, `false` trustEmail Default value: false ##### `full_sync_period` fullSyncPeriod Default value: -1 ##### `changed_sync_period` changedSyncPeriod Default value: -1 #### Parameters The following parameters are available in the `keycloak_ldap_user_provider` type. ##### `name` namevar The LDAP user provider name ##### `resource_name` The LDAP user provider name. Defaults to `name`. ##### `id` Id. Defaults to "`resource_name`-`realm`" ##### `realm` parentId ### keycloak_protocol_mapper Manage Keycloak client scope protocol mappers #### Examples ##### Add email protocol mapper to oidc-client client scope in realm test ```puppet keycloak_protocol_mapper { "email for oidc-clients on test": claim_name => 'email', user_attribute => 'email', } ``` #### Properties The following properties are available in the `keycloak_protocol_mapper` type. ##### `ensure` Valid values: present, absent The basic property that the resource should be in. Default value: present ##### `protocol` Valid values: openid-connect, saml protocol Default value: openid-connect ##### `user_attribute` user.attribute. Default to `resource_name` for `type` `oidc-usermodel-property-mapper` or `saml-user-property-mapper` ##### `json_type_label` json.type.label. Default to `String` for `type` `oidc-usermodel-property-mapper` and `oidc-group-membership-mapper`. ##### `full_path` Valid values: `true`, `false` full.path. Default to `false` for `type` `oidc-group-membership-mapper`. ##### `friendly_name` friendly.name. Default to `resource_name` for `type` `saml-user-property-mapper`. ##### `attribute_name` attribute.name Default to `resource_name` for `type` `saml-user-property-mapper`. ##### `claim_name` claim.name ##### `id_token_claim` Valid values: `true`, `false` id.token.claim. Default to `true` for `protocol` `openid-connect`. ##### `access_token_claim` Valid values: `true`, `false` access.token.claim. Default to `true` for `protocol` `openid-connect`. ##### `userinfo_token_claim` Valid values: `true`, `false` userinfo.token.claim. Default to `true` for `protocol` `openid-connect` except `type` of `oidc-audience-mapper`. ##### `attribute_nameformat` attribute.nameformat ##### `single` Valid values: `true`, `false` single. Default to `false` for `type` `saml-role-list-mapper` or `saml-javascript-mapper`. ##### `script` Script, only valid for `type` of `saml-javascript-mapper`' Array values will be joined with newlines. Strings will be kept unchanged. ##### `included_client_audience` included.client.audience Required for `type` of `oidc-audience-mapper` #### Parameters The following parameters are available in the `keycloak_protocol_mapper` type. ##### `name` namevar The protocol mapper name ##### `id` Id. ##### `resource_name` The protocol mapper name. Defaults to `name`. ##### `client_scope` client scope ##### `realm` realm ##### `type` Valid values: oidc-usermodel-property-mapper, oidc-full-name-mapper, oidc-group-membership-mapper, oidc-audience-mapper, saml-user-property-mapper, saml-role-list-mapper protocolMapper. Default is `oidc-usermodel-property-mapper` for `protocol` `openid-connect` and `saml-user-property-mapper` for `protocol` `saml`. ### keycloak_realm Manage Keycloak realms #### Examples ##### Add a realm with a custom theme ```puppet keycloak_realm { 'test': ensure => 'present', remember_me => true, login_with_email_allowed => false, login_theme => 'my_theme', } ``` #### Properties The following properties are available in the `keycloak_realm` type. ##### `ensure` Valid values: present, absent The basic property that the resource should be in. Default value: present ##### `display_name` displayName ##### `display_name_html` displayNameHtml ##### `login_theme` loginTheme Default value: keycloak ##### `account_theme` accountTheme Default value: keycloak ##### `admin_theme` adminTheme Default value: keycloak ##### `email_theme` emailTheme Default value: keycloak ##### `internationalization_enabled` Valid values: `true`, `false` internationalizationEnabled Default value: false ##### `sso_session_idle_timeout` ssoSessionIdleTimeout ##### `sso_session_max_lifespan` ssoSessionMaxLifespan ##### `access_code_lifespan` accessCodeLifespan ##### `access_code_lifespan_user_action` accessCodeLifespanUserAction ##### `access_token_lifespan` accessTokenLifespan ##### `access_token_lifespan_for_implicit_flow` accessTokenLifespanForImplicitFlow ##### `enabled` Valid values: `true`, `false` enabled Default value: true ##### `remember_me` Valid values: `true`, `false` rememberMe Default value: false ##### `registration_allowed` Valid values: `true`, `false` registrationAllowed Default value: false ##### `login_with_email_allowed` Valid values: `true`, `false` loginWithEmailAllowed Default value: true ##### `reset_password_allowed` Valid values: `true`, `false` resetPasswordAllowed Default value: false ##### `verify_email` Valid values: `true`, `false` verifyEmail Default value: false ##### `browser_flow` browserFlow Default value: browser ##### `registration_flow` registrationFlow Default value: registration ##### `direct_grant_flow` directGrantFlow Default value: direct grant ##### `reset_credentials_flow` resetCredentialsFlow Default value: reset credentials ##### `client_authentication_flow` clientAuthenticationFlow Default value: clients ##### `docker_authentication_flow` dockerAuthenticationFlow Default value: docker auth ##### `default_client_scopes` Default Client Scopes ##### `optional_client_scopes` Optional Client Scopes ##### `supported_locales` Supported Locales ##### `content_security_policy` contentSecurityPolicy Default value: frame-src 'self'; frame-ancestors 'self'; object-src 'none'; ##### `events_enabled` Valid values: `true`, `false` eventsEnabled Default value: false ##### `events_expiration` eventsExpiration ##### `events_listeners` eventsListeners Default value: ['jboss-logging'] ##### `admin_events_enabled` Valid values: `true`, `false` adminEventsEnabled Default value: false ##### `admin_events_details_enabled` Valid values: `true`, `false` adminEventsDetailsEnabled Default value: false ##### `smtp_server_user` smtpServer user ##### `smtp_server_password` smtpServer password ##### `smtp_server_host` smtpServer host ##### `smtp_server_port` smtpServer port ##### `smtp_server_auth` Valid values: `true`, `false` smtpServer auth ##### `smtp_server_starttls` Valid values: `true`, `false` smtpServer starttls ##### `smtp_server_ssl` Valid values: `true`, `false` smtpServer ssl ##### `smtp_server_from` smtpServer from ##### `smtp_server_envelope_from` smtpServer envelope_from ##### `smtp_server_from_display_name` smtpServer fromDisplayName ##### `smtp_server_reply_to` smtpServer replyto ##### `smtp_server_reply_to_display_name` smtpServer replyToDisplayName ##### `brute_force_protected` Valid values: `true`, `false` bruteForceProtected +##### `roles` + +List of non composite realm roles to define. + +Default value: ['offline_access', 'uma_authorization'] + #### Parameters The following parameters are available in the `keycloak_realm` type. ##### `name` namevar The realm name ##### `id` Id. Default to `name`. ### keycloak_required_action Manage Keycloak required actions #### Examples ##### Enable Webauthn Register and make it default ```puppet keycloak_required_action { 'webauthn-register on master': ensure => present, provider_id => 'webauthn-register', display_name => 'Webauthn Register', default => true, enabled => true, priority => 1, config => { 'something' => 'true', # keep in mind that keycloak only supports strings for both keys and values 'smth else' => '1', }, alias => 'webauthn', } @example Minimal example to enable email verification without making it default keycloak_required_action { 'VERIFY_EMAIL on master': ensure => present, provider_id => 'webauthn-register', } ``` #### Properties The following properties are available in the `keycloak_required_action` type. ##### `ensure` Valid values: present, absent The basic property that the resource should be in. Default value: present ##### `display_name` Displayed name. Default to `provider_id` ##### `enabled` Valid values: `true`, `false` If the required action is enabled. Default to true. Default value: true ##### `alias` Alias. Default to `provider_id`. ##### `default` Valid values: `true`, `false` If the required action is a default one. Default to false Default value: false ##### `priority` Required action priority ##### `config` Required action config #### Parameters The following parameters are available in the `keycloak_required_action` type. ##### `name` namevar The required action name ##### `realm` realm ##### `provider_id` providerId of the required action ### keycloak_resource_validator Verify that a specific Keycloak resource is available #### Properties The following properties are available in the `keycloak_resource_validator` type. ##### `ensure` Valid values: present, absent The basic property that the resource should be in. Default value: present #### Parameters The following parameters are available in the `keycloak_resource_validator` type. ##### `name` namevar An arbitrary name used as the identity of the resource. ##### `test_url` URL to use for testing if the Keycloak database is up ##### `test_key` Key to lookup ##### `test_value` Value to lookup ##### `realm` Realm to query ##### `timeout` The max number of seconds that the validator should wait before giving up and deciding that keycloak is not running; defaults to 15 seconds. Default value: 30 ### keycloak_sssd_user_provider Manage Keycloak SSSD user providers #### Examples ##### Add SSSD user provider to test realm ```puppet keycloak_sssd_user_provider { 'SSSD on test': ensure => 'present', } ``` #### Properties The following properties are available in the `keycloak_sssd_user_provider` type. ##### `ensure` Valid values: present, absent The basic property that the resource should be in. Default value: present ##### `enabled` Valid values: `true`, `false` enabled Default value: true ##### `priority` priority Default value: 0 ##### `cache_policy` Valid values: DEFAULT, EVICT_DAILY, EVICT_WEEKLY, MAX_LIFESPAN, NO_CACHE cachePolicy Default value: DEFAULT ##### `eviction_day` evictionDay ##### `eviction_hour` evictionHour ##### `eviction_minute` evictionMinute ##### `max_lifespan` maxLifespan #### Parameters The following parameters are available in the `keycloak_sssd_user_provider` type. ##### `name` namevar The SSSD user provider name ##### `resource_name` The SSSD user provider name. Defaults to `name`. ##### `id` Id. Defaults to "`resource_name`-`realm`" ##### `realm` parentId diff --git a/lib/puppet/provider/keycloak_client/kcadm.rb b/lib/puppet/provider/keycloak_client/kcadm.rb index 3a2e87b..25e7682 100644 --- a/lib/puppet/provider/keycloak_client/kcadm.rb +++ b/lib/puppet/provider/keycloak_client/kcadm.rb @@ -1,373 +1,448 @@ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'keycloak_api')) Puppet::Type.type(:keycloak_client).provide(:kcadm, parent: Puppet::Provider::KeycloakAPI) do desc '' mk_resource_methods def attributes_properties [ :login_theme, :access_token_lifespan, ] end def dot_attributes_properties [ :access_token_lifespan, ] end def self.auth_flow_properties { browser_flow: 'browser', direct_grant_flow: 'direct_grant', } end def auth_flow_properties self.class.auth_flow_properties end def attribute_key(property) if dot_attributes_properties.include?(property) property.to_s.tr('_', '.') else property end end + def self.get_client_roles(realm, client) + output = kcadm('get', "clients/#{client}/roles", realm) + Puppet.debug("Client #{client} in realm #{realm} roles: #{output}") + data = JSON.parse(output) + roles = [] + data.each do |d| + # filter out 'uma_protection' client role created when + # authorization_services_enabled property is set to true + if !d['composite'] && d['name'] != 'uma_protection' + roles.push(d['name']) + end + end + Puppet.debug("Returned client roles: #{roles}") + roles + end + + def get_client_roles(*args) + self.class.get_client_roles(*args) + end + def self.instances clients = [] realms.each do |realm| output = kcadm('get', 'clients', realm) Puppet.debug("#{realm} clients: #{output}") begin data = JSON.parse(output) rescue JSON::ParserError Puppet.debug('Unable to parse output from kcadm get clients') data = [] end data.each do |d| # avoid built-in clients if d.key?('clientAuthenticatorType') && d['clientAuthenticatorType'] == 'client-secret' && !d.key?('name') begin secret_output = kcadm('get', "clients/#{d['id']}/client-secret", realm) rescue Puppet.debug("Unable to get clients/#{d['id']}/client-secret") secret_output = '{}' end secret_data = JSON.parse(secret_output) secret = secret_data['value'] else secret = nil end client = {} client[:ensure] = :present client[:id] = d['id'] client[:client_id] = d['clientId'] client[:realm] = realm client[:name] = "#{client[:client_id]} on #{client[:realm]}" type_properties.each do |property| + next if [:roles].include?(property) camel_key = camelize(property) dot_key = property.to_s.tr('_', '.') key = property.to_s attributes = d['attributes'] || {} auth_flows = d['authenticationFlowBindingOverrides'] || {} if property == :secret value = secret elsif d.key?(camel_key) value = d[camel_key] elsif attributes.key?(dot_key) value = attributes[dot_key] elsif attributes.key?(key) value = attributes[key] elsif auth_flows.key?(auth_flow_properties[property]) flow_alias = flow_ids(realm)[auth_flows[auth_flow_properties[property]]] value = flow_alias end if !!value == value # rubocop:disable Style/DoubleNegation value = value.to_s.to_sym end client[property.to_sym] = value end # The absence of a value should be 'absent' client[:login_theme] = 'absent' if client[:login_theme].nil? + client[:roles] = get_client_roles(realm, client[:id]) clients << new(client) end end clients end def self.prefetch(resources) clients = instances resources.keys.each do |name| provider = clients.find { |c| c.client_id == resources[name][:client_id] && c.realm == resources[name][:realm] } if provider resources[name].provider = provider end end end def scope_map return @scope_map if @scope_map output = kcadm('get', 'client-scopes', resource[:realm], nil, ['id', 'name']) begin data = JSON.parse(output) rescue JSON::ParserError Puppet.debug('Unable to parse output from kcadm get client-scopes') return {} end @scope_map = {} data.each do |d| @scope_map[d['name']] = d['id'] end @scope_map end def self.flow_ids(realm) @flow_ids = {} unless @flow_ids return @flow_ids[realm] if @flow_ids[realm] output = kcadm('get', 'authentication/flows', realm, nil, ['id', 'alias']) begin data = JSON.parse(output) rescue JSON::ParserError Puppet.debug('Unable to parse output from kcadm get authentication/flows') return {} end @flow_ids[realm] = {} data.each do |d| @flow_ids[realm][d['alias']] = d['id'] @flow_ids[realm][d['id']] = d['alias'] end @flow_ids[realm] end def flow_ids @flow_ids = {} unless @flow_ids return @flow_ids unless @flow_ids.empty? self.class.instance_variable_set(:@flow_ids, nil) @flow_ids = self.class.flow_ids(resource[:realm]) @flow_ids end def create raise(Puppet::Error, "Realm is mandatory for #{resource.type} #{resource.name}") if resource[:realm].nil? data = {} data[:id] = resource[:id] data[:clientId] = resource[:client_id] data[:secret] = resource[:secret] if resource[:secret] type_properties.each do |property| - next if [:default_client_scopes, :optional_client_scopes].include?(property) + next if [:default_client_scopes, :optional_client_scopes, :roles].include?(property) next unless resource[property.to_sym] value = convert_property_value(resource[property.to_sym]) next if value == 'absent' || value == :absent || value.nil? if attributes_properties.include?(property) unless data.key?(:attributes) data[:attributes] = {} end data[:attributes][attribute_key(property)] = value elsif auth_flow_properties.include?(property) unless data.key?(:authenticationFlowBindingOverrides) data[:authenticationFlowBindingOverrides] = {} end flow_id = flow_ids[value] data[:authenticationFlowBindingOverrides][auth_flow_properties[property]] = flow_id else data[camelize(property)] = value end end t = Tempfile.new('keycloak_client') t.write(JSON.pretty_generate(data)) t.close Puppet.debug(IO.read(t.path)) begin if resource[:login_theme] check_theme_exists(resource[:login_theme], "Keycloak_client[#{resource[:name]}]") end output = kcadm('create', 'clients', resource[:realm], t.path) Puppet.debug("create client output: #{output}") rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm create client failed\nError message: #{e.message}" end if resource[:default_client_scopes] || resource[:optional_client_scopes] client = JSON.parse(output) scope_id = nil end if resource[:default_client_scopes] remove_default_scopes = client['defaultClientScopes'] - resource[:default_client_scopes] begin remove_default_scopes.each do |s| scope_id = scope_map[s] kcadm('delete', "clients/#{resource[:id]}/default-client-scopes/#{scope_id}", resource[:realm]) end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm delete clients/#{resource[:id]}/default-client-scopes/#{scope_id}: #{e.message}" end end if resource[:optional_client_scopes] remove_optional_scopes = client['optionalClientScopes'] - resource[:optional_client_scopes] begin remove_optional_scopes.each do |s| scope_id = scope_map[s] kcadm('delete', "clients/#{resource[:id]}/optional-client-scopes/#{scope_id}", resource[:realm]) end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm delete clients/#{resource[:id]}/optional-client-scopes/#{scope_id}: #{e.message}" end end if resource[:default_client_scopes] add_default_scopes = resource[:default_client_scopes] - client['defaultClientScopes'] begin add_default_scopes.each do |s| scope_id = scope_map[s] kcadm('update', "clients/#{resource[:id]}/default-client-scopes/#{scope_id}", resource[:realm]) end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update clients/#{resource[:id]}/default-client-scopes/#{scope_id}: #{e.message}" end end if resource[:optional_client_scopes] add_optional_scopes = resource[:optional_client_scopes] - client['optionalClientScopes'] begin add_optional_scopes.each do |s| scope_id = scope_map[s] kcadm('update', "clients/#{resource[:id]}/optional-client-scopes/#{scope_id}", resource[:realm]) end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update clients/#{resource[:id]}/optional-client-scopes/#{scope_id}: #{e.message}" end end + role = nil + if resource[:roles] + roles = get_client_roles(resource[:realm], resource[:id]) + remove_roles = roles - resource[:roles] + begin + remove_roles.each do |s| + role = s + kcadm('delete', "clients/#{resource[:id]}/roles/#{role}", resource[:realm]) + end + rescue Puppet::ExecutionFailure => e + raise Puppet::Error, "kcadm delete realms/#{resource[:realm]}/clients/#{resource[:id]}/roles/#{role}: #{e.message}" + end + add_roles = resource[:roles] - roles + begin + add_roles.each do |s| + role = s + role_data = { 'description' => "${role_#{role}}", 'name' => role } + role_data_t = Tempfile.new('keycloak_client_role') + role_data_t.write(JSON.pretty_generate(role_data)) + role_data_t.close + Puppet.debug(IO.read(role_data_t.path)) + kcadm('create', "clients/#{resource[:id]}/roles", resource[:realm], role_data_t.path) + end + rescue Puppet::ExecutionFailure => e + raise Puppet::Error, "kcadm create realms/#{resource[:realm]}/clients/#{resource[:id]}/roles/#{role}: #{e.message}" + end + end @property_hash[:ensure] = :present end def destroy raise(Puppet::Error, "Realm is mandatory for #{resource.type} #{resource.name}") if resource[:realm].nil? begin kcadm('delete', "clients/#{id}", resource[:realm]) rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm delete realm failed\nError message: #{e.message}" end @property_hash.clear end def exists? @property_hash[:ensure] == :present end def initialize(value = {}) super(value) @property_flush = {} end type_properties.each do |prop| define_method "#{prop}=".to_sym do |value| @property_flush[prop] = value end end def flush unless @property_flush.empty? raise(Puppet::Error, "Realm is mandatory for #{resource.type} #{resource.name}") if resource[:realm].nil? data = {} data[:clientId] = resource[:client_id] data[:authenticationFlowBindingOverrides] = {} type_properties.each do |property| - next if [:default_client_scopes, :optional_client_scopes].include?(property) + next if [:default_client_scopes, :optional_client_scopes, :roles].include?(property) next unless @property_flush[property.to_sym] value = convert_property_value(@property_flush[property.to_sym]) value = nil if value.to_s == 'absent' if attributes_properties.include?(property) unless data.key?(:attributes) data[:attributes] = {} end data[:attributes][attribute_key(property)] = value elsif auth_flow_properties.include?(property) flow_id = value.nil? ? nil : flow_ids[value] data[:authenticationFlowBindingOverrides][auth_flow_properties[property]] = flow_id else data[camelize(property)] = value end end # Keycload API requires "serviceAccountsEnabled": true to be present in # the JSON when "authorizationServicesEnabled": true if data['authorizationServicesEnabled'] && data['serviceAccountsEnabled'].nil? data[:serviceAccountsEnabled] = true end # Only update if more than clientId set if data.keys.size > 1 t = Tempfile.new('keycloak_client') t.write(JSON.pretty_generate(data)) t.close Puppet.debug(IO.read(t.path)) begin if @property_flush[:login_theme] check_theme_exists(@property_flush[:login_theme], "Keycloak_client[#{resource[:name]}]") end kcadm('update', "clients/#{id}", resource[:realm], t.path) rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update client failed\nError message: #{e.message}" end end if @property_flush[:default_client_scopes] || @property_flush[:optional_client_scopes] scope_id = nil end if @property_flush[:default_client_scopes] remove_default_scopes = @property_hash[:default_client_scopes] - @property_flush[:default_client_scopes] begin remove_default_scopes.each do |s| scope_id = scope_map[s] kcadm('delete', "clients/#{id}/default-client-scopes/#{scope_id}", resource[:realm]) end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm delete clients/#{id}/default-client-scopes/#{scope_id}: #{e.message}" end end if @property_flush[:optional_client_scopes] remove_optional_scopes = @property_hash[:optional_client_scopes] - @property_flush[:optional_client_scopes] begin remove_optional_scopes.each do |s| scope_id = scope_map[s] kcadm('delete', "clients/#{id}/optional-client-scopes/#{scope_id}", resource[:realm]) end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm delete clients/#{id}/optional-client-scopes/#{scope_id}: #{e.message}" end end if @property_flush[:default_client_scopes] add_default_scopes = @property_flush[:default_client_scopes] - @property_hash[:default_client_scopes] begin add_default_scopes.each do |s| scope_id = scope_map[s] kcadm('update', "clients/#{id}/default-client-scopes/#{scope_id}", resource[:realm]) end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update clients/#{id}/default-client-scopes/#{scope_id}: #{e.message}" end end if @property_flush[:optional_client_scopes] add_optional_scopes = @property_flush[:optional_client_scopes] - @property_hash[:optional_client_scopes] begin add_optional_scopes.each do |s| scope_id = scope_map[s] kcadm('update', "clients/#{id}/optional-client-scopes/#{scope_id}", resource[:realm]) end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update clients/#{id}/optional-client-scopes/#{scope_id}: #{e.message}" end end + role = nil + if @property_flush[:roles] + remove_roles = @property_hash[:roles] - @property_flush[:roles] + begin + remove_roles.each do |s| + role = s + kcadm('delete', "clients/#{resource[:id]}/roles/#{role}", resource[:realm]) + end + rescue Puppet::ExecutionFailure => e + raise Puppet::Error, "kcadm delete realms/#{resource[:realm]}/clients/#{resource[:id]}/roles/#{role}: #{e.message}" + end + add_roles = @property_flush[:roles] - @property_hash[:roles] + begin + add_roles.each do |s| + role = s + role_data = { 'description' => "${role_#{role}}", 'name' => role } + role_data_t = Tempfile.new('keycloak_client_role') + role_data_t.write(JSON.pretty_generate(role_data)) + role_data_t.close + Puppet.debug(IO.read(role_data_t.path)) + kcadm('create', "clients/#{resource[:id]}/roles", resource[:realm], role_data_t.path) + end + rescue Puppet::ExecutionFailure => e + raise Puppet::Error, "kcadm create realms/#{resource[:realm]}/clients/#{resource[:id]}/roles/#{role}: #{e.message}" + end + end end # Collect the resources again once they've been changed (that way `puppet # resource` will show the correct values after changes have been made). @property_hash = resource.to_hash end end diff --git a/lib/puppet/provider/keycloak_realm/kcadm.rb b/lib/puppet/provider/keycloak_realm/kcadm.rb index 2d5f5d6..0c71f25 100644 --- a/lib/puppet/provider/keycloak_realm/kcadm.rb +++ b/lib/puppet/provider/keycloak_realm/kcadm.rb @@ -1,384 +1,457 @@ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'keycloak_api')) Puppet::Type.type(:keycloak_realm).provide(:kcadm, parent: Puppet::Provider::KeycloakAPI) do desc '' mk_resource_methods def flow_properties [ :browser_flow, :registration_flow, :direct_grant_flow, :reset_credentials_flow, :client_authentication_flow, :docker_authentication_flow, ] end def self.smtp_server_properties [ :smtp_server_user, :smtp_server_password, :smtp_server_host, :smtp_server_port, :smtp_server_auth, :smtp_server_starttls, :smtp_server_ssl, :smtp_server_envelope_from, :smtp_server_from, :smtp_server_from_display_name, :smtp_server_reply_to, :smtp_server_reply_to_display_name, ] end def self.browser_security_headers [ :content_security_policy, ] end def self.get_client_scopes(realm, type) output = kcadm('get', "realms/#{realm}/default-#{type}-client-scopes") Puppet.debug("Realms #{realm} #{type} client scopes: #{output}") data = JSON.parse(output) scopes = {} data.each do |d| scopes[d['name']] = d['id'] end Puppet.debug("Returned scopes: #{scopes}") scopes end def get_client_scopes(*args) self.class.get_client_scopes(*args) end + def self.get_realm_roles(realm) + output = kcadm('get', 'roles', realm) + Puppet.debug("Realms #{realm} roles: #{output}") + data = JSON.parse(output) + roles = [] + data.each do |d| + # filter out 'create-realm' role from master realm as it should not be removed + if !d['composite'] && d['name'] != 'create-realm' + roles.push(d['name']) + end + end + Puppet.debug("Returned roles: #{roles}") + roles + end + + def get_realm_roles(*args) + self.class.get_realm_roles(*args) + end + def self.get_events_config(realm) output = kcadm('get', 'events/config', realm) Puppet.debug("#{realm} events/config: #{output}") begin data = JSON.parse(output) rescue JSON::ParserError Puppet.debug('Unable to parse output from kcadm get events/config') data = {} end data.delete('enabledEventTypes') data end def available_flows(realm) output = kcadm('get', 'authentication/flows', realm, nil, ['alias']) Puppet.debug("#{realm} authentication/flows: #{output}") begin data = JSON.parse(output) rescue JSON::ParserError Puppet.debug('Unable to parse output from kcadm get authentication/flows') return [] end data.map { |f| f['alias'] } end def self.instances realms = [] output = kcadm('get', 'realms') Puppet.debug("Realms: #{output}") begin data = JSON.parse(output) rescue JSON::ParserError Puppet.debug('Unable to parse output from kcadm get realms') data = [] end data.each do |d| realm = {} realm[:ensure] = :present realm[:id] = d['id'] realm[:name] = d['realm'] events_config = get_events_config(d['realm']) type_properties.each do |property| - next if [:default_client_scopes, :optional_client_scopes].include?(property) + next if [:default_client_scopes, :optional_client_scopes, :roles].include?(property) value = if property.to_s =~ %r{events} events_config[camelize(property)] elsif browser_security_headers.include?(property) d['browserSecurityHeaders'][camelize(property)] elsif smtp_server_properties.include?(property) d['smtpServer'][camelize(property.to_s.gsub(%r{smtp_server_}, ''))] else d[camelize(property)] end if !!value == value # rubocop:disable Style/DoubleNegation value = value.to_s.to_sym end realm[property.to_sym] = value end default_scopes = get_client_scopes(realm[:name], 'default') realm[:default_client_scopes] = default_scopes.keys.map { |k| k.to_s } optional_scopes = get_client_scopes(realm[:name], 'optional') realm[:optional_client_scopes] = optional_scopes.keys.map { |k| k.to_s } + realm[:roles] = get_realm_roles(realm[:name]) realms << new(realm) end realms end def self.prefetch(resources) realms = instances resources.keys.each do |name| provider = realms.find { |realm| realm.name == name } if provider resources[name].provider = provider end end end def create data = {} events_config = {} data[:id] = resource[:id] data[:realm] = resource[:name] type_properties.each do |property| next if flow_properties.include?(property) - next if [:default_client_scopes, :optional_client_scopes].include?(property) + next if [:default_client_scopes, :optional_client_scopes, :roles].include?(property) if self.class.browser_security_headers.include?(property) && !data.key?('browserSecurityHeaders') data['browserSecurityHeaders'] = {} end if self.class.smtp_server_properties.include?(property) && !data.key?('smtpServer') data['smtpServer'] = {} end if property.to_s =~ %r{events} events_config[camelize(property)] = convert_property_value(resource[property.to_sym]) elsif resource[property.to_sym] if self.class.browser_security_headers.include?(property) data['browserSecurityHeaders'][camelize(property)] = convert_property_value(resource[property.to_sym]) elsif self.class.smtp_server_properties.include?(property) && resource[property] data['smtpServer'][camelize(property.to_s.gsub(%r{smtp_server_}, ''))] = resource[property] else data[camelize(property)] = convert_property_value(resource[property.to_sym]) end end end t = Tempfile.new('keycloak_realm') t.write(JSON.pretty_generate(data)) t.close Puppet.debug(IO.read(t.path)) begin [ :login_theme, :account_theme, :admin_theme, :email_theme, ].each do |theme| if resource[theme] check_theme_exists(resource[theme], "Keycloak_realm[#{resource[:name]}]") end end kcadm('create', 'realms', nil, t.path) rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm create realm failed\nError message: #{e.message}" end scope_id = nil if resource[:default_client_scopes] default_scopes = default_scopes ||= get_client_scopes(resource[:name], 'default') remove_default_scopes = default_scopes.keys - resource[:default_client_scopes] begin remove_default_scopes.each do |s| scope_id = default_scopes[s] kcadm('delete', "realms/#{resource[:name]}/default-default-client-scopes/#{scope_id}") end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm delete realms/#{resource[:name]}/default-default-client-scopes/#{scope_id}: #{e.message}" end end if resource[:optional_client_scopes] optional_scopes = optional_scopes ||= get_client_scopes(resource[:name], 'optional') remove_optional_scopes = optional_scopes.keys - resource[:optional_client_scopes] begin remove_optional_scopes.each do |s| scope_id = optional_scopes[s] kcadm('delete', "realms/#{resource[:name]}/default-optional-client-scopes/#{scope_id}") end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm delete realms/#{resource[:name]}/default-optional-client-scopes/#{scope_id}: #{e.message}" end end if resource[:default_client_scopes] default_scopes = default_scopes ||= get_client_scopes(resource[:name], 'default') add_default_scopes = resource[:default_client_scopes] - default_scopes.keys begin add_default_scopes.each do |s| scope_id = default_scopes[s] kcadm('update', "realms/#{resource[:name]}/default-default-client-scopes/#{scope_id}") end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update realms/#{resource[:name]}/default-default-client-scopes/#{scope_id}: #{e.message}" end end if resource[:optional_client_scopes] optional_scopes = optional_scopes ||= get_client_scopes(resource[:name], 'optional') add_optional_scopes = resource[:optional_client_scopes] - optional_scopes.keys begin add_optional_scopes.each do |s| scope_id = optional_scopes[s] kcadm('update', "realms/#{resource[:name]}/default-optional-client-scopes/#{scope_id}") end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update realms/#{resource[:name]}/default-optional-client-scopes/#{scope_id}: #{e.message}" end end + role = nil + if resource[:roles] + roles = get_realm_roles(resource[:name]) + remove_roles = roles - resource[:roles] + begin + remove_roles.each do |s| + role = s + kcadm('delete', "roles/#{role}", resource[:name]) + end + rescue Puppet::ExecutionFailure => e + raise Puppet::Error, "kcadm delete realms/#{resource[:name]}/roles/#{role}: #{e.message}" + end + add_roles = resource[:roles] - roles + begin + add_roles.each do |s| + role = s + role_data = { 'description' => "${role_#{role}}", 'name' => role } + role_data_t = Tempfile.new('keycloak_realm_role') + role_data_t.write(JSON.pretty_generate(role_data)) + role_data_t.close + Puppet.debug(IO.read(role_data_t.path)) + kcadm('create', 'roles', resource[:name], role_data_t.path) + end + rescue Puppet::ExecutionFailure => e + raise Puppet::Error, "kcadm create realms/#{resource[:name]}/roles/#{role}: #{e.message}" + end + end unless events_config.empty? events_config_t = Tempfile.new('keycloak_events_config') events_config_t.write(JSON.pretty_generate(events_config)) events_config_t.close Puppet.debug(IO.read(events_config_t.path)) begin kcadm('update', 'events/config', resource[:name], events_config_t.path) rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update events config failed\nError message: #{e.message}" end end @property_hash[:ensure] = :present end def destroy begin kcadm('delete', "realms/#{resource[:name]}") rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm delete realm failed\nError message: #{e.message}" end @property_hash.clear end def exists? @property_hash[:ensure] == :present end def initialize(value = {}) super(value) @property_flush = {} end type_properties.each do |prop| define_method "#{prop}=".to_sym do |value| @property_flush[prop] = value end end def flush unless @property_flush.empty? data = {} events_config = {} type_properties.each do |property| - next if [:default_client_scopes, :optional_client_scopes].include?(property) + next if [:default_client_scopes, :optional_client_scopes, :roles].include?(property) if flow_properties.include?(property) && !available_flows(resource[:name]).include?(resource[property.to_sym]) Puppet.warning("Keycloak_realm[#{resource[:name]}]: #{property} '#{resource[property.to_sym]}' does not exist, skipping") next end if self.class.browser_security_headers.include?(property) && !data.key?('browserSecurityHeaders') data['browserSecurityHeaders'] = {} end if self.class.smtp_server_properties.include?(property) && !data.key?('smtpServer') data['smtpServer'] = {} end if @property_flush[property.to_sym] || resource[property.to_sym] if self.class.browser_security_headers.include?(property) data['browserSecurityHeaders'][camelize(property)] = convert_property_value(resource[property.to_sym]) elsif self.class.smtp_server_properties.include?(property) && resource[property] data['smtpServer'][camelize(property.to_s.gsub(%r{smtp_server_}, ''))] = resource[property] else data[camelize(property)] = convert_property_value(resource[property.to_sym]) end end if property.to_s =~ %r{events} events_config[camelize(property)] = convert_property_value(resource[property.to_sym]) end end unless data.empty? t = Tempfile.new('keycloak_realm') t.write(JSON.pretty_generate(data)) t.close Puppet.debug(IO.read(t.path)) begin [ :login_theme, :account_theme, :admin_theme, :email_theme, ].each do |theme| if @property_flush[theme] check_theme_exists(@property_flush[theme], "Keycloak_realm[#{resource[:name]}]") end end kcadm('update', "realms/#{resource[:name]}", nil, t.path) rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update realm failed\nError message: #{e.message}" end end scope_id = nil if @property_flush[:default_client_scopes] default_scopes = default_scopes ||= get_client_scopes(resource[:name], 'default') remove_default_scopes = default_scopes.keys - @property_flush[:default_client_scopes] begin remove_default_scopes.each do |s| scope_id = default_scopes[s] kcadm('delete', "realms/#{resource[:name]}/default-default-client-scopes/#{scope_id}") end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm delete realms/#{resource[:name]}/default-default-client-scopes/#{scope_id}: #{e.message}" end end if @property_flush[:optional_client_scopes] optional_scopes = optional_scopes ||= get_client_scopes(resource[:name], 'optional') remove_optional_scopes = optional_scopes.keys - @property_flush[:optional_client_scopes] begin remove_optional_scopes.each do |s| scope_id = optional_scopes[s] kcadm('delete', "realms/#{resource[:name]}/default-optional-client-scopes/#{scope_id}") end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm delete realms/#{resource[:name]}/default-optional-client-scopes/#{scope_id}: #{e.message}" end end if @property_flush[:default_client_scopes] default_scopes = default_scopes ||= get_client_scopes(resource[:name], 'default') add_default_scopes = @property_flush[:default_client_scopes] - default_scopes.keys begin add_default_scopes.each do |s| scope_id = default_scopes[s] kcadm('update', "realms/#{resource[:name]}/default-default-client-scopes/#{scope_id}") end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update realms/#{resource[:name]}/default-default-client-scopes/#{scope_id}: #{e.message}" end end if @property_flush[:optional_client_scopes] optional_scopes = optional_scopes ||= get_client_scopes(resource[:name], 'optional') add_optional_scopes = @property_flush[:optional_client_scopes] - optional_scopes.keys begin add_optional_scopes.each do |s| scope_id = optional_scopes[s] kcadm('update', "realms/#{resource[:name]}/default-optional-client-scopes/#{scope_id}") end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update realms/#{resource[:name]}/default-optional-client-scopes/#{scope_id}: #{e.message}" end end + role = nil + if @property_flush[:roles] + remove_roles = @property_hash[:roles] - @property_flush[:roles] + begin + remove_roles.each do |s| + role = s + kcadm('delete', "roles/#{role}", resource[:name]) + end + rescue Puppet::ExecutionFailure => e + raise Puppet::Error, "kcadm delete realms/#{resource[:name]}/roles/#{role}: #{e.message}" + end + add_roles = @property_flush[:roles] - @property_hash[:roles] + begin + add_roles.each do |s| + role = s + role_data = { 'description' => "${role_#{role}}", 'name' => role } + role_data_t = Tempfile.new('keycloak_realm_role') + role_data_t.write(JSON.pretty_generate(role_data)) + role_data_t.close + Puppet.debug(IO.read(role_data_t.path)) + kcadm('create', 'roles', resource[:name], role_data_t.path) + end + rescue Puppet::ExecutionFailure => e + raise Puppet::Error, "kcadm create realms/#{resource[:name]}/roles/#{role}: #{e.message}" + end + end unless events_config.empty? events_config_t = Tempfile.new('keycloak_events_config') events_config_t.write(JSON.pretty_generate(events_config)) events_config_t.close Puppet.debug(IO.read(events_config_t.path)) begin kcadm('update', 'events/config', resource[:name], events_config_t.path) rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update events config failed\nError message: #{e.message}" end end end # Collect the resources again once they've been changed (that way `puppet # resource` will show the correct values after changes have been made). @property_hash = resource.to_hash end end diff --git a/lib/puppet/type/keycloak_client.rb b/lib/puppet/type/keycloak_client.rb index ddd6bae..d6fba2d 100644 --- a/lib/puppet/type/keycloak_client.rb +++ b/lib/puppet/type/keycloak_client.rb @@ -1,255 +1,260 @@ require_relative '../../puppet_x/keycloak/type' require_relative '../../puppet_x/keycloak/array_property' Puppet::Type.newtype(:keycloak_client) do desc <<-DESC Manage Keycloak clients @example Add a OpenID Connect client keycloak_client { 'www.example.com': ensure => 'present', realm => 'test', redirect_uris => [ "https://www.example.com/oidc", "https://www.example.com", ], default_client_scopes => ['profile','email'], secret => 'supersecret', } DESC extend PuppetX::Keycloak::Type add_autorequires ensurable newparam(:name, namevar: true) do desc 'The client name' end newparam(:client_id, namevar: true) do desc 'clientId. Defaults to `name`.' defaultto do @resource[:name] end end newparam(:id) do desc 'Id. Defaults to `client_id`' defaultto do @resource[:client_id] end end newparam(:realm, namevar: true) do desc 'realm' end newparam(:secret) do desc 'secret' def change_to_s(currentvalue, _newvalue) if currentvalue == :absent 'created secret' else 'changed secret' end end def is_to_s(_currentvalue) # rubocop:disable Style/PredicateName '[old secret redacted]' end def should_to_s(_newvalue) '[new secret redacted]' end end newproperty(:protocol) do desc 'protocol' defaultto('openid-connect') newvalues('openid-connect', 'saml') munge { |v| v } end newproperty(:client_authenticator_type) do desc 'clientAuthenticatorType' defaultto 'client-secret' end newproperty(:default_client_scopes, array_matching: :all, parent: PuppetX::Keycloak::ArrayProperty) do desc 'defaultClientScopes' defaultto [] end newproperty(:optional_client_scopes, array_matching: :all, parent: PuppetX::Keycloak::ArrayProperty) do desc 'optionalClientScopes' defaultto [] end newproperty(:full_scope_allowed, boolean: true) do desc 'fullScopeAllowed' newvalues(:true, :false) defaultto(:true) end newproperty(:enabled, boolean: true) do desc 'enabled' newvalues(:true, :false) defaultto :true end newproperty(:standard_flow_enabled, boolean: true) do desc 'standardFlowEnabled' newvalues(:true, :false) defaultto :true end newproperty(:implicit_flow_enabled, boolean: true) do desc 'implicitFlowEnabled' newvalues(:true, :false) defaultto :false end newproperty(:direct_access_grants_enabled, boolean: true) do desc 'enabled' newvalues(:true, :false) defaultto :true end newproperty(:service_accounts_enabled, boolean: true) do desc 'serviceAccountsEnabled' newvalues(:true, :false) defaultto :false end newproperty(:authorization_services_enabled, boolean: true) do desc 'authorizationServicesEnabled' newvalues(:true, :false) defaultto :false # If authorizationServicesEnabled is set to false it will not be present in # "get client/" output. Puppet will thus see it as "absent". # This custom insync? implementation prevents Puppet from trying to set # the property to false on every run. def insync?(is) if is == :true && resource[:authorization_services_enabled] == :true true elsif is == :absent && resource[:authorization_services_enabled] == :false true else false end end end newproperty(:public_client, boolean: true) do desc 'enabled' newvalues(:true, :false) defaultto :false end newproperty(:root_url) do desc 'rootUrl' end newproperty(:redirect_uris, array_matching: :all, parent: PuppetX::Keycloak::ArrayProperty) do desc 'redirectUris' defaultto [] end newproperty(:base_url) do desc 'baseUrl' end newproperty(:web_origins, array_matching: :all, parent: PuppetX::Keycloak::ArrayProperty) do desc 'webOrigins' defaultto [] end newproperty(:login_theme) do desc 'login_theme' defaultto 'absent' end newproperty(:access_token_lifespan) do desc 'access.token.lifespan' end newproperty(:browser_flow) do desc 'authenticationFlowBindingOverrides.browser (Use flow alias, not ID)' defaultto :absent end newproperty(:direct_grant_flow) do desc 'authenticationFlowBindingOverrides.direct_grant (Use flow alias, not ID)' defaultto :absent end + newproperty(:roles, array_matching: :all, parent: PuppetX::Keycloak::ArrayProperty) do + desc 'roles' + defaultto [] + end + autorequire(:keycloak_client_scope) do requires = [] catalog.resources.each do |resource| next unless resource.class.to_s == 'Puppet::Type::Keycloak_client_scope' if self[:default_client_scopes].include?(resource[:resource_name]) requires << resource.name end if self[:optional_client_scopes].include?(resource[:resource_name]) requires << resource.name end end requires end autorequire(:keycloak_protocol_mapper) do requires = [] catalog.resources.each do |resource| next unless resource.class.to_s == 'Puppet::Type::Keycloak_protocol_mapper' if self[:default_client_scopes].include?(resource[:client_scope]) requires << resource.name end if self[:optional_client_scopes].include?(resource[:client_scope]) requires << resource.name end end requires end autorequire(:keycloak_flow) do requires = [] catalog.resources.each do |resource| next unless resource.class.to_s == 'Puppet::Type::Keycloak_flow' next if self[:realm] != resource[:realm] if self[:browser_flow] == resource[:alias] requires << resource.name end if self[:direct_grant_flow] == resource[:alias] requires << resource.name end end requires end validate do if self[:authorization_services_enabled] == :true && self[:service_accounts_enabled] == :false raise "Keycloak_client[#{self[:name]}] must have service_accounts_enabled => true if authorization_services_enabled => true" end end def self.title_patterns [ [ %r{^((\S+) on (\S+))$}, [ [:name], [:client_id], [:realm], ], ], [ %r{(.*)}, [ [:name], ], ], ] end end diff --git a/lib/puppet/type/keycloak_realm.rb b/lib/puppet/type/keycloak_realm.rb index 04f9dc6..229d0b4 100644 --- a/lib/puppet/type/keycloak_realm.rb +++ b/lib/puppet/type/keycloak_realm.rb @@ -1,276 +1,281 @@ require_relative '../../puppet_x/keycloak/type' require_relative '../../puppet_x/keycloak/array_property' require_relative '../../puppet_x/keycloak/integer_property' Puppet::Type.newtype(:keycloak_realm) do desc <<-DESC Manage Keycloak realms @example Add a realm with a custom theme keycloak_realm { 'test': ensure => 'present', remember_me => true, login_with_email_allowed => false, login_theme => 'my_theme', } DESC extend PuppetX::Keycloak::Type add_autorequires(false) ensurable newparam(:name, namevar: true) do desc 'The realm name' end newparam(:id) do desc 'Id. Default to `name`.' defaultto do @resource[:name] end end newproperty(:display_name) do desc 'displayName' end newproperty(:display_name_html) do desc 'displayNameHtml' end newproperty(:login_theme) do desc 'loginTheme' defaultto 'keycloak' end newproperty(:account_theme) do desc 'accountTheme' defaultto 'keycloak' end newproperty(:admin_theme) do desc 'adminTheme' defaultto 'keycloak' end newproperty(:email_theme) do desc 'emailTheme' defaultto 'keycloak' end newproperty(:internationalization_enabled, boolean: true) do desc 'internationalizationEnabled' newvalues(:true, :false) defaultto :false end newproperty(:sso_session_idle_timeout, parent: PuppetX::Keycloak::IntegerProperty) do desc 'ssoSessionIdleTimeout' end newproperty(:sso_session_max_lifespan, parent: PuppetX::Keycloak::IntegerProperty) do desc 'ssoSessionMaxLifespan' end newproperty(:access_code_lifespan, parent: PuppetX::Keycloak::IntegerProperty) do desc 'accessCodeLifespan' end newproperty(:access_code_lifespan_user_action, parent: PuppetX::Keycloak::IntegerProperty) do desc 'accessCodeLifespanUserAction' end newproperty(:access_token_lifespan, parent: PuppetX::Keycloak::IntegerProperty) do desc 'accessTokenLifespan' end newproperty(:access_token_lifespan_for_implicit_flow, parent: PuppetX::Keycloak::IntegerProperty) do desc 'accessTokenLifespanForImplicitFlow' end newproperty(:enabled, boolean: true) do desc 'enabled' newvalues(:true, :false) defaultto :true end newproperty(:remember_me, boolean: true) do desc 'rememberMe' newvalues(:true, :false) defaultto :false end newproperty(:registration_allowed, boolean: true) do desc 'registrationAllowed' newvalues(:true, :false) defaultto :false end newproperty(:login_with_email_allowed, boolean: true) do desc 'loginWithEmailAllowed' newvalues(:true, :false) defaultto :true end newproperty(:reset_password_allowed, boolean: true) do desc 'resetPasswordAllowed' newvalues(:true, :false) defaultto :false end newproperty(:verify_email, boolean: true) do desc 'verifyEmail' newvalues(:true, :false) defaultto :false end newproperty(:browser_flow) do desc 'browserFlow' defaultto('browser') munge { |v| v.to_s } end newproperty(:registration_flow) do desc 'registrationFlow' defaultto('registration') munge { |v| v.to_s } end newproperty(:direct_grant_flow) do desc 'directGrantFlow' defaultto('direct grant') munge { |v| v.to_s } end newproperty(:reset_credentials_flow) do desc 'resetCredentialsFlow' defaultto('reset credentials') munge { |v| v.to_s } end newproperty(:client_authentication_flow) do desc 'clientAuthenticationFlow' defaultto('clients') munge { |v| v.to_s } end newproperty(:docker_authentication_flow) do desc 'dockerAuthenticationFlow' defaultto('docker auth') munge { |v| v.to_s } end newproperty(:default_client_scopes, array_matching: :all, parent: PuppetX::Keycloak::ArrayProperty) do desc 'Default Client Scopes' end newproperty(:optional_client_scopes, array_matching: :all, parent: PuppetX::Keycloak::ArrayProperty) do desc 'Optional Client Scopes' end newproperty(:supported_locales, array_matching: :all, parent: PuppetX::Keycloak::ArrayProperty) do desc 'Supported Locales' end newproperty(:content_security_policy) do desc 'contentSecurityPolicy' defaultto("frame-src 'self'; frame-ancestors 'self'; object-src 'none';") munge { |v| v.to_s } end newproperty(:events_enabled, boolean: true) do desc 'eventsEnabled' newvalues(:true, :false) defaultto :false end newproperty(:events_expiration) do desc 'eventsExpiration' end newproperty(:events_listeners, array_matching: :all, parent: PuppetX::Keycloak::ArrayProperty) do desc 'eventsListeners' defaultto ['jboss-logging'] end newproperty(:admin_events_enabled, boolean: true) do desc 'adminEventsEnabled' newvalues(:true, :false) defaultto :false end newproperty(:admin_events_details_enabled, boolean: true) do desc 'adminEventsDetailsEnabled' newvalues(:true, :false) defaultto :false end newproperty(:smtp_server_user) do desc 'smtpServer user' end newproperty(:smtp_server_password) do desc 'smtpServer password' def insync?(is) if is =~ %r{^[\*]+$} Puppet.warning("Property 'smtp_server_password' is set and Puppet has no way to check current value") true else false end end def should_to_s(_newvalue) '[new smtp_server_password redacted]' end end newproperty(:smtp_server_host) do desc 'smtpServer host' end newproperty(:smtp_server_port, parent: PuppetX::Keycloak::IntegerProperty) do desc 'smtpServer port' end newproperty(:smtp_server_auth, boolean: true) do desc 'smtpServer auth' newvalues(:true, :false) end newproperty(:smtp_server_starttls, boolean: true) do desc 'smtpServer starttls' newvalues(:true, :false) end newproperty(:smtp_server_ssl, boolean: true) do desc 'smtpServer ssl' newvalues(:true, :false) end newproperty(:smtp_server_from) do desc 'smtpServer from' end newproperty(:smtp_server_envelope_from) do desc 'smtpServer envelope_from' end newproperty(:smtp_server_from_display_name) do desc 'smtpServer fromDisplayName' end newproperty(:smtp_server_reply_to) do desc 'smtpServer replyto' end newproperty(:smtp_server_reply_to_display_name) do desc 'smtpServer replyToDisplayName' end newproperty(:brute_force_protected, boolean: true) do desc 'bruteForceProtected' newvalues(:true, :false) end + + newproperty(:roles, array_matching: :all, parent: PuppetX::Keycloak::ArrayProperty) do + desc 'roles' + defaultto ['offline_access', 'uma_authorization'] + end end diff --git a/spec/acceptance/2_realm_spec.rb b/spec/acceptance/2_realm_spec.rb index d8786b6..abf6864 100644 --- a/spec/acceptance/2_realm_spec.rb +++ b/spec/acceptance/2_realm_spec.rb @@ -1,203 +1,233 @@ require 'spec_helper_acceptance' describe 'keycloak_realm:', if: RSpec.configuration.keycloak_full do context 'creates realm' do it 'runs successfully' do pp = <<-EOS include mysql::server class { 'keycloak': datasource_driver => 'mysql', } keycloak_realm { 'test': ensure => 'present', smtp_server_host => 'smtp.example.org', smtp_server_port => 587, smtp_server_starttls => false, smtp_server_auth => false, smtp_server_user => 'john', smtp_server_password => 'secret', smtp_server_envelope_from => 'keycloak@id.example.org', smtp_server_from => 'keycloak@id.example.org', smtp_server_from_display_name => 'Keycloak', smtp_server_reply_to => 'webmaster@example.org', smtp_server_reply_to_display_name => 'Webmaster', brute_force_protected => false, + roles => ['offline_access', 'uma_authorization', 'new_role'], } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) end it 'has created a realm' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get realms/test' do data = JSON.parse(stdout) expect(data['id']).to eq('test') expect(data['bruteForceProtected']).to eq(false) expect(data['registrationAllowed']).to eq(false) expect(data['resetPasswordAllowed']).to eq(false) expect(data['verifyEmail']).to eq(false) end end it 'has left default-client-scopes' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get realms/test/default-default-client-scopes' do data = JSON.parse(stdout) names = data.map { |d| d['name'] }.sort expect(names).to include('email') expect(names).to include('profile') expect(names).to include('role_list') end end it 'has left optional-client-scopes' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get realms/test/default-optional-client-scopes' do data = JSON.parse(stdout) names = data.map { |d| d['name'] }.sort expect(names).to include('address') expect(names).to include('offline_access') expect(names).to include('phone') end end it 'has default events config' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get events/config -r test' do data = JSON.parse(stdout) expect(data['eventsEnabled']).to eq(false) expect(data['eventsExpiration']).to be_nil expect(data['eventsListeners']).to eq(['jboss-logging']) expect(data['adminEventsEnabled']).to eq(false) expect(data['adminEventsDetailsEnabled']).to eq(false) end end it 'has correct smtp settings' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get realms/test' do data = JSON.parse(stdout) expect(data['smtpServer']['host']).to eq('smtp.example.org') expect(data['smtpServer']['port']).to eq('587') expect(data['smtpServer']['starttls']).to eq('false') expect(data['smtpServer']['auth']).to eq('false') expect(data['smtpServer']['user']).to eq('john') expect(data['smtpServer']['envelopeFrom']).to eq('keycloak@id.example.org') expect(data['smtpServer']['from']).to eq('keycloak@id.example.org') expect(data['smtpServer']['fromDisplayName']).to eq('Keycloak') expect(data['smtpServer']['replyTo']).to eq('webmaster@example.org') expect(data['smtpServer']['replyToDisplayName']).to eq('Webmaster') end end + + it 'has correct roles settings' do + on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get roles -r test' do + data = JSON.parse(stdout) + expected_roles = ['new_role', 'offline_access', 'uma_authorization'] + realm_roles = [] + data.each do |d| + unless d['composite'] + realm_roles.push(d['name']) + end + end + expect(expected_roles - realm_roles).to eq([]) + end + end end context 'updates realm' do it 'runs successfully' do pp = <<-EOS include mysql::server class { 'keycloak': datasource_driver => 'mysql', } keycloak_realm { 'test': ensure => 'present', remember_me => true, registration_allowed => true, reset_password_allowed => true, verify_email => true, access_code_lifespan => 3600, access_token_lifespan => 3600, sso_session_idle_timeout => 3600, sso_session_max_lifespan => 72000, default_client_scopes => ['profile'], content_security_policy => "frame-src https://*.duosecurity.com/ 'self'; frame-src 'self'; frame-ancestors 'self'; object-src 'none';", events_enabled => true, events_expiration => 2678400, admin_events_enabled => true, admin_events_details_enabled => true, smtp_server_host => 'smtp.example.org', smtp_server_port => 587, smtp_server_starttls => false, smtp_server_auth => true, smtp_server_user => 'jane', smtp_server_password => 'secret', smtp_server_envelope_from => 'keycloak@id.example.org', smtp_server_from => 'keycloak@id.example.org', smtp_server_from_display_name => 'Keycloak', smtp_server_reply_to => 'webmaster@example.org', smtp_server_reply_to_display_name => 'Hostmaster', brute_force_protected => true, + roles => ['uma_authorization', 'new_role', 'other_new_role'], } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) end it 'has updated the realm' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get realms/test' do data = JSON.parse(stdout) expect(data['rememberMe']).to eq(true) expect(data['registrationAllowed']).to eq(true) expect(data['resetPasswordAllowed']).to eq(true) expect(data['verifyEmail']).to eq(true) expect(data['accessCodeLifespan']).to eq(3600) expect(data['accessTokenLifespan']).to eq(3600) expect(data['ssoSessionIdleTimeout']).to eq(3600) expect(data['ssoSessionMaxLifespan']).to eq(72_000) expect(data['browserSecurityHeaders']['contentSecurityPolicy']).to eq("frame-src https://*.duosecurity.com/ 'self'; frame-src 'self'; frame-ancestors 'self'; object-src 'none';") expect(data['smtpServer']['host']).to eq('smtp.example.org') expect(data['smtpServer']['port']).to eq('587') expect(data['smtpServer']['starttls']).to eq('false') expect(data['smtpServer']['auth']).to eq('true') expect(data['smtpServer']['user']).to eq('jane') expect(data['smtpServer']['envelopeFrom']).to eq('keycloak@id.example.org') expect(data['smtpServer']['from']).to eq('keycloak@id.example.org') expect(data['smtpServer']['fromDisplayName']).to eq('Keycloak') expect(data['smtpServer']['replyTo']).to eq('webmaster@example.org') expect(data['smtpServer']['replyToDisplayName']).to eq('Hostmaster') expect(data['bruteForceProtected']).to eq(true) end end it 'has updated the realm default-client-scopes' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get realms/test/default-default-client-scopes' do data = JSON.parse(stdout) names = data.map { |d| d['name'] } expect(names).to eq(['profile']) end end it 'has updated events config' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get events/config -r test' do data = JSON.parse(stdout) expect(data['eventsEnabled']).to eq(true) expect(data['eventsExpiration']).to eq(2_678_400) expect(data['eventsListeners']).to eq(['jboss-logging']) expect(data['adminEventsEnabled']).to eq(true) expect(data['adminEventsDetailsEnabled']).to eq(true) end end + + it 'has updated roles settings' do + on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get roles -r test' do + data = JSON.parse(stdout) + expected_roles = ['new_role', 'other_new_role', 'uma_authorization'] + realm_roles = [] + data.each do |d| + unless d['composite'] + realm_roles.push(d['name']) + end + end + expect(expected_roles - realm_roles).to eq([]) + end + end end context 'creates realm with invalid browser flow' do it 'runs successfully' do pp = <<-EOS include mysql::server class { 'keycloak': datasource_driver => 'mysql', } keycloak_realm { 'test2': ensure => 'present', browser_flow => 'Copy of browser', } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, expect_changes: true) end it 'has created a realm' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get realms/test2' do data = JSON.parse(stdout) expect(data['browserFlow']).to eq('browser') end end end end diff --git a/spec/acceptance/5_client_spec.rb b/spec/acceptance/5_client_spec.rb index 72098dd..9b171b5 100644 --- a/spec/acceptance/5_client_spec.rb +++ b/spec/acceptance/5_client_spec.rb @@ -1,144 +1,196 @@ require 'spec_helper_acceptance' describe 'keycloak_client define:', if: RSpec.configuration.keycloak_full do context 'creates client' do it 'runs successfully' do pp = <<-EOS include mysql::server class { 'keycloak': datasource_driver => 'mysql', } keycloak_realm { 'test': ensure => 'present' } keycloak_flow { 'foo on test': ensure => 'present' } keycloak_client { 'test.foo.bar': realm => 'test', root_url => 'https://test.foo.bar', redirect_uris => ['https://test.foo.bar/test1'], default_client_scopes => ['address'], secret => 'foobar', login_theme => 'keycloak', authorization_services_enabled => false, service_accounts_enabled => true, browser_flow => 'foo', + roles => ['bar_role', 'other_bar_role'], } keycloak_client { 'test.foo.baz': realm => 'test', root_url => 'https://test.foo.bar', redirect_uris => ['https://test.foo.bar/test1'], default_client_scopes => ['address'], secret => 'foobar', login_theme => 'keycloak', authorization_services_enabled => false, service_accounts_enabled => true, browser_flow => 'foo', } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) end it 'has created a client' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get clients/test.foo.bar -r test' do data = JSON.parse(stdout) expect(data['id']).to eq('test.foo.bar') expect(data['clientId']).to eq('test.foo.bar') expect(data['defaultClientScopes']).to eq(['address']) expect(data['rootUrl']).to eq('https://test.foo.bar') expect(data['redirectUris']).to eq(['https://test.foo.bar/test1']) expect(data['attributes']['login_theme']).to eq('keycloak') expect(data['authorizationServicesEnabled']).to eq(nil) expect(data['serviceAccountsEnabled']).to eq(true) expect(data['authenticationFlowBindingOverrides']['browser']).to eq('foo-test') end end it 'has created a client2' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get clients/test.foo.baz -r test' do data = JSON.parse(stdout) expect(data['authenticationFlowBindingOverrides']['browser']).to eq('foo-test') end end it 'has set the client secret' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get clients/test.foo.bar/client-secret -r test' do data = JSON.parse(stdout) expect(data['value']).to eq('foobar') end end + + it 'has updated roles settings for client' do + on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get clients/test.foo.bar/roles -r test' do + data = JSON.parse(stdout) + expected_roles = ['bar_role', 'other_bar_role'] + client_roles = [] + data.each do |d| + unless d['composite'] + client_roles.push(d['name']) + end + end + expect(expected_roles - client_roles).to eq([]) + end + end + + it 'has not updated roles settings for client2' do + on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get clients/test.foo.baz/roles -r test' do + data = JSON.parse(stdout) + expect(data).to eq([]) + end + end end context 'updates client' do it 'runs successfully' do pp = <<-EOS include mysql::server class { 'keycloak': datasource_driver => 'mysql', } keycloak_realm { 'test': ensure => 'present' } keycloak_client { 'test.foo.bar': realm => 'test', root_url => 'https://test.foo.bar/test', redirect_uris => ['https://test.foo.bar/test2'], default_client_scopes => ['profile', 'email'], secret => 'foobar', authorization_services_enabled => true, service_accounts_enabled => true, + roles => ['bar_role'], } keycloak_client { 'test.foo.baz': realm => 'test', root_url => 'https://test.foo.bar', redirect_uris => ['https://test.foo.bar/test1'], default_client_scopes => ['address'], secret => 'foobar', login_theme => 'keycloak', authorization_services_enabled => false, service_accounts_enabled => true, browser_flow => 'browser', + roles => ['baz_role'], } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) end it 'has updated a client' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get clients/test.foo.bar -r test' do data = JSON.parse(stdout) expect(data['id']).to eq('test.foo.bar') expect(data['clientId']).to eq('test.foo.bar') expect(data['defaultClientScopes']).to eq(['profile', 'email']) expect(data['rootUrl']).to eq('https://test.foo.bar/test') expect(data['redirectUris']).to eq(['https://test.foo.bar/test2']) expect(data['attributes']['login_theme']).to be_nil expect(data['authorizationServicesEnabled']).to eq(true) expect(data['serviceAccountsEnabled']).to eq(true) expect(data['authenticationFlowBindingOverrides']).to eq({}) end end it 'has updated a client flow' do browser_id = nil on hosts, "/opt/keycloak/bin/kcadm-wrapper.sh get authentication/flows -r test --fields 'id,alias'" do data = JSON.parse(stdout) data.each do |d| if d['alias'] == 'browser' browser_id = d['id'] break end end end on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get clients/test.foo.baz -r test' do data = JSON.parse(stdout) expect(data['authenticationFlowBindingOverrides']['browser']).to eq(browser_id) end end it 'has set the same client secret' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get clients/test.foo.bar/client-secret -r test' do data = JSON.parse(stdout) expect(data['value']).to eq('foobar') end end + + it 'has updated client roles settings' do + on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get clients/test.foo.bar/roles -r test' do + data = JSON.parse(stdout) + expected_roles = ['bar_role'] + client_roles = [] + data.each do |d| + unless d['composite'] + client_roles.push(d['name']) + end + end + expect(expected_roles - client_roles).to eq([]) + end + end + + it 'has updated client2 roles settings' do + on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get clients/test.foo.baz/roles -r test' do + data = JSON.parse(stdout) + expected_roles = ['baz_role'] + client_roles = [] + data.each do |d| + unless d['composite'] + client_roles.push(d['name']) + end + end + expect(expected_roles - client_roles).to eq([]) + end + end end end diff --git a/spec/unit/puppet/provider/keycloak_client/kcadm_spec.rb b/spec/unit/puppet/provider/keycloak_client/kcadm_spec.rb index a31253d..49b169f 100644 --- a/spec/unit/puppet/provider/keycloak_client/kcadm_spec.rb +++ b/spec/unit/puppet/provider/keycloak_client/kcadm_spec.rb @@ -1,130 +1,160 @@ require 'spec_helper' describe Puppet::Type.type(:keycloak_client).provider(:kcadm) do let(:type) do Puppet::Type.type(:keycloak_client) end let(:resource) do type.new(name: 'foo', realm: 'test', default_client_scopes: ['profile']) end describe 'self.instances' do it 'creates instances' do allow(described_class).to receive(:realms).and_return(['test', 'master']) allow(described_class).to receive(:kcadm).with('get', 'clients', 'master').and_return(my_fixture_read('get-master.out')) allow(described_class).to receive(:kcadm).with('get', 'clients', 'test').and_return(my_fixture_read('get-test.out')) allow(described_class).to receive(:kcadm).with('get', 'clients/example.com/client-secret', 'test').and_return(my_fixture_read('get-client-secret.out')) allow(described_class).to receive(:kcadm).with('get', 'authentication/flows', 'master', nil, ['id', 'alias']).and_return('[]') allow(described_class).to receive(:kcadm).with('get', 'authentication/flows', 'test', nil, ['id', 'alias']).and_return(my_fixture_read('get-flows.out')) + master_clients = JSON.parse(my_fixture_read('get-master.out')) + test_clients = JSON.parse(my_fixture_read('get-test.out')) + master_clients.each do |c| + allow(described_class).to receive(:get_client_roles).with('master', c['id']).and_return([]) + end + test_clients.each do |c| + allow(described_class).to receive(:get_client_roles).with('test', c['id']).and_return([]) + end expect(described_class.instances.length).to eq(7 + 6) end it 'returns the resource for a fileset' do allow(described_class).to receive(:realms).and_return(['test', 'master']) allow(described_class).to receive(:kcadm).with('get', 'clients', 'master').and_return(my_fixture_read('get-master.out')) allow(described_class).to receive(:kcadm).with('get', 'clients', 'test').and_return(my_fixture_read('get-test.out')) allow(described_class).to receive(:kcadm).with('get', 'clients/example.com/client-secret', 'test').and_return(my_fixture_read('get-client-secret.out')) allow(described_class).to receive(:kcadm).with('get', 'authentication/flows', 'master', nil, ['id', 'alias']).and_return('[]') allow(described_class).to receive(:kcadm).with('get', 'authentication/flows', 'test', nil, ['id', 'alias']).and_return(my_fixture_read('get-flows.out')) + master_clients = JSON.parse(my_fixture_read('get-master.out')) + test_clients = JSON.parse(my_fixture_read('get-test.out')) + master_clients.each do |c| + allow(described_class).to receive(:get_client_roles).with('master', c['id']).and_return([]) + end + test_clients.each do |c| + allow(described_class).to receive(:get_client_roles).with('test', c['id']).and_return([]) + end property_hash = described_class.instances[0].instance_variable_get('@property_hash') expect(property_hash[:name]).to eq('example.com on test') expect(property_hash[:browser_flow]).to eq('browser') + expect(property_hash[:roles]).to eq([]) end end # describe 'self.prefetch' do # let(:instances) do # all_realms.map { |f| described_class.new(f) } # end # let(:resources) do # all_realms.each_with_object({}) do |f, h| # h[f[:name]] = type.new(f.reject {|k,v| v.nil?}) # end # end # # before(:each) do # allow(described_class).to receive(:instances).and_return(instances) # end # # it 'should prefetch' do # resources.keys.each do |r| # expect(resources[r]).to receive(:provider=).with(described_class) # end # described_class.prefetch(resources) # end # end describe 'create' do - it 'creates a realm' do + it 'creates a client' do resource[:browser_flow] = 'browser' + resource[:roles] = ['foo_role'] temp = Tempfile.new('keycloak_client') + rtemp = Tempfile.new('keycloak_client_role') allow(Tempfile).to receive(:new).with('keycloak_client').and_return(temp) allow(resource.provider).to receive(:kcadm).with('get', 'client-scopes', 'test', nil, ['id', 'name']).and_return(my_fixture_read('get-scopes.out')) + allow(described_class).to receive(:get_client_roles).with('test', 'foo').and_return([]) expect(resource.provider).to receive(:kcadm).with('create', 'clients', 'test', temp.path).and_return(my_fixture_read('get-client.out')) expect(resource.provider).to receive(:kcadm).with('delete', 'clients/foo/default-client-scopes/b8ebafcc-485f-44d2-9fe6-f4ed0da80980', 'test') expect(resource.provider).to receive(:kcadm).with('delete', 'clients/foo/default-client-scopes/3e40378d-d26d-471f-b2c7-7a3d9651e588', 'test') expect(resource.provider).to receive(:kcadm).with('delete', 'clients/foo/optional-client-scopes/96f8b56b-7b3a-44cf-82a5-ffbda49271bd', 'test') expect(resource.provider).to receive(:kcadm).with('delete', 'clients/foo/optional-client-scopes/a83d9575-d122-4af1-afb0-10edb851798e', 'test') expect(resource.provider).to receive(:kcadm).with('delete', 'clients/foo/optional-client-scopes/dbd3b1c1-9159-46d9-a879-9602972f1994', 'test') expect(resource.provider).to receive(:kcadm).with('update', 'clients/foo/default-client-scopes/ee85ec64-4853-4fd4-a2f4-ff578016c9b5', 'test') allow(described_class).to receive(:kcadm).with('get', 'authentication/flows', 'test', nil, ['id', 'alias']).and_return(my_fixture_read('get-flows.out')) + allow(Tempfile).to receive(:new).with('keycloak_client_role').and_return(rtemp) + expect(resource.provider).to receive(:kcadm).with('create', 'clients/foo/roles', 'test', rtemp.path) resource.provider.create property_hash = resource.provider.instance_variable_get('@property_hash') expect(property_hash[:ensure]).to eq(:present) f = File.read(temp.path) data = JSON.parse(f) expect(data['authenticationFlowBindingOverrides']['browser']).to eq('5a6bbfbb-5096-4d7b-bdcd-819145a10bb1') end end describe 'destroy' do - it 'deletes a realm' do + it 'deletes a client' do hash = resource.to_hash resource.provider.instance_variable_set(:@property_hash, hash) expect(resource.provider).to receive(:kcadm).with('delete', 'clients/foo', 'test') resource.provider.destroy property_hash = resource.provider.instance_variable_get('@property_hash') expect(property_hash).to eq({}) end end describe 'flush' do - it 'updates a realm' do + it 'updates a client' do hash = resource.to_hash resource.provider.instance_variable_set(:@property_hash, hash) temp = Tempfile.new('keycloak_client') + rtemp = Tempfile.new('keycloak_client_role') allow(Tempfile).to receive(:new).with('keycloak_client').and_return(temp) + allow(Tempfile).to receive(:new).with('keycloak_client_role').and_return(rtemp) + allow(described_class).to receive(:get_client_roles).with('test', 'foo').and_return(['foo_role']) expect(resource.provider).to receive(:kcadm).with('update', 'clients/foo', 'test', temp.path) + expect(resource.provider).to receive(:kcadm).with('delete', 'clients/foo/roles/foo_role', 'test') + expect(resource.provider).to receive(:kcadm).with('create', 'clients/foo/roles', 'test', rtemp.path) + property_hash = resource.provider.instance_variable_get('@property_hash') + property_hash[:roles] = ['foo_role'] resource.provider.redirect_uris = ['foobar'] + resource.provider.roles = ['new_foo_role'] resource.provider.flush end it 'updates default_client_scopes' do hash = resource.to_hash resource.provider.instance_variable_set(:@property_hash, hash) temp = Tempfile.new('keycloak_client') allow(Tempfile).to receive(:new).with('keycloak_client').and_return(temp) allow(resource.provider).to receive(:kcadm).with('get', 'client-scopes', 'test', nil, ['id', 'name']).and_return(my_fixture_read('get-scopes.out')) expect(resource.provider).to receive(:kcadm).with('update', 'clients/foo', 'test', temp.path) expect(resource.provider).to receive(:kcadm).with('delete', 'clients/foo/default-client-scopes/ee85ec64-4853-4fd4-a2f4-ff578016c9b5', 'test') expect(resource.provider).to receive(:kcadm).with('update', 'clients/foo/default-client-scopes/openid-connect-clients', 'test') resource.provider.default_client_scopes = ['openid-connect-clients'] resource.provider.redirect_uris = ['foobar'] resource.provider.flush end it 'updates flow' do hash = resource.to_hash resource.provider.instance_variable_set(:@property_hash, hash) temp = Tempfile.new('keycloak_client') allow(Tempfile).to receive(:new).with('keycloak_client').and_return(temp) allow(described_class).to receive(:kcadm).with('get', 'authentication/flows', 'test', nil, ['id', 'alias']).and_return(my_fixture_read('get-flows.out')) expect(resource.provider).to receive(:kcadm).with('update', 'clients/foo', 'test', temp.path) resource.provider.browser_flow = 'browser' resource.provider.flush f = File.read(temp.path) data = JSON.parse(f) expect(data['authenticationFlowBindingOverrides']['browser']).to eq('5a6bbfbb-5096-4d7b-bdcd-819145a10bb1') end end end diff --git a/spec/unit/puppet/provider/keycloak_realm/kcadm_spec.rb b/spec/unit/puppet/provider/keycloak_realm/kcadm_spec.rb index 7da69da..f6b4b90 100644 --- a/spec/unit/puppet/provider/keycloak_realm/kcadm_spec.rb +++ b/spec/unit/puppet/provider/keycloak_realm/kcadm_spec.rb @@ -1,95 +1,112 @@ require 'spec_helper' describe Puppet::Type.type(:keycloak_realm).provider(:kcadm) do let(:type) do Puppet::Type.type(:keycloak_realm) end let(:resource) do type.new(name: 'test') end describe 'self.instances' do it 'creates instances' do allow(described_class).to receive(:kcadm).with('get', 'realms').and_return(my_fixture_read('get.out')) allow(described_class).to receive(:get_client_scopes).with('test', 'default').and_return('profile' => '8a6759cb-3950-48a2-b29b-c2c06fc3379b') allow(described_class).to receive(:get_client_scopes).with('test', 'optional').and_return('address' => '1cda5a52-aa2c-4b07-b620-30b703619581') allow(described_class).to receive(:get_client_scopes).with('master', 'default').and_return('profile' => '8a6759cb-3950-48a2-b29b-c2c06fc3379b') allow(described_class).to receive(:get_client_scopes).with('master', 'optional').and_return('address' => '1cda5a52-aa2c-4b07-b620-30b703619581') allow(described_class).to receive(:get_events_config).with('test').and_return({}) allow(described_class).to receive(:get_events_config).with('master').and_return({}) + allow(described_class).to receive(:get_realm_roles).with('test').and_return(['offline_access', 'uma_authorization']) + allow(described_class).to receive(:get_realm_roles).with('master').and_return(['offline_access', 'uma_authorization']) expect(described_class.instances.length).to eq(2) end it 'returns the resource for a fileset' do allow(described_class).to receive(:kcadm).with('get', 'realms').and_return(my_fixture_read('get.out')) allow(described_class).to receive(:get_client_scopes).with('test', 'default').and_return('profile' => '8a6759cb-3950-48a2-b29b-c2c06fc3379b') allow(described_class).to receive(:get_client_scopes).with('test', 'optional').and_return('address' => '1cda5a52-aa2c-4b07-b620-30b703619581') allow(described_class).to receive(:get_client_scopes).with('master', 'default').and_return('profile' => '8a6759cb-3950-48a2-b29b-c2c06fc3379b') allow(described_class).to receive(:get_client_scopes).with('master', 'optional').and_return('address' => '1cda5a52-aa2c-4b07-b620-30b703619581') allow(described_class).to receive(:get_events_config).with('test').and_return({}) allow(described_class).to receive(:get_events_config).with('master').and_return({}) + allow(described_class).to receive(:get_realm_roles).with('test').and_return(['offline_access', 'uma_authorization']) + allow(described_class).to receive(:get_realm_roles).with('master').and_return(['offline_access', 'uma_authorization']) property_hash = described_class.instances[0].instance_variable_get('@property_hash') expect(property_hash[:enabled]).to eq(:true) expect(property_hash[:login_with_email_allowed]).to eq(:false) expect(property_hash[:default_client_scopes]).to eq(['profile']) expect(property_hash[:optional_client_scopes]).to eq(['address']) + expect(property_hash[:roles]).to eq(['offline_access', 'uma_authorization']) end end # describe 'self.prefetch' do # let(:instances) do # all_realms.map { |f| described_class.new(f) } # end # let(:resources) do # all_realms.each_with_object({}) do |f, h| # h[f[:name]] = type.new(f.reject {|k,v| v.nil?}) # end # end # # before(:each) do # allow(described_class).to receive(:instances).and_return(instances) # end # # it 'should prefetch' do # resources.keys.each do |r| # expect(resources[r]).to receive(:provider=).with(described_class) # end # described_class.prefetch(resources) # end # end describe 'create' do it 'creates a realm' do temp = Tempfile.new('keycloak_realm') etemp = Tempfile.new('keycloak_events_config') + rtemp = Tempfile.new('keycloak_realm_role') allow(Tempfile).to receive(:new).with('keycloak_realm').and_return(temp) allow(Tempfile).to receive(:new).with('keycloak_events_config').and_return(etemp) + allow(Tempfile).to receive(:new).with('keycloak_realm_role').and_return(rtemp) + allow(described_class).to receive(:get_realm_roles).with('test').and_return(['offline_access', 'uma_authorization']) expect(resource.provider).to receive(:kcadm).with('create', 'realms', nil, temp.path) + expect(resource.provider).to receive(:kcadm).with('create', 'roles', 'test', rtemp.path) expect(resource.provider).to receive(:kcadm).with('update', 'events/config', 'test', etemp.path) + resource[:roles] = ['offline_access', 'uma_authorization', 'new_role'] resource.provider.create property_hash = resource.provider.instance_variable_get('@property_hash') expect(property_hash[:ensure]).to eq(:present) end end describe 'destroy' do it 'deletes a realm' do expect(resource.provider).to receive(:kcadm).with('delete', 'realms/test') resource.provider.destroy property_hash = resource.provider.instance_variable_get('@property_hash') expect(property_hash).to eq({}) end end describe 'flush' do it 'updates a realm' do temp = Tempfile.new('keycloak_realm') etemp = Tempfile.new('keycloak_events_config') + rtemp = Tempfile.new('keycloak_realm_role') allow(Tempfile).to receive(:new).with('keycloak_realm').and_return(temp) allow(Tempfile).to receive(:new).with('keycloak_events_config').and_return(etemp) + allow(Tempfile).to receive(:new).with('keycloak_realm_role').and_return(rtemp) allow(resource.provider).to receive(:kcadm).with('get', 'authentication/flows', 'test', nil, ['alias']).and_return('[]') expect(resource.provider).to receive(:kcadm).with('update', 'realms/test', nil, temp.path) + expect(resource.provider).to receive(:kcadm).with('delete', 'roles/offline_access', 'test') + expect(resource.provider).to receive(:kcadm).with('create', 'roles', 'test', rtemp.path) expect(resource.provider).to receive(:kcadm).with('update', 'events/config', 'test', etemp.path) + property_hash = resource.provider.instance_variable_get('@property_hash') + property_hash[:roles] = ['offline_access', 'uma_authorization'] resource.provider.login_with_email_allowed = :false + resource.provider.roles = ['uma_authorization', 'new_role'] resource.provider.flush end end end diff --git a/spec/unit/puppet/type/keycloak_client_spec.rb b/spec/unit/puppet/type/keycloak_client_spec.rb index 8813dd7..84d283e 100644 --- a/spec/unit/puppet/type/keycloak_client_spec.rb +++ b/spec/unit/puppet/type/keycloak_client_spec.rb @@ -1,221 +1,222 @@ require 'spec_helper' describe Puppet::Type.type(:keycloak_client) do let(:default_config) do { name: 'foo', realm: 'test', } end let(:config) do default_config end let(:resource) do described_class.new(config) end it 'adds to catalog without raising an error' do catalog = Puppet::Resource::Catalog.new expect { catalog.add_resource resource }.not_to raise_error end it 'has a name' do expect(resource[:name]).to eq('foo') end it 'has client_id default to name' do expect(resource[:client_id]).to eq('foo') end it 'has id default to name' do expect(resource[:id]).to eq('foo') end it 'has realm' do expect(resource[:realm]).to eq('test') end it 'handles componsite name' do component = described_class.new(name: 'foo on test') expect(component[:name]).to eq('foo on test') expect(component[:client_id]).to eq('foo') expect(component[:realm]).to eq('test') end it 'defaults to client_authenticator_type=client-secret' do expect(resource[:client_authenticator_type]).to eq('client-secret') end it 'defaults to protocol=openid-connect' do expect(resource[:protocol]).to eq('openid-connect') end it 'does not allow invalid protocol' do config[:protocol] = 'foo' expect { resource }.to raise_error(%r{foo}) end defaults = { enabled: :true, standard_flow_enabled: :true, implicit_flow_enabled: :false, direct_access_grants_enabled: :true, service_accounts_enabled: :false, public_client: :false, full_scope_allowed: :true, default_client_scopes: [], optional_client_scopes: [], redirect_uris: [], web_origins: [], login_theme: 'absent', browser_flow: :absent, direct_grant_flow: :absent, } describe 'basic properties' do # Test basic properties [ :secret, :login_theme, :root_url, :base_url, :browser_flow, :direct_grant_flow, ].each do |p| it "should accept a #{p}" do config[p] = 'foo' expect(resource[p]).to eq('foo') end next unless defaults[p] it "should have default for #{p}" do expect(resource[p]).to eq(defaults[p]) end end end describe 'boolean properties' do # Test boolean properties [ :enabled, :standard_flow_enabled, :implicit_flow_enabled, :direct_access_grants_enabled, :service_accounts_enabled, :public_client, :full_scope_allowed, ].each do |p| it "should accept true for #{p}" do config[p] = true expect(resource[p]).to eq(:true) end it "should accept true for #{p} string" do config[p] = 'true' expect(resource[p]).to eq(:true) end it "should accept false for #{p}" do config[p] = false expect(resource[p]).to eq(:false) end it "should accept false for #{p} string" do config[p] = 'false' expect(resource[p]).to eq(:false) end it "should not accept strings for #{p}" do config[p] = 'foo' expect { resource }.to raise_error(%r{foo}) end next unless defaults[p] it "should have default for #{p}" do expect(resource[p]).to eq(defaults[p]) end end end describe 'array properties' do # Array properties [ :default_client_scopes, :optional_client_scopes, :redirect_uris, :web_origins, + :roles, ].each do |p| it "should accept array for #{p}" do config[p] = ['foo', 'bar'] expect(resource[p]).to eq(['foo', 'bar']) end next unless defaults[p] it "should have default for #{p}" do expect(resource[p]).to eq(defaults[p]) end end end it 'autorequires keycloak_conn_validator' do keycloak_conn_validator = Puppet::Type.type(:keycloak_conn_validator).new(name: 'keycloak') catalog = Puppet::Resource::Catalog.new catalog.add_resource resource catalog.add_resource keycloak_conn_validator rel = resource.autorequire[0] expect(rel.source.ref).to eq(keycloak_conn_validator.ref) expect(rel.target.ref).to eq(resource.ref) end it 'autorequires kcadm-wrapper.sh' do file = Puppet::Type.type(:file).new(name: 'kcadm-wrapper.sh', path: '/opt/keycloak/bin/kcadm-wrapper.sh') catalog = Puppet::Resource::Catalog.new catalog.add_resource resource catalog.add_resource file rel = resource.autorequire[0] expect(rel.source.ref).to eq(file.ref) expect(rel.target.ref).to eq(resource.ref) end it 'autorequires keycloak_realm' do keycloak_realm = Puppet::Type.type(:keycloak_realm).new(name: 'test') catalog = Puppet::Resource::Catalog.new catalog.add_resource resource catalog.add_resource keycloak_realm rel = resource.autorequire[0] expect(rel.source.ref).to eq(keycloak_realm.ref) expect(rel.target.ref).to eq(resource.ref) end it 'autorequires keycloak_client_scope' do config[:default_client_scopes] = ['foo'] keycloak_client_scope = Puppet::Type.type(:keycloak_client_scope).new(name: 'foo', realm: 'test') catalog = Puppet::Resource::Catalog.new catalog.add_resource resource catalog.add_resource keycloak_client_scope rel = resource.autorequire[0] expect(rel.source.ref).to eq(keycloak_client_scope.ref) expect(rel.target.ref).to eq(resource.ref) end it 'autorequires client_scope protocol mappers' do config[:default_client_scopes] = ['foo'] keycloak_protocol_mapper = Puppet::Type.type(:keycloak_protocol_mapper).new(name: 'bar', realm: 'test', client_scope: 'foo') catalog = Puppet::Resource::Catalog.new catalog.add_resource resource catalog.add_resource keycloak_protocol_mapper rel = resource.autorequire[0] expect(rel.source.ref).to eq(keycloak_protocol_mapper.ref) expect(rel.target.ref).to eq(resource.ref) end it 'autorequires browser flow' do config[:browser_flow] = 'foo' flow = Puppet::Type.type(:keycloak_flow).new(name: 'foo', realm: 'test') catalog = Puppet::Resource::Catalog.new catalog.add_resource resource catalog.add_resource flow rel = resource.autorequire[0] expect(rel.source.ref).to eq(flow.ref) expect(rel.target.ref).to eq(resource.ref) end end diff --git a/spec/unit/puppet/type/keycloak_realm_spec.rb b/spec/unit/puppet/type/keycloak_realm_spec.rb index a47427b..9b242a3 100644 --- a/spec/unit/puppet/type/keycloak_realm_spec.rb +++ b/spec/unit/puppet/type/keycloak_realm_spec.rb @@ -1,197 +1,198 @@ require 'spec_helper' describe Puppet::Type.type(:keycloak_realm) do let(:default_config) do { name: 'test', } end let(:config) do default_config end let(:resource) do described_class.new(config) end it 'adds to catalog without raising an error' do catalog = Puppet::Resource::Catalog.new expect { catalog.add_resource resource }.not_to raise_error end it 'has a name' do expect(resource[:name]).to eq('test') end it 'has id default to name' do expect(resource[:id]).to eq('test') end defaults = { login_theme: 'keycloak', account_theme: 'keycloak', admin_theme: 'keycloak', email_theme: 'keycloak', access_code_lifespan_user_action: nil, access_token_lifespan_for_implicit_flow: nil, enabled: :true, remember_me: :false, login_with_email_allowed: :true, browser_flow: 'browser', registration_flow: 'registration', direct_grant_flow: 'direct grant', reset_credentials_flow: 'reset credentials', client_authentication_flow: 'clients', docker_authentication_flow: 'docker auth', content_security_policy: "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", events_enabled: :false, events_listeners: ['jboss-logging'], admin_events_enabled: :false, admin_events_details_enabled: :false, } describe 'basic properties' do # Test basic properties [ :display_name, :display_name_html, :login_theme, :account_theme, :admin_theme, :email_theme, :events_expiration, :browser_flow, :registration_flow, :direct_grant_flow, :reset_credentials_flow, :client_authentication_flow, :docker_authentication_flow, :content_security_policy, :smtp_server_user, :smtp_server_password, :smtp_server_host, :smtp_server_envelope_from, :smtp_server_from, :smtp_server_from_display_name, :smtp_server_reply_to, :smtp_server_reply_to_display_name, ].each do |p| it "should accept a #{p}" do config[p] = 'foo' expect(resource[p]).to eq('foo') end next unless defaults[p] it "should have default for #{p}" do expect(resource[p]).to eq(defaults[p]) end end end describe 'integer properties' do # Test integer properties [ :sso_session_idle_timeout, :sso_session_max_lifespan, :access_code_lifespan, :access_code_lifespan_user_action, :access_token_lifespan, :access_token_lifespan_for_implicit_flow, :smtp_server_port, ].each do |p| it "should accept a #{p}" do config[p] = 100 expect(resource[p]).to eq(100) end next unless defaults[p] it "should have default for #{p}" do expect(resource[p]).to eq(defaults[p]) end end end describe 'boolean properties' do # Test boolean properties [ :remember_me, :registration_allowed, :reset_password_allowed, :verify_email, :login_with_email_allowed, :internationalization_enabled, :events_enabled, :admin_events_enabled, :admin_events_details_enabled, :smtp_server_auth, :smtp_server_starttls, :smtp_server_ssl, :brute_force_protected, ].each do |p| it "should accept true for #{p}" do config[p] = true expect(resource[p]).to eq(:true) end it "should accept true for #{p} string" do config[p] = 'true' expect(resource[p]).to eq(:true) end it "should accept false for #{p}" do config[p] = false expect(resource[p]).to eq(:false) end it "should accept false for #{p} string" do config[p] = 'false' expect(resource[p]).to eq(:false) end it "should not accept strings for #{p}" do config[p] = 'foo' expect { resource }.to raise_error(%r{foo}) end next unless defaults[p] it "should have default for #{p}" do expect(resource[p]).to eq(defaults[p]) end end end describe 'array properties' do # Array properties [ :default_client_scopes, :optional_client_scopes, :events_listeners, :supported_locales, + :roles, ].each do |p| it "should accept array for #{p}" do config[p] = ['foo', 'bar'] expect(resource[p]).to eq(['foo', 'bar']) end next unless defaults[p] it "should have default for #{p}" do expect(resource[p]).to eq(defaults[p]) end end end it 'autorequires keycloak_conn_validator' do keycloak_conn_validator = Puppet::Type.type(:keycloak_conn_validator).new(name: 'keycloak') catalog = Puppet::Resource::Catalog.new catalog.add_resource resource catalog.add_resource keycloak_conn_validator rel = resource.autorequire[0] expect(rel.source.ref).to eq(keycloak_conn_validator.ref) expect(rel.target.ref).to eq(resource.ref) end it 'autorequires kcadm-wrapper.sh' do file = Puppet::Type.type(:file).new(name: 'kcadm-wrapper.sh', path: '/opt/keycloak/bin/kcadm-wrapper.sh') catalog = Puppet::Resource::Catalog.new catalog.add_resource resource catalog.add_resource file rel = resource.autorequire[0] expect(rel.source.ref).to eq(file.ref) expect(rel.target.ref).to eq(resource.ref) end end