2021-04-21 12:42:28 +02:00
|
|
|
#!/usr/bin/python
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
# Copyright (c) 2017, Eike Frost <ei@kefro.st>
|
|
|
|
# Copyright (c) 2021, Christophe Gilles <christophe.gilles54@gmail.com>
|
2022-08-05 12:28:29 +02:00
|
|
|
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
2021-04-21 12:42:28 +02:00
|
|
|
|
|
|
|
from __future__ import absolute_import, division, print_function
|
|
|
|
__metaclass__ = type
|
|
|
|
|
|
|
|
DOCUMENTATION = '''
|
|
|
|
---
|
|
|
|
module: keycloak_realm
|
|
|
|
|
|
|
|
short_description: Allows administration of Keycloak realm via Keycloak API
|
2021-10-22 07:57:18 +02:00
|
|
|
|
2021-04-21 12:42:28 +02:00
|
|
|
version_added: 3.0.0
|
|
|
|
|
|
|
|
description:
|
|
|
|
- This module allows the administration of Keycloak realm via the Keycloak REST API. It
|
|
|
|
requires access to the REST API via OpenID Connect; the user connecting and the realm being
|
|
|
|
used must have the requisite access rights. In a default Keycloak installation, admin-cli
|
|
|
|
and an admin user would work, as would a separate realm definition with the scope tailored
|
|
|
|
to your needs and a user having the expected roles.
|
|
|
|
|
|
|
|
- The names of module options are snake_cased versions of the camelCase ones found in the
|
|
|
|
Keycloak API and its documentation at U(https://www.keycloak.org/docs-api/8.0/rest-api/index.html).
|
|
|
|
Aliases are provided so camelCased versions can be used as well.
|
|
|
|
|
|
|
|
- The Keycloak API does not always sanity check inputs e.g. you can set
|
|
|
|
SAML-specific settings on an OpenID Connect client for instance and vice versa. Be careful.
|
|
|
|
If you do not specify a setting, usually a sensible default is chosen.
|
|
|
|
|
2023-02-24 09:25:31 +01:00
|
|
|
attributes:
|
|
|
|
check_mode:
|
|
|
|
support: full
|
|
|
|
diff_mode:
|
|
|
|
support: full
|
|
|
|
|
2021-04-21 12:42:28 +02:00
|
|
|
options:
|
|
|
|
state:
|
|
|
|
description:
|
|
|
|
- State of the realm.
|
2023-06-15 19:04:45 +02:00
|
|
|
- On V(present), the realm will be created (or updated if it exists already).
|
|
|
|
- On V(absent), the realm will be removed if it exists.
|
2021-04-21 12:42:28 +02:00
|
|
|
choices: ['present', 'absent']
|
|
|
|
default: 'present'
|
|
|
|
type: str
|
|
|
|
|
|
|
|
id:
|
|
|
|
description:
|
|
|
|
- The realm to create.
|
|
|
|
type: str
|
|
|
|
realm:
|
|
|
|
description:
|
|
|
|
- The realm name.
|
|
|
|
type: str
|
|
|
|
access_code_lifespan:
|
|
|
|
description:
|
|
|
|
- The realm access code lifespan.
|
|
|
|
aliases:
|
|
|
|
- accessCodeLifespan
|
|
|
|
type: int
|
|
|
|
access_code_lifespan_login:
|
|
|
|
description:
|
|
|
|
- The realm access code lifespan login.
|
|
|
|
aliases:
|
|
|
|
- accessCodeLifespanLogin
|
|
|
|
type: int
|
|
|
|
access_code_lifespan_user_action:
|
|
|
|
description:
|
|
|
|
- The realm access code lifespan user action.
|
|
|
|
aliases:
|
|
|
|
- accessCodeLifespanUserAction
|
|
|
|
type: int
|
|
|
|
access_token_lifespan:
|
|
|
|
description:
|
|
|
|
- The realm access token lifespan.
|
|
|
|
aliases:
|
|
|
|
- accessTokenLifespan
|
|
|
|
type: int
|
|
|
|
access_token_lifespan_for_implicit_flow:
|
|
|
|
description:
|
|
|
|
- The realm access token lifespan for implicit flow.
|
|
|
|
aliases:
|
|
|
|
- accessTokenLifespanForImplicitFlow
|
|
|
|
type: int
|
|
|
|
account_theme:
|
|
|
|
description:
|
|
|
|
- The realm account theme.
|
|
|
|
aliases:
|
|
|
|
- accountTheme
|
|
|
|
type: str
|
|
|
|
action_token_generated_by_admin_lifespan:
|
|
|
|
description:
|
|
|
|
- The realm action token generated by admin lifespan.
|
|
|
|
aliases:
|
|
|
|
- actionTokenGeneratedByAdminLifespan
|
|
|
|
type: int
|
|
|
|
action_token_generated_by_user_lifespan:
|
|
|
|
description:
|
|
|
|
- The realm action token generated by user lifespan.
|
|
|
|
aliases:
|
|
|
|
- actionTokenGeneratedByUserLifespan
|
|
|
|
type: int
|
|
|
|
admin_events_details_enabled:
|
|
|
|
description:
|
|
|
|
- The realm admin events details enabled.
|
|
|
|
aliases:
|
|
|
|
- adminEventsDetailsEnabled
|
|
|
|
type: bool
|
|
|
|
admin_events_enabled:
|
|
|
|
description:
|
|
|
|
- The realm admin events enabled.
|
|
|
|
aliases:
|
|
|
|
- adminEventsEnabled
|
|
|
|
type: bool
|
|
|
|
admin_theme:
|
|
|
|
description:
|
|
|
|
- The realm admin theme.
|
|
|
|
aliases:
|
|
|
|
- adminTheme
|
|
|
|
type: str
|
|
|
|
attributes:
|
|
|
|
description:
|
|
|
|
- The realm attributes.
|
|
|
|
type: dict
|
|
|
|
browser_flow:
|
|
|
|
description:
|
|
|
|
- The realm browser flow.
|
|
|
|
aliases:
|
|
|
|
- browserFlow
|
|
|
|
type: str
|
|
|
|
browser_security_headers:
|
|
|
|
description:
|
|
|
|
- The realm browser security headers.
|
|
|
|
aliases:
|
|
|
|
- browserSecurityHeaders
|
|
|
|
type: dict
|
|
|
|
brute_force_protected:
|
|
|
|
description:
|
|
|
|
- The realm brute force protected.
|
|
|
|
aliases:
|
|
|
|
- bruteForceProtected
|
|
|
|
type: bool
|
|
|
|
client_authentication_flow:
|
|
|
|
description:
|
|
|
|
- The realm client authentication flow.
|
|
|
|
aliases:
|
|
|
|
- clientAuthenticationFlow
|
|
|
|
type: str
|
|
|
|
client_scope_mappings:
|
|
|
|
description:
|
|
|
|
- The realm client scope mappings.
|
|
|
|
aliases:
|
|
|
|
- clientScopeMappings
|
|
|
|
type: dict
|
|
|
|
default_default_client_scopes:
|
|
|
|
description:
|
|
|
|
- The realm default default client scopes.
|
|
|
|
aliases:
|
|
|
|
- defaultDefaultClientScopes
|
|
|
|
type: list
|
2022-04-22 22:31:38 +02:00
|
|
|
elements: str
|
2021-04-21 12:42:28 +02:00
|
|
|
default_groups:
|
|
|
|
description:
|
|
|
|
- The realm default groups.
|
|
|
|
aliases:
|
|
|
|
- defaultGroups
|
|
|
|
type: list
|
2022-05-30 12:48:06 +02:00
|
|
|
elements: str
|
2021-04-21 12:42:28 +02:00
|
|
|
default_locale:
|
|
|
|
description:
|
|
|
|
- The realm default locale.
|
|
|
|
aliases:
|
|
|
|
- defaultLocale
|
|
|
|
type: str
|
|
|
|
default_optional_client_scopes:
|
|
|
|
description:
|
|
|
|
- The realm default optional client scopes.
|
|
|
|
aliases:
|
|
|
|
- defaultOptionalClientScopes
|
|
|
|
type: list
|
2022-04-22 22:31:38 +02:00
|
|
|
elements: str
|
2021-04-21 12:42:28 +02:00
|
|
|
default_roles:
|
|
|
|
description:
|
|
|
|
- The realm default roles.
|
|
|
|
aliases:
|
|
|
|
- defaultRoles
|
|
|
|
type: list
|
2022-05-30 12:48:06 +02:00
|
|
|
elements: str
|
2021-04-21 12:42:28 +02:00
|
|
|
default_signature_algorithm:
|
|
|
|
description:
|
|
|
|
- The realm default signature algorithm.
|
|
|
|
aliases:
|
|
|
|
- defaultSignatureAlgorithm
|
|
|
|
type: str
|
|
|
|
direct_grant_flow:
|
|
|
|
description:
|
|
|
|
- The realm direct grant flow.
|
|
|
|
aliases:
|
|
|
|
- directGrantFlow
|
|
|
|
type: str
|
|
|
|
display_name:
|
|
|
|
description:
|
|
|
|
- The realm display name.
|
|
|
|
aliases:
|
|
|
|
- displayName
|
|
|
|
type: str
|
|
|
|
display_name_html:
|
|
|
|
description:
|
|
|
|
- The realm display name HTML.
|
|
|
|
aliases:
|
|
|
|
- displayNameHtml
|
|
|
|
type: str
|
|
|
|
docker_authentication_flow:
|
|
|
|
description:
|
|
|
|
- The realm docker authentication flow.
|
|
|
|
aliases:
|
|
|
|
- dockerAuthenticationFlow
|
|
|
|
type: str
|
|
|
|
duplicate_emails_allowed:
|
|
|
|
description:
|
|
|
|
- The realm duplicate emails allowed option.
|
|
|
|
aliases:
|
|
|
|
- duplicateEmailsAllowed
|
|
|
|
type: bool
|
|
|
|
edit_username_allowed:
|
|
|
|
description:
|
|
|
|
- The realm edit username allowed option.
|
|
|
|
aliases:
|
|
|
|
- editUsernameAllowed
|
|
|
|
type: bool
|
|
|
|
email_theme:
|
|
|
|
description:
|
|
|
|
- The realm email theme.
|
|
|
|
aliases:
|
|
|
|
- emailTheme
|
|
|
|
type: str
|
|
|
|
enabled:
|
|
|
|
description:
|
|
|
|
- The realm enabled option.
|
|
|
|
type: bool
|
|
|
|
enabled_event_types:
|
|
|
|
description:
|
|
|
|
- The realm enabled event types.
|
|
|
|
aliases:
|
|
|
|
- enabledEventTypes
|
|
|
|
type: list
|
|
|
|
elements: str
|
2021-08-27 06:17:04 +02:00
|
|
|
events_enabled:
|
|
|
|
description:
|
|
|
|
- Enables or disables login events for this realm.
|
|
|
|
aliases:
|
|
|
|
- eventsEnabled
|
|
|
|
type: bool
|
|
|
|
version_added: 3.6.0
|
2021-04-21 12:42:28 +02:00
|
|
|
events_expiration:
|
|
|
|
description:
|
|
|
|
- The realm events expiration.
|
|
|
|
aliases:
|
|
|
|
- eventsExpiration
|
|
|
|
type: int
|
|
|
|
events_listeners:
|
|
|
|
description:
|
|
|
|
- The realm events listeners.
|
|
|
|
aliases:
|
|
|
|
- eventsListeners
|
|
|
|
type: list
|
2021-08-27 06:17:04 +02:00
|
|
|
elements: str
|
2021-04-21 12:42:28 +02:00
|
|
|
failure_factor:
|
|
|
|
description:
|
|
|
|
- The realm failure factor.
|
|
|
|
aliases:
|
|
|
|
- failureFactor
|
|
|
|
type: int
|
|
|
|
internationalization_enabled:
|
|
|
|
description:
|
|
|
|
- The realm internationalization enabled option.
|
|
|
|
aliases:
|
|
|
|
- internationalizationEnabled
|
|
|
|
type: bool
|
|
|
|
login_theme:
|
|
|
|
description:
|
|
|
|
- The realm login theme.
|
|
|
|
aliases:
|
|
|
|
- loginTheme
|
|
|
|
type: str
|
|
|
|
login_with_email_allowed:
|
|
|
|
description:
|
|
|
|
- The realm login with email allowed option.
|
|
|
|
aliases:
|
|
|
|
- loginWithEmailAllowed
|
|
|
|
type: bool
|
|
|
|
max_delta_time_seconds:
|
|
|
|
description:
|
|
|
|
- The realm max delta time in seconds.
|
|
|
|
aliases:
|
|
|
|
- maxDeltaTimeSeconds
|
|
|
|
type: int
|
|
|
|
max_failure_wait_seconds:
|
|
|
|
description:
|
|
|
|
- The realm max failure wait in seconds.
|
|
|
|
aliases:
|
|
|
|
- maxFailureWaitSeconds
|
|
|
|
type: int
|
|
|
|
minimum_quick_login_wait_seconds:
|
|
|
|
description:
|
|
|
|
- The realm minimum quick login wait in seconds.
|
|
|
|
aliases:
|
|
|
|
- minimumQuickLoginWaitSeconds
|
|
|
|
type: int
|
|
|
|
not_before:
|
|
|
|
description:
|
|
|
|
- The realm not before.
|
|
|
|
aliases:
|
|
|
|
- notBefore
|
|
|
|
type: int
|
|
|
|
offline_session_idle_timeout:
|
|
|
|
description:
|
|
|
|
- The realm offline session idle timeout.
|
|
|
|
aliases:
|
|
|
|
- offlineSessionIdleTimeout
|
|
|
|
type: int
|
|
|
|
offline_session_max_lifespan:
|
|
|
|
description:
|
|
|
|
- The realm offline session max lifespan.
|
|
|
|
aliases:
|
|
|
|
- offlineSessionMaxLifespan
|
|
|
|
type: int
|
|
|
|
offline_session_max_lifespan_enabled:
|
|
|
|
description:
|
|
|
|
- The realm offline session max lifespan enabled option.
|
|
|
|
aliases:
|
|
|
|
- offlineSessionMaxLifespanEnabled
|
|
|
|
type: bool
|
|
|
|
otp_policy_algorithm:
|
|
|
|
description:
|
|
|
|
- The realm otp policy algorithm.
|
|
|
|
aliases:
|
|
|
|
- otpPolicyAlgorithm
|
|
|
|
type: str
|
|
|
|
otp_policy_digits:
|
|
|
|
description:
|
|
|
|
- The realm otp policy digits.
|
|
|
|
aliases:
|
|
|
|
- otpPolicyDigits
|
|
|
|
type: int
|
|
|
|
otp_policy_initial_counter:
|
|
|
|
description:
|
|
|
|
- The realm otp policy initial counter.
|
|
|
|
aliases:
|
|
|
|
- otpPolicyInitialCounter
|
|
|
|
type: int
|
|
|
|
otp_policy_look_ahead_window:
|
|
|
|
description:
|
|
|
|
- The realm otp policy look ahead window.
|
|
|
|
aliases:
|
|
|
|
- otpPolicyLookAheadWindow
|
|
|
|
type: int
|
|
|
|
otp_policy_period:
|
|
|
|
description:
|
|
|
|
- The realm otp policy period.
|
|
|
|
aliases:
|
|
|
|
- otpPolicyPeriod
|
|
|
|
type: int
|
|
|
|
otp_policy_type:
|
|
|
|
description:
|
|
|
|
- The realm otp policy type.
|
|
|
|
aliases:
|
|
|
|
- otpPolicyType
|
|
|
|
type: str
|
|
|
|
otp_supported_applications:
|
|
|
|
description:
|
|
|
|
- The realm otp supported applications.
|
|
|
|
aliases:
|
|
|
|
- otpSupportedApplications
|
|
|
|
type: list
|
|
|
|
elements: str
|
|
|
|
password_policy:
|
|
|
|
description:
|
|
|
|
- The realm password policy.
|
|
|
|
aliases:
|
|
|
|
- passwordPolicy
|
|
|
|
type: str
|
|
|
|
permanent_lockout:
|
|
|
|
description:
|
|
|
|
- The realm permanent lockout.
|
|
|
|
aliases:
|
|
|
|
- permanentLockout
|
|
|
|
type: bool
|
|
|
|
quick_login_check_milli_seconds:
|
|
|
|
description:
|
|
|
|
- The realm quick login check in milliseconds.
|
|
|
|
aliases:
|
|
|
|
- quickLoginCheckMilliSeconds
|
|
|
|
type: int
|
|
|
|
refresh_token_max_reuse:
|
|
|
|
description:
|
|
|
|
- The realm refresh token max reuse.
|
|
|
|
aliases:
|
|
|
|
- refreshTokenMaxReuse
|
|
|
|
type: int
|
|
|
|
registration_allowed:
|
|
|
|
description:
|
|
|
|
- The realm registration allowed option.
|
|
|
|
aliases:
|
|
|
|
- registrationAllowed
|
|
|
|
type: bool
|
|
|
|
registration_email_as_username:
|
|
|
|
description:
|
|
|
|
- The realm registration email as username option.
|
|
|
|
aliases:
|
|
|
|
- registrationEmailAsUsername
|
|
|
|
type: bool
|
|
|
|
registration_flow:
|
|
|
|
description:
|
|
|
|
- The realm registration flow.
|
|
|
|
aliases:
|
|
|
|
- registrationFlow
|
|
|
|
type: str
|
|
|
|
remember_me:
|
|
|
|
description:
|
|
|
|
- The realm remember me option.
|
|
|
|
aliases:
|
|
|
|
- rememberMe
|
|
|
|
type: bool
|
|
|
|
reset_credentials_flow:
|
|
|
|
description:
|
|
|
|
- The realm reset credentials flow.
|
|
|
|
aliases:
|
|
|
|
- resetCredentialsFlow
|
|
|
|
type: str
|
|
|
|
reset_password_allowed:
|
|
|
|
description:
|
|
|
|
- The realm reset password allowed option.
|
|
|
|
aliases:
|
|
|
|
- resetPasswordAllowed
|
|
|
|
type: bool
|
|
|
|
revoke_refresh_token:
|
|
|
|
description:
|
|
|
|
- The realm revoke refresh token option.
|
|
|
|
aliases:
|
|
|
|
- revokeRefreshToken
|
|
|
|
type: bool
|
|
|
|
smtp_server:
|
|
|
|
description:
|
|
|
|
- The realm smtp server.
|
|
|
|
aliases:
|
|
|
|
- smtpServer
|
|
|
|
type: dict
|
|
|
|
ssl_required:
|
|
|
|
description:
|
|
|
|
- The realm ssl required option.
|
2021-06-03 21:44:54 +02:00
|
|
|
choices: ['all', 'external', 'none']
|
2021-04-21 12:42:28 +02:00
|
|
|
aliases:
|
|
|
|
- sslRequired
|
2021-06-03 21:44:54 +02:00
|
|
|
type: str
|
2021-04-21 12:42:28 +02:00
|
|
|
sso_session_idle_timeout:
|
|
|
|
description:
|
|
|
|
- The realm sso session idle timeout.
|
|
|
|
aliases:
|
|
|
|
- ssoSessionIdleTimeout
|
|
|
|
type: int
|
|
|
|
sso_session_idle_timeout_remember_me:
|
|
|
|
description:
|
|
|
|
- The realm sso session idle timeout remember me.
|
|
|
|
aliases:
|
|
|
|
- ssoSessionIdleTimeoutRememberMe
|
|
|
|
type: int
|
|
|
|
sso_session_max_lifespan:
|
|
|
|
description:
|
|
|
|
- The realm sso session max lifespan.
|
|
|
|
aliases:
|
|
|
|
- ssoSessionMaxLifespan
|
|
|
|
type: int
|
|
|
|
sso_session_max_lifespan_remember_me:
|
|
|
|
description:
|
|
|
|
- The realm sso session max lifespan remember me.
|
|
|
|
aliases:
|
|
|
|
- ssoSessionMaxLifespanRememberMe
|
|
|
|
type: int
|
|
|
|
supported_locales:
|
|
|
|
description:
|
|
|
|
- The realm supported locales.
|
|
|
|
aliases:
|
|
|
|
- supportedLocales
|
|
|
|
type: list
|
|
|
|
elements: str
|
|
|
|
user_managed_access_allowed:
|
|
|
|
description:
|
|
|
|
- The realm user managed access allowed option.
|
|
|
|
aliases:
|
|
|
|
- userManagedAccessAllowed
|
|
|
|
type: bool
|
|
|
|
verify_email:
|
|
|
|
description:
|
|
|
|
- The realm verify email option.
|
|
|
|
aliases:
|
|
|
|
- verifyEmail
|
|
|
|
type: bool
|
|
|
|
wait_increment_seconds:
|
|
|
|
description:
|
|
|
|
- The realm wait increment in seconds.
|
|
|
|
aliases:
|
|
|
|
- waitIncrementSeconds
|
|
|
|
type: int
|
|
|
|
|
|
|
|
extends_documentation_fragment:
|
2023-02-24 09:25:31 +01:00
|
|
|
- community.general.keycloak
|
|
|
|
- community.general.attributes
|
2021-04-21 12:42:28 +02:00
|
|
|
|
|
|
|
author:
|
|
|
|
- Christophe Gilles (@kris2kris)
|
|
|
|
'''
|
|
|
|
|
|
|
|
EXAMPLES = '''
|
|
|
|
- name: Create or update Keycloak realm (minimal example)
|
|
|
|
community.general.keycloak_realm:
|
|
|
|
auth_client_id: admin-cli
|
|
|
|
auth_keycloak_url: https://auth.example.com/auth
|
|
|
|
auth_realm: master
|
|
|
|
auth_username: USERNAME
|
|
|
|
auth_password: PASSWORD
|
|
|
|
id: realm
|
2022-12-01 22:20:11 +01:00
|
|
|
realm: realm
|
2021-04-21 12:42:28 +02:00
|
|
|
state: present
|
|
|
|
|
|
|
|
- name: Delete a Keycloak realm
|
|
|
|
community.general.keycloak_realm:
|
|
|
|
auth_client_id: admin-cli
|
|
|
|
auth_keycloak_url: https://auth.example.com/auth
|
|
|
|
auth_realm: master
|
|
|
|
auth_username: USERNAME
|
|
|
|
auth_password: PASSWORD
|
|
|
|
id: test
|
|
|
|
state: absent
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
RETURN = '''
|
|
|
|
msg:
|
2021-10-22 07:57:18 +02:00
|
|
|
description: Message as to what action was taken.
|
|
|
|
returned: always
|
|
|
|
type: str
|
|
|
|
sample: "Realm testrealm has been updated"
|
2021-04-21 12:42:28 +02:00
|
|
|
|
|
|
|
proposed:
|
2021-10-22 07:57:18 +02:00
|
|
|
description: Representation of proposed realm.
|
2021-04-21 12:42:28 +02:00
|
|
|
returned: always
|
|
|
|
type: dict
|
|
|
|
sample: {
|
|
|
|
id: "test"
|
|
|
|
}
|
2021-10-22 07:57:18 +02:00
|
|
|
|
2021-04-21 12:42:28 +02:00
|
|
|
existing:
|
2021-10-22 07:57:18 +02:00
|
|
|
description: Representation of existing realm (sample is truncated).
|
2021-04-21 12:42:28 +02:00
|
|
|
returned: always
|
|
|
|
type: dict
|
|
|
|
sample: {
|
|
|
|
"adminUrl": "http://www.example.com/admin_url",
|
|
|
|
"attributes": {
|
|
|
|
"request.object.signature.alg": "RS256",
|
|
|
|
}
|
|
|
|
}
|
2021-10-22 07:57:18 +02:00
|
|
|
|
2021-04-21 12:42:28 +02:00
|
|
|
end_state:
|
2021-10-22 07:57:18 +02:00
|
|
|
description: Representation of realm after module execution (sample is truncated).
|
|
|
|
returned: on success
|
2021-04-21 12:42:28 +02:00
|
|
|
type: dict
|
|
|
|
sample: {
|
|
|
|
"adminUrl": "http://www.example.com/admin_url",
|
|
|
|
"attributes": {
|
|
|
|
"request.object.signature.alg": "RS256",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
'''
|
|
|
|
|
|
|
|
from ansible_collections.community.general.plugins.module_utils.identity.keycloak.keycloak import KeycloakAPI, camel, \
|
|
|
|
keycloak_argument_spec, get_token, KeycloakError
|
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
|
|
|
|
|
|
|
2024-04-21 09:10:44 +02:00
|
|
|
def normalise_cr(realmrep):
|
|
|
|
""" Re-sorts any properties where the order is important so that diff's is minimised and the change detection is more effective.
|
|
|
|
|
|
|
|
:param realmrep: the realmrep dict to be sanitized
|
|
|
|
:return: normalised realmrep dict
|
|
|
|
"""
|
|
|
|
# Avoid the dict passed in to be modified
|
|
|
|
realmrep = realmrep.copy()
|
|
|
|
|
|
|
|
if 'enabledEventTypes' in realmrep:
|
|
|
|
realmrep['enabledEventTypes'] = list(sorted(realmrep['enabledEventTypes']))
|
|
|
|
|
|
|
|
if 'otpSupportedApplications' in realmrep:
|
|
|
|
realmrep['otpSupportedApplications'] = list(sorted(realmrep['otpSupportedApplications']))
|
|
|
|
|
|
|
|
if 'supportedLocales' in realmrep:
|
|
|
|
realmrep['supportedLocales'] = list(sorted(realmrep['supportedLocales']))
|
|
|
|
|
|
|
|
return realmrep
|
|
|
|
|
|
|
|
|
2021-04-21 12:42:28 +02:00
|
|
|
def sanitize_cr(realmrep):
|
2021-10-22 07:57:18 +02:00
|
|
|
""" Removes probably sensitive details from a realm representation.
|
2021-04-21 12:42:28 +02:00
|
|
|
|
|
|
|
:param realmrep: the realmrep dict to be sanitized
|
|
|
|
:return: sanitized realmrep dict
|
|
|
|
"""
|
|
|
|
result = realmrep.copy()
|
|
|
|
if 'secret' in result:
|
|
|
|
result['secret'] = '********'
|
|
|
|
if 'attributes' in result:
|
|
|
|
if 'saml.signing.private.key' in result['attributes']:
|
|
|
|
result['attributes'] = result['attributes'].copy()
|
|
|
|
result['attributes']['saml.signing.private.key'] = '********'
|
2024-04-21 09:10:44 +02:00
|
|
|
return normalise_cr(result)
|
2021-04-21 12:42:28 +02:00
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
"""
|
|
|
|
Module execution
|
|
|
|
|
|
|
|
:return:
|
|
|
|
"""
|
|
|
|
argument_spec = keycloak_argument_spec()
|
|
|
|
|
|
|
|
meta_args = dict(
|
|
|
|
state=dict(default='present', choices=['present', 'absent']),
|
|
|
|
|
|
|
|
id=dict(type='str'),
|
|
|
|
realm=dict(type='str'),
|
|
|
|
access_code_lifespan=dict(type='int', aliases=['accessCodeLifespan']),
|
|
|
|
access_code_lifespan_login=dict(type='int', aliases=['accessCodeLifespanLogin']),
|
|
|
|
access_code_lifespan_user_action=dict(type='int', aliases=['accessCodeLifespanUserAction']),
|
|
|
|
access_token_lifespan=dict(type='int', aliases=['accessTokenLifespan'], no_log=False),
|
|
|
|
access_token_lifespan_for_implicit_flow=dict(type='int', aliases=['accessTokenLifespanForImplicitFlow'], no_log=False),
|
|
|
|
account_theme=dict(type='str', aliases=['accountTheme']),
|
|
|
|
action_token_generated_by_admin_lifespan=dict(type='int', aliases=['actionTokenGeneratedByAdminLifespan'], no_log=False),
|
|
|
|
action_token_generated_by_user_lifespan=dict(type='int', aliases=['actionTokenGeneratedByUserLifespan'], no_log=False),
|
|
|
|
admin_events_details_enabled=dict(type='bool', aliases=['adminEventsDetailsEnabled']),
|
|
|
|
admin_events_enabled=dict(type='bool', aliases=['adminEventsEnabled']),
|
|
|
|
admin_theme=dict(type='str', aliases=['adminTheme']),
|
|
|
|
attributes=dict(type='dict'),
|
|
|
|
browser_flow=dict(type='str', aliases=['browserFlow']),
|
|
|
|
browser_security_headers=dict(type='dict', aliases=['browserSecurityHeaders']),
|
|
|
|
brute_force_protected=dict(type='bool', aliases=['bruteForceProtected']),
|
|
|
|
client_authentication_flow=dict(type='str', aliases=['clientAuthenticationFlow']),
|
|
|
|
client_scope_mappings=dict(type='dict', aliases=['clientScopeMappings']),
|
2022-04-22 22:31:38 +02:00
|
|
|
default_default_client_scopes=dict(type='list', elements='str', aliases=['defaultDefaultClientScopes']),
|
2022-05-30 12:48:06 +02:00
|
|
|
default_groups=dict(type='list', elements='str', aliases=['defaultGroups']),
|
2021-04-21 12:42:28 +02:00
|
|
|
default_locale=dict(type='str', aliases=['defaultLocale']),
|
2022-04-22 22:31:38 +02:00
|
|
|
default_optional_client_scopes=dict(type='list', elements='str', aliases=['defaultOptionalClientScopes']),
|
2022-05-30 12:48:06 +02:00
|
|
|
default_roles=dict(type='list', elements='str', aliases=['defaultRoles']),
|
2021-04-21 12:42:28 +02:00
|
|
|
default_signature_algorithm=dict(type='str', aliases=['defaultSignatureAlgorithm']),
|
|
|
|
direct_grant_flow=dict(type='str', aliases=['directGrantFlow']),
|
|
|
|
display_name=dict(type='str', aliases=['displayName']),
|
|
|
|
display_name_html=dict(type='str', aliases=['displayNameHtml']),
|
|
|
|
docker_authentication_flow=dict(type='str', aliases=['dockerAuthenticationFlow']),
|
|
|
|
duplicate_emails_allowed=dict(type='bool', aliases=['duplicateEmailsAllowed']),
|
|
|
|
edit_username_allowed=dict(type='bool', aliases=['editUsernameAllowed']),
|
|
|
|
email_theme=dict(type='str', aliases=['emailTheme']),
|
|
|
|
enabled=dict(type='bool'),
|
|
|
|
enabled_event_types=dict(type='list', elements='str', aliases=['enabledEventTypes']),
|
2021-08-27 06:17:04 +02:00
|
|
|
events_enabled=dict(type='bool', aliases=['eventsEnabled']),
|
2021-04-21 12:42:28 +02:00
|
|
|
events_expiration=dict(type='int', aliases=['eventsExpiration']),
|
2021-08-27 06:17:04 +02:00
|
|
|
events_listeners=dict(type='list', elements='str', aliases=['eventsListeners']),
|
2021-04-21 12:42:28 +02:00
|
|
|
failure_factor=dict(type='int', aliases=['failureFactor']),
|
|
|
|
internationalization_enabled=dict(type='bool', aliases=['internationalizationEnabled']),
|
|
|
|
login_theme=dict(type='str', aliases=['loginTheme']),
|
|
|
|
login_with_email_allowed=dict(type='bool', aliases=['loginWithEmailAllowed']),
|
|
|
|
max_delta_time_seconds=dict(type='int', aliases=['maxDeltaTimeSeconds']),
|
|
|
|
max_failure_wait_seconds=dict(type='int', aliases=['maxFailureWaitSeconds']),
|
|
|
|
minimum_quick_login_wait_seconds=dict(type='int', aliases=['minimumQuickLoginWaitSeconds']),
|
|
|
|
not_before=dict(type='int', aliases=['notBefore']),
|
|
|
|
offline_session_idle_timeout=dict(type='int', aliases=['offlineSessionIdleTimeout']),
|
|
|
|
offline_session_max_lifespan=dict(type='int', aliases=['offlineSessionMaxLifespan']),
|
|
|
|
offline_session_max_lifespan_enabled=dict(type='bool', aliases=['offlineSessionMaxLifespanEnabled']),
|
|
|
|
otp_policy_algorithm=dict(type='str', aliases=['otpPolicyAlgorithm']),
|
|
|
|
otp_policy_digits=dict(type='int', aliases=['otpPolicyDigits']),
|
|
|
|
otp_policy_initial_counter=dict(type='int', aliases=['otpPolicyInitialCounter']),
|
|
|
|
otp_policy_look_ahead_window=dict(type='int', aliases=['otpPolicyLookAheadWindow']),
|
|
|
|
otp_policy_period=dict(type='int', aliases=['otpPolicyPeriod']),
|
|
|
|
otp_policy_type=dict(type='str', aliases=['otpPolicyType']),
|
|
|
|
otp_supported_applications=dict(type='list', elements='str', aliases=['otpSupportedApplications']),
|
|
|
|
password_policy=dict(type='str', aliases=['passwordPolicy'], no_log=False),
|
|
|
|
permanent_lockout=dict(type='bool', aliases=['permanentLockout']),
|
|
|
|
quick_login_check_milli_seconds=dict(type='int', aliases=['quickLoginCheckMilliSeconds']),
|
|
|
|
refresh_token_max_reuse=dict(type='int', aliases=['refreshTokenMaxReuse'], no_log=False),
|
|
|
|
registration_allowed=dict(type='bool', aliases=['registrationAllowed']),
|
|
|
|
registration_email_as_username=dict(type='bool', aliases=['registrationEmailAsUsername']),
|
|
|
|
registration_flow=dict(type='str', aliases=['registrationFlow']),
|
|
|
|
remember_me=dict(type='bool', aliases=['rememberMe']),
|
|
|
|
reset_credentials_flow=dict(type='str', aliases=['resetCredentialsFlow']),
|
2021-06-03 21:42:05 +02:00
|
|
|
reset_password_allowed=dict(type='bool', aliases=['resetPasswordAllowed'], no_log=False),
|
2021-04-21 12:42:28 +02:00
|
|
|
revoke_refresh_token=dict(type='bool', aliases=['revokeRefreshToken']),
|
|
|
|
smtp_server=dict(type='dict', aliases=['smtpServer']),
|
2021-06-03 21:44:54 +02:00
|
|
|
ssl_required=dict(choices=["external", "all", "none"], aliases=['sslRequired']),
|
2021-04-21 12:42:28 +02:00
|
|
|
sso_session_idle_timeout=dict(type='int', aliases=['ssoSessionIdleTimeout']),
|
|
|
|
sso_session_idle_timeout_remember_me=dict(type='int', aliases=['ssoSessionIdleTimeoutRememberMe']),
|
|
|
|
sso_session_max_lifespan=dict(type='int', aliases=['ssoSessionMaxLifespan']),
|
|
|
|
sso_session_max_lifespan_remember_me=dict(type='int', aliases=['ssoSessionMaxLifespanRememberMe']),
|
|
|
|
supported_locales=dict(type='list', elements='str', aliases=['supportedLocales']),
|
|
|
|
user_managed_access_allowed=dict(type='bool', aliases=['userManagedAccessAllowed']),
|
|
|
|
verify_email=dict(type='bool', aliases=['verifyEmail']),
|
|
|
|
wait_increment_seconds=dict(type='int', aliases=['waitIncrementSeconds']),
|
|
|
|
)
|
2021-10-22 07:57:18 +02:00
|
|
|
|
2021-04-21 12:42:28 +02:00
|
|
|
argument_spec.update(meta_args)
|
|
|
|
|
|
|
|
module = AnsibleModule(argument_spec=argument_spec,
|
|
|
|
supports_check_mode=True,
|
|
|
|
required_one_of=([['id', 'realm', 'enabled'],
|
|
|
|
['token', 'auth_realm', 'auth_username', 'auth_password']]),
|
|
|
|
required_together=([['auth_realm', 'auth_username', 'auth_password']]))
|
|
|
|
|
|
|
|
result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={})
|
|
|
|
|
|
|
|
# Obtain access token, initialize API
|
|
|
|
try:
|
|
|
|
connection_header = get_token(module.params)
|
|
|
|
except KeycloakError as e:
|
|
|
|
module.fail_json(msg=str(e))
|
|
|
|
|
|
|
|
kc = KeycloakAPI(module, connection_header)
|
|
|
|
|
|
|
|
realm = module.params.get('realm')
|
|
|
|
state = module.params.get('state')
|
|
|
|
|
|
|
|
# convert module parameters to realm representation parameters (if they belong in there)
|
|
|
|
params_to_ignore = list(keycloak_argument_spec().keys()) + ['state']
|
2021-10-22 07:57:18 +02:00
|
|
|
|
|
|
|
# Filter and map the parameters names that apply to the role
|
2021-04-21 12:42:28 +02:00
|
|
|
realm_params = [x for x in module.params
|
|
|
|
if x not in params_to_ignore and
|
|
|
|
module.params.get(x) is not None]
|
|
|
|
|
|
|
|
# See whether the realm already exists in Keycloak
|
2021-10-22 07:57:18 +02:00
|
|
|
before_realm = kc.get_realm_by_id(realm=realm)
|
|
|
|
|
|
|
|
if before_realm is None:
|
|
|
|
before_realm = {}
|
2021-04-21 12:42:28 +02:00
|
|
|
|
|
|
|
# Build a proposed changeset from parameters given to this module
|
2021-10-22 07:57:18 +02:00
|
|
|
changeset = {}
|
2021-04-21 12:42:28 +02:00
|
|
|
|
|
|
|
for realm_param in realm_params:
|
|
|
|
new_param_value = module.params.get(realm_param)
|
|
|
|
changeset[camel(realm_param)] = new_param_value
|
|
|
|
|
2021-10-22 07:57:18 +02:00
|
|
|
# Prepare the desired values using the existing values (non-existence results in a dict that is save to use as a basis)
|
|
|
|
desired_realm = before_realm.copy()
|
|
|
|
desired_realm.update(changeset)
|
2021-04-21 12:42:28 +02:00
|
|
|
|
|
|
|
result['proposed'] = sanitize_cr(changeset)
|
|
|
|
before_realm_sanitized = sanitize_cr(before_realm)
|
|
|
|
result['existing'] = before_realm_sanitized
|
|
|
|
|
2021-10-22 07:57:18 +02:00
|
|
|
# Cater for when it doesn't exist (an empty dict)
|
2021-04-21 12:42:28 +02:00
|
|
|
if not before_realm:
|
|
|
|
if state == 'absent':
|
2021-10-22 07:57:18 +02:00
|
|
|
# Do nothing and exit
|
2021-04-21 12:42:28 +02:00
|
|
|
if module._diff:
|
|
|
|
result['diff'] = dict(before='', after='')
|
2021-10-22 07:57:18 +02:00
|
|
|
result['changed'] = False
|
|
|
|
result['end_state'] = {}
|
2021-04-21 12:42:28 +02:00
|
|
|
result['msg'] = 'Realm does not exist, doing nothing.'
|
|
|
|
module.exit_json(**result)
|
|
|
|
|
2021-10-22 07:57:18 +02:00
|
|
|
# Process a creation
|
2021-04-21 12:42:28 +02:00
|
|
|
result['changed'] = True
|
2021-10-22 07:57:18 +02:00
|
|
|
|
|
|
|
if 'id' not in desired_realm:
|
2021-04-21 12:42:28 +02:00
|
|
|
module.fail_json(msg='id needs to be specified when creating a new realm')
|
|
|
|
|
|
|
|
if module._diff:
|
2021-10-22 07:57:18 +02:00
|
|
|
result['diff'] = dict(before='', after=sanitize_cr(desired_realm))
|
2021-04-21 12:42:28 +02:00
|
|
|
|
|
|
|
if module.check_mode:
|
|
|
|
module.exit_json(**result)
|
|
|
|
|
2021-10-22 07:57:18 +02:00
|
|
|
# create it
|
|
|
|
kc.create_realm(desired_realm)
|
|
|
|
after_realm = kc.get_realm_by_id(desired_realm['id'])
|
2021-04-21 12:42:28 +02:00
|
|
|
|
|
|
|
result['end_state'] = sanitize_cr(after_realm)
|
|
|
|
|
2021-10-22 07:57:18 +02:00
|
|
|
result['msg'] = 'Realm %s has been created.' % desired_realm['id']
|
2021-04-21 12:42:28 +02:00
|
|
|
module.exit_json(**result)
|
2021-10-22 07:57:18 +02:00
|
|
|
|
2021-04-21 12:42:28 +02:00
|
|
|
else:
|
|
|
|
if state == 'present':
|
2021-10-22 07:57:18 +02:00
|
|
|
# Process an update
|
|
|
|
|
|
|
|
# doing an update
|
2021-04-21 12:42:28 +02:00
|
|
|
result['changed'] = True
|
|
|
|
if module.check_mode:
|
|
|
|
# We can only compare the current realm with the proposed updates we have
|
2024-04-21 09:10:44 +02:00
|
|
|
before_norm = normalise_cr(before_realm)
|
|
|
|
desired_norm = normalise_cr(desired_realm)
|
2021-04-21 12:42:28 +02:00
|
|
|
if module._diff:
|
2024-04-21 09:10:44 +02:00
|
|
|
result['diff'] = dict(before=sanitize_cr(before_norm),
|
|
|
|
after=sanitize_cr(desired_norm))
|
2021-10-22 07:57:18 +02:00
|
|
|
result['changed'] = (before_realm != desired_realm)
|
2021-04-21 12:42:28 +02:00
|
|
|
|
|
|
|
module.exit_json(**result)
|
|
|
|
|
2021-10-22 07:57:18 +02:00
|
|
|
# do the update
|
|
|
|
kc.update_realm(desired_realm, realm=realm)
|
2021-04-21 12:42:28 +02:00
|
|
|
|
|
|
|
after_realm = kc.get_realm_by_id(realm=realm)
|
2021-10-22 07:57:18 +02:00
|
|
|
|
2021-04-21 12:42:28 +02:00
|
|
|
if before_realm == after_realm:
|
|
|
|
result['changed'] = False
|
2021-10-22 07:57:18 +02:00
|
|
|
|
|
|
|
result['end_state'] = sanitize_cr(after_realm)
|
|
|
|
|
2021-04-21 12:42:28 +02:00
|
|
|
if module._diff:
|
|
|
|
result['diff'] = dict(before=before_realm_sanitized,
|
|
|
|
after=sanitize_cr(after_realm))
|
|
|
|
|
2021-10-22 07:57:18 +02:00
|
|
|
result['msg'] = 'Realm %s has been updated.' % desired_realm['id']
|
2021-04-21 12:42:28 +02:00
|
|
|
module.exit_json(**result)
|
2021-10-22 07:57:18 +02:00
|
|
|
|
2021-04-21 12:42:28 +02:00
|
|
|
else:
|
2021-10-22 07:57:18 +02:00
|
|
|
# Process a deletion (because state was not 'present')
|
2021-04-21 12:42:28 +02:00
|
|
|
result['changed'] = True
|
2021-10-22 07:57:18 +02:00
|
|
|
|
2021-04-21 12:42:28 +02:00
|
|
|
if module._diff:
|
2021-10-22 07:57:18 +02:00
|
|
|
result['diff'] = dict(before=before_realm_sanitized, after='')
|
2021-04-21 12:42:28 +02:00
|
|
|
|
|
|
|
if module.check_mode:
|
|
|
|
module.exit_json(**result)
|
|
|
|
|
2021-10-22 07:57:18 +02:00
|
|
|
# delete it
|
2021-04-21 12:42:28 +02:00
|
|
|
kc.delete_realm(realm=realm)
|
2021-10-22 07:57:18 +02:00
|
|
|
|
|
|
|
result['proposed'] = {}
|
|
|
|
result['end_state'] = {}
|
|
|
|
|
2021-04-21 12:42:28 +02:00
|
|
|
result['msg'] = 'Realm %s has been deleted.' % before_realm['id']
|
|
|
|
|
|
|
|
module.exit_json(**result)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|