mirror of
https://github.com/ansible-middleware/keycloak.git
synced 2026-06-13 12:05:54 +00:00
Move community.general keycloak modules into keycloak collection
This commit is contained in:
775
plugins/modules/keycloak_identity_provider.py
Normal file
775
plugins/modules/keycloak_identity_provider.py
Normal file
@@ -0,0 +1,775 @@
|
||||
|
||||
# Copyright (c) Ansible project
|
||||
# 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
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
module: keycloak_identity_provider
|
||||
|
||||
short_description: Allows administration of Keycloak identity providers using Keycloak API
|
||||
|
||||
version_added: "3.0.0"
|
||||
|
||||
description:
|
||||
- This module allows you to add, remove or modify Keycloak identity providers using the Keycloak REST API. It requires access
|
||||
to the REST API using OpenID Connect; the user connecting and the client 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 client 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/15.0/rest-api/index.html).
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
diff_mode:
|
||||
support: full
|
||||
action_group:
|
||||
version_added: "3.0.0"
|
||||
|
||||
options:
|
||||
state:
|
||||
description:
|
||||
- State of the identity provider.
|
||||
- On V(present), the identity provider is created if it does not yet exist, or updated with the parameters you provide.
|
||||
- On V(absent), the identity provider is removed if it exists.
|
||||
default: 'present'
|
||||
type: str
|
||||
choices:
|
||||
- present
|
||||
- absent
|
||||
|
||||
realm:
|
||||
description:
|
||||
- The Keycloak realm under which this identity provider resides.
|
||||
default: 'master'
|
||||
type: str
|
||||
|
||||
alias:
|
||||
description:
|
||||
- The alias uniquely identifies an identity provider and it is also used to build the redirect URI.
|
||||
required: true
|
||||
type: str
|
||||
|
||||
display_name:
|
||||
description:
|
||||
- Friendly name for identity provider.
|
||||
aliases:
|
||||
- displayName
|
||||
type: str
|
||||
|
||||
enabled:
|
||||
description:
|
||||
- Enable/disable this identity provider.
|
||||
type: bool
|
||||
|
||||
store_token:
|
||||
description:
|
||||
- Enable/disable whether tokens must be stored after authenticating users.
|
||||
aliases:
|
||||
- storeToken
|
||||
type: bool
|
||||
|
||||
add_read_token_role_on_create:
|
||||
description:
|
||||
- Enable/disable whether new users can read any stored tokens. This assigns the C(broker.read-token) role.
|
||||
aliases:
|
||||
- addReadTokenRoleOnCreate
|
||||
type: bool
|
||||
|
||||
trust_email:
|
||||
description:
|
||||
- If enabled, email provided by this provider is not verified even if verification is enabled for the realm.
|
||||
aliases:
|
||||
- trustEmail
|
||||
type: bool
|
||||
|
||||
link_only:
|
||||
description:
|
||||
- If true, users cannot log in through this provider. They can only link to this provider. This is useful if you do
|
||||
not want to allow login from the provider, but want to integrate with a provider.
|
||||
aliases:
|
||||
- linkOnly
|
||||
type: bool
|
||||
|
||||
first_broker_login_flow_alias:
|
||||
description:
|
||||
- Alias of authentication flow, which is triggered after first login with this identity provider.
|
||||
aliases:
|
||||
- firstBrokerLoginFlowAlias
|
||||
type: str
|
||||
|
||||
post_broker_login_flow_alias:
|
||||
description:
|
||||
- Alias of authentication flow, which is triggered after each login with this identity provider.
|
||||
aliases:
|
||||
- postBrokerLoginFlowAlias
|
||||
type: str
|
||||
|
||||
authenticate_by_default:
|
||||
description:
|
||||
- Specifies if this identity provider should be used by default for authentication even before displaying login screen.
|
||||
aliases:
|
||||
- authenticateByDefault
|
||||
type: bool
|
||||
|
||||
provider_id:
|
||||
description:
|
||||
- Protocol used by this provider (supported values are V(oidc) or V(saml)).
|
||||
aliases:
|
||||
- providerId
|
||||
type: str
|
||||
|
||||
config:
|
||||
description:
|
||||
- Dict specifying the configuration options for the provider; the contents differ depending on the value of O(provider_id).
|
||||
Examples are given below for V(oidc) and V(saml). It is easiest to obtain valid config values by dumping an already-existing
|
||||
identity provider configuration through check-mode in the RV(existing) field.
|
||||
type: dict
|
||||
suboptions:
|
||||
hide_on_login_page:
|
||||
description:
|
||||
- If hidden, login with this provider is possible only if requested explicitly, for example using the C(kc_idp_hint)
|
||||
parameter.
|
||||
aliases:
|
||||
- hideOnLoginPage
|
||||
type: bool
|
||||
|
||||
gui_order:
|
||||
description:
|
||||
- Number defining order of the provider in GUI (for example, on Login page).
|
||||
aliases:
|
||||
- guiOrder
|
||||
type: int
|
||||
|
||||
sync_mode:
|
||||
description:
|
||||
- Default sync mode for all mappers. The sync mode determines when user data is synced using the mappers.
|
||||
aliases:
|
||||
- syncMode
|
||||
type: str
|
||||
|
||||
issuer:
|
||||
description:
|
||||
- The issuer identifier for the issuer of the response. If not provided, no validation is performed.
|
||||
type: str
|
||||
|
||||
authorizationUrl:
|
||||
description:
|
||||
- The Authorization URL.
|
||||
type: str
|
||||
|
||||
tokenUrl:
|
||||
description:
|
||||
- The Token URL.
|
||||
type: str
|
||||
|
||||
logoutUrl:
|
||||
description:
|
||||
- End session endpoint to use to logout user from external IDP.
|
||||
type: str
|
||||
|
||||
userInfoUrl:
|
||||
description:
|
||||
- The User Info URL.
|
||||
type: str
|
||||
|
||||
clientAuthMethod:
|
||||
description:
|
||||
- The client authentication method.
|
||||
type: str
|
||||
|
||||
clientId:
|
||||
description:
|
||||
- The client or client identifier registered within the identity provider.
|
||||
type: str
|
||||
|
||||
clientSecret:
|
||||
description:
|
||||
- The client or client secret registered within the identity provider.
|
||||
type: str
|
||||
|
||||
defaultScope:
|
||||
description:
|
||||
- The scopes to be sent when asking for authorization.
|
||||
type: str
|
||||
|
||||
validateSignature:
|
||||
description:
|
||||
- Enable/disable signature validation of external IDP signatures.
|
||||
type: bool
|
||||
|
||||
useJwksUrl:
|
||||
description:
|
||||
- If V(true), identity provider public keys are downloaded from given JWKS URL.
|
||||
type: bool
|
||||
|
||||
jwksUrl:
|
||||
description:
|
||||
- URL where identity provider keys in JWK format are stored. See JWK specification for more details.
|
||||
type: str
|
||||
|
||||
entityId:
|
||||
description:
|
||||
- The Entity ID that is used to uniquely identify this SAML Service Provider.
|
||||
type: str
|
||||
|
||||
singleSignOnServiceUrl:
|
||||
description:
|
||||
- The URL that must be used to send authentication requests (SAML AuthnRequest).
|
||||
type: str
|
||||
|
||||
singleLogoutServiceUrl:
|
||||
description:
|
||||
- The URL that must be used to send logout requests.
|
||||
type: str
|
||||
|
||||
backchannelSupported:
|
||||
description:
|
||||
- Does the external IDP support backchannel logout?
|
||||
type: str
|
||||
|
||||
nameIDPolicyFormat:
|
||||
description:
|
||||
- Specifies the URI reference corresponding to a name identifier format.
|
||||
type: str
|
||||
|
||||
principalType:
|
||||
description:
|
||||
- Way to identify and track external users from the assertion.
|
||||
type: str
|
||||
|
||||
fromUrl:
|
||||
description:
|
||||
- IDP well-known OpenID Connect configuration URL.
|
||||
- Support only O(provider_id=oidc).
|
||||
- O(config.fromUrl) is mutually exclusive with O(config.userInfoUrl), O(config.authorizationUrl),
|
||||
O(config.tokenUrl), O(config.logoutUrl), O(config.issuer) and O(config.jwksUrl).
|
||||
type: str
|
||||
|
||||
mappers:
|
||||
description:
|
||||
- A list of dicts defining mappers associated with this Identity Provider.
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
id:
|
||||
description:
|
||||
- Unique ID of this mapper.
|
||||
type: str
|
||||
|
||||
name:
|
||||
description:
|
||||
- Name of the mapper.
|
||||
type: str
|
||||
|
||||
identityProviderAlias:
|
||||
description:
|
||||
- Alias of the identity provider for this mapper.
|
||||
type: str
|
||||
|
||||
identityProviderMapper:
|
||||
description:
|
||||
- Type of mapper.
|
||||
type: str
|
||||
|
||||
config:
|
||||
description:
|
||||
- Dict specifying the configuration options for the mapper; the contents differ depending on the value of O(mappers[].identityProviderMapper).
|
||||
type: dict
|
||||
|
||||
extends_documentation_fragment:
|
||||
- middleware_automation.keycloak.keycloak
|
||||
- middleware_automation.keycloak.actiongroup_keycloak
|
||||
- middleware_automation.keycloak.attributes
|
||||
|
||||
author:
|
||||
- Laurent Paumier (@laurpaum)
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
- name: Create OIDC identity provider, authentication with credentials
|
||||
middleware_automation.keycloak.keycloak_identity_provider:
|
||||
state: present
|
||||
auth_keycloak_url: https://auth.example.com
|
||||
auth_realm: master
|
||||
auth_username: admin
|
||||
auth_password: admin
|
||||
realm: myrealm
|
||||
alias: oidc-idp
|
||||
display_name: OpenID Connect IdP
|
||||
enabled: true
|
||||
provider_id: oidc
|
||||
config:
|
||||
issuer: https://idp.example.com
|
||||
authorizationUrl: https://idp.example.com
|
||||
tokenUrl: https://idp.example.com/token
|
||||
userInfoUrl: https://idp.example.com/userinfo
|
||||
clientAuthMethod: client_secret_post
|
||||
clientId: my-client
|
||||
clientSecret: secret
|
||||
syncMode: FORCE
|
||||
mappers:
|
||||
- name: first_name
|
||||
identityProviderMapper: oidc-user-attribute-idp-mapper
|
||||
config:
|
||||
claim: first_name
|
||||
user.attribute: first_name
|
||||
syncMode: INHERIT
|
||||
- name: last_name
|
||||
identityProviderMapper: oidc-user-attribute-idp-mapper
|
||||
config:
|
||||
claim: last_name
|
||||
user.attribute: last_name
|
||||
syncMode: INHERIT
|
||||
|
||||
- name: Create OIDC identity provider, with well-known configuration URL
|
||||
middleware_automation.keycloak.keycloak_identity_provider:
|
||||
state: present
|
||||
auth_keycloak_url: https://auth.example.com
|
||||
auth_realm: master
|
||||
auth_username: admin
|
||||
auth_password: admin
|
||||
realm: myrealm
|
||||
alias: oidc-idp
|
||||
display_name: OpenID Connect IdP
|
||||
enabled: true
|
||||
provider_id: oidc
|
||||
config:
|
||||
fromUrl: https://the-idp.example.com/realms/idprealm/.well-known/openid-configuration
|
||||
clientAuthMethod: client_secret_post
|
||||
clientId: my-client
|
||||
clientSecret: secret
|
||||
|
||||
- name: Create SAML identity provider, authentication with credentials
|
||||
middleware_automation.keycloak.keycloak_identity_provider:
|
||||
state: present
|
||||
auth_keycloak_url: https://auth.example.com
|
||||
auth_realm: master
|
||||
auth_username: admin
|
||||
auth_password: admin
|
||||
realm: myrealm
|
||||
alias: saml-idp
|
||||
display_name: SAML IdP
|
||||
enabled: true
|
||||
provider_id: saml
|
||||
config:
|
||||
entityId: https://auth.example.com/realms/myrealm
|
||||
singleSignOnServiceUrl: https://idp.example.com/login
|
||||
wantAuthnRequestsSigned: true
|
||||
wantAssertionsSigned: true
|
||||
mappers:
|
||||
- name: roles
|
||||
identityProviderMapper: saml-user-attribute-idp-mapper
|
||||
config:
|
||||
user.attribute: roles
|
||||
attribute.friendly.name: User Roles
|
||||
attribute.name: roles
|
||||
syncMode: INHERIT
|
||||
|
||||
- name: Create OIDC identity provider, authentication with credentials and advanced claim to group
|
||||
middleware_automation.keycloak.keycloak_identity_provider:
|
||||
state: present
|
||||
auth_keycloak_url: https://auth.example.com
|
||||
auth_realm: master
|
||||
auth_username: admin
|
||||
auth_password: admin
|
||||
realm: myrealm
|
||||
alias: oidc-idp
|
||||
display_name: OpenID Connect IdP
|
||||
enabled: true
|
||||
provider_id: oidc
|
||||
config:
|
||||
issuer: https://idp.example.com
|
||||
authorizationUrl: https://idp.example.com
|
||||
tokenUrl: https://idp.example.com/token
|
||||
userInfoUrl: https://idp.example.com/userinfo
|
||||
clientAuthMethod: client_secret_post
|
||||
clientId: my-client
|
||||
clientSecret: secret
|
||||
syncMode: FORCE
|
||||
mappers:
|
||||
- name: group_name
|
||||
identityProviderMapper: oidc-advanced-group-idp-mapper
|
||||
config:
|
||||
claims: '[{"key":"my_key","value":"my_value"}]'
|
||||
group: group_name
|
||||
syncMode: INHERIT
|
||||
"""
|
||||
|
||||
RETURN = r"""
|
||||
msg:
|
||||
description: Message as to what action was taken.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "Identity provider my-idp has been created"
|
||||
|
||||
proposed:
|
||||
description: Representation of proposed identity provider.
|
||||
returned: always
|
||||
type: dict
|
||||
sample:
|
||||
{
|
||||
"config": {
|
||||
"authorizationUrl": "https://idp.example.com",
|
||||
"clientAuthMethod": "client_secret_post",
|
||||
"clientId": "my-client",
|
||||
"clientSecret": "secret",
|
||||
"issuer": "https://idp.example.com",
|
||||
"tokenUrl": "https://idp.example.com/token",
|
||||
"userInfoUrl": "https://idp.example.com/userinfo"
|
||||
},
|
||||
"displayName": "OpenID Connect IdP",
|
||||
"providerId": "oidc"
|
||||
}
|
||||
|
||||
existing:
|
||||
description: Representation of existing identity provider.
|
||||
returned: always
|
||||
type: dict
|
||||
sample:
|
||||
{
|
||||
"addReadTokenRoleOnCreate": false,
|
||||
"alias": "my-idp",
|
||||
"authenticateByDefault": false,
|
||||
"config": {
|
||||
"authorizationUrl": "https://old.example.com",
|
||||
"clientAuthMethod": "client_secret_post",
|
||||
"clientId": "my-client",
|
||||
"clientSecret": "**********",
|
||||
"issuer": "https://old.example.com",
|
||||
"syncMode": "FORCE",
|
||||
"tokenUrl": "https://old.example.com/token",
|
||||
"userInfoUrl": "https://old.example.com/userinfo"
|
||||
},
|
||||
"displayName": "OpenID Connect IdP",
|
||||
"enabled": true,
|
||||
"firstBrokerLoginFlowAlias": "first broker login",
|
||||
"internalId": "4d28d7e3-1b80-45bb-8a30-5822bf55aa1c",
|
||||
"linkOnly": false,
|
||||
"providerId": "oidc",
|
||||
"storeToken": false,
|
||||
"trustEmail": false
|
||||
}
|
||||
|
||||
end_state:
|
||||
description: Representation of identity provider after module execution.
|
||||
returned: on success
|
||||
type: dict
|
||||
sample:
|
||||
{
|
||||
"addReadTokenRoleOnCreate": false,
|
||||
"alias": "my-idp",
|
||||
"authenticateByDefault": false,
|
||||
"config": {
|
||||
"authorizationUrl": "https://idp.example.com",
|
||||
"clientAuthMethod": "client_secret_post",
|
||||
"clientId": "my-client",
|
||||
"clientSecret": "**********",
|
||||
"issuer": "https://idp.example.com",
|
||||
"tokenUrl": "https://idp.example.com/token",
|
||||
"userInfoUrl": "https://idp.example.com/userinfo"
|
||||
},
|
||||
"displayName": "OpenID Connect IdP",
|
||||
"enabled": true,
|
||||
"firstBrokerLoginFlowAlias": "first broker login",
|
||||
"internalId": "4d28d7e3-1b80-45bb-8a30-5822bf55aa1c",
|
||||
"linkOnly": false,
|
||||
"providerId": "oidc",
|
||||
"storeToken": false,
|
||||
"trustEmail": false
|
||||
}
|
||||
"""
|
||||
|
||||
from copy import deepcopy
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
from ansible_collections.middleware_automation.keycloak.plugins.module_utils.identity.keycloak.keycloak import (
|
||||
KeycloakAPI,
|
||||
KeycloakError,
|
||||
camel,
|
||||
get_token,
|
||||
keycloak_argument_spec,
|
||||
)
|
||||
|
||||
|
||||
def sanitize(idp):
|
||||
idpcopy = deepcopy(idp)
|
||||
if "config" in idpcopy:
|
||||
if "clientSecret" in idpcopy["config"]:
|
||||
idpcopy["config"]["clientSecret"] = "**********"
|
||||
return idpcopy
|
||||
|
||||
|
||||
def get_identity_provider_with_mappers(kc, alias, realm):
|
||||
idp = kc.get_identity_provider(alias, realm)
|
||||
if idp is not None:
|
||||
idp["mappers"] = sorted(kc.get_identity_provider_mappers(alias, realm), key=lambda x: x.get("name"))
|
||||
# clientSecret returned by API when using `get_identity_provider(alias, realm)` is always **********
|
||||
# to detect changes to the secret, we get the actual cleartext secret from the full realm info
|
||||
if "config" in idp:
|
||||
if "clientSecret" in idp["config"]:
|
||||
for idp_from_realm in kc.get_realm_by_id(realm).get("identityProviders", []):
|
||||
if idp_from_realm["internalId"] == idp["internalId"]:
|
||||
cleartext_secret = idp_from_realm.get("config", {}).get("clientSecret")
|
||||
if cleartext_secret:
|
||||
idp["config"]["clientSecret"] = cleartext_secret
|
||||
if idp is None:
|
||||
idp = {}
|
||||
return idp
|
||||
|
||||
|
||||
def fetch_identity_provider_wellknown_config(kc, config):
|
||||
"""
|
||||
Fetches OpenID Connect well-known configuration from a given URL and updates the config dict with discovered endpoints.
|
||||
Support for oidc providers only.
|
||||
:param kc: KeycloakAPI instance used to fetch endpoints and handle errors.
|
||||
:param config: Dictionary containing identity provider configuration, must include 'fromUrl' key to trigger fetch.
|
||||
:return: None. The config dict is updated in-place.
|
||||
"""
|
||||
if config and "fromUrl" in config:
|
||||
if "providerId" in config and config["providerId"] != "oidc":
|
||||
kc.module.fail_json(msg="Only 'oidc' provider_id is supported when using 'fromUrl'.")
|
||||
endpoints = ["userInfoUrl", "authorizationUrl", "tokenUrl", "logoutUrl", "issuer", "jwksUrl"]
|
||||
if any(k in config for k in endpoints):
|
||||
kc.module.fail_json(
|
||||
msg="Cannot specify both 'fromUrl' and 'userInfoUrl', 'authorizationUrl', 'tokenUrl', 'logoutUrl', 'issuer' or 'jwksUrl'."
|
||||
)
|
||||
openIdConfig = kc.fetch_idp_endpoints_import_config_url(
|
||||
fromUrl=config["fromUrl"], realm=kc.module.params.get("realm", "master")
|
||||
)
|
||||
for k in endpoints:
|
||||
if k in openIdConfig:
|
||||
config[k] = openIdConfig[k]
|
||||
del config["fromUrl"]
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Module execution
|
||||
|
||||
:return:
|
||||
"""
|
||||
argument_spec = keycloak_argument_spec()
|
||||
|
||||
mapper_spec = dict(
|
||||
id=dict(type="str"),
|
||||
name=dict(type="str"),
|
||||
identityProviderAlias=dict(type="str"),
|
||||
identityProviderMapper=dict(type="str"),
|
||||
config=dict(type="dict"),
|
||||
)
|
||||
|
||||
meta_args = dict(
|
||||
state=dict(type="str", default="present", choices=["present", "absent"]),
|
||||
realm=dict(type="str", default="master"),
|
||||
alias=dict(type="str", required=True),
|
||||
add_read_token_role_on_create=dict(type="bool", aliases=["addReadTokenRoleOnCreate"]),
|
||||
authenticate_by_default=dict(type="bool", aliases=["authenticateByDefault"]),
|
||||
config=dict(type="dict"),
|
||||
display_name=dict(type="str", aliases=["displayName"]),
|
||||
enabled=dict(type="bool"),
|
||||
first_broker_login_flow_alias=dict(type="str", aliases=["firstBrokerLoginFlowAlias"]),
|
||||
link_only=dict(type="bool", aliases=["linkOnly"]),
|
||||
post_broker_login_flow_alias=dict(type="str", aliases=["postBrokerLoginFlowAlias"]),
|
||||
provider_id=dict(type="str", aliases=["providerId"]),
|
||||
store_token=dict(type="bool", aliases=["storeToken"]),
|
||||
trust_email=dict(type="bool", aliases=["trustEmail"]),
|
||||
mappers=dict(type="list", elements="dict", options=mapper_spec),
|
||||
)
|
||||
|
||||
argument_spec.update(meta_args)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
required_one_of=(
|
||||
[["token", "auth_realm", "auth_username", "auth_password", "auth_client_id", "auth_client_secret"]]
|
||||
),
|
||||
required_together=([["auth_username", "auth_password"]]),
|
||||
required_by={"refresh_token": "auth_realm"},
|
||||
)
|
||||
|
||||
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")
|
||||
alias = module.params.get("alias")
|
||||
state = module.params.get("state")
|
||||
config = module.params.get("config")
|
||||
|
||||
fetch_identity_provider_wellknown_config(kc, config)
|
||||
|
||||
# Filter and map the parameters names that apply to the identity provider.
|
||||
idp_params = [
|
||||
x
|
||||
for x in module.params
|
||||
if x not in list(keycloak_argument_spec().keys()) + ["state", "realm", "mappers"]
|
||||
and module.params.get(x) is not None
|
||||
]
|
||||
|
||||
# See if it already exists in Keycloak
|
||||
before_idp = get_identity_provider_with_mappers(kc, alias, realm)
|
||||
|
||||
# Build a proposed changeset from parameters given to this module
|
||||
changeset = {}
|
||||
|
||||
for param in idp_params:
|
||||
new_param_value = module.params.get(param)
|
||||
old_value = before_idp[camel(param)] if camel(param) in before_idp else None
|
||||
if new_param_value != old_value:
|
||||
changeset[camel(param)] = new_param_value
|
||||
|
||||
# special handling of mappers list to allow change detection
|
||||
if module.params.get("mappers") is not None:
|
||||
for change in module.params["mappers"]:
|
||||
change = {k: v for k, v in change.items() if v is not None}
|
||||
if change.get("id") is None and change.get("name") is None:
|
||||
module.fail_json(msg="Either `name` or `id` has to be specified on each mapper.")
|
||||
if before_idp == dict():
|
||||
old_mapper = dict()
|
||||
elif change.get("id") is not None:
|
||||
old_mapper = kc.get_identity_provider_mapper(change["id"], alias, realm)
|
||||
if old_mapper is None:
|
||||
old_mapper = dict()
|
||||
else:
|
||||
found = [x for x in kc.get_identity_provider_mappers(alias, realm) if x["name"] == change["name"]]
|
||||
if len(found) == 1:
|
||||
old_mapper = found[0]
|
||||
else:
|
||||
old_mapper = dict()
|
||||
new_mapper = old_mapper.copy()
|
||||
new_mapper.update(change)
|
||||
|
||||
if changeset.get("mappers") is None:
|
||||
changeset["mappers"] = list()
|
||||
# eventually this holds all desired mappers, unchanged, modified and newly added
|
||||
changeset["mappers"].append(new_mapper)
|
||||
|
||||
# ensure idempotency in case module.params.mappers is not sorted by name
|
||||
changeset["mappers"] = sorted(
|
||||
changeset["mappers"], key=lambda x: x.get("id") if x.get("name") is None else x["name"]
|
||||
)
|
||||
|
||||
# Prepare the desired values using the existing values (non-existence results in a dict that is save to use as a basis)
|
||||
desired_idp = before_idp.copy()
|
||||
desired_idp.update(changeset)
|
||||
|
||||
result["proposed"] = sanitize(changeset)
|
||||
result["existing"] = sanitize(before_idp)
|
||||
|
||||
# Cater for when it doesn't exist (an empty dict)
|
||||
if not before_idp:
|
||||
if state == "absent":
|
||||
# Do nothing and exit
|
||||
if module._diff:
|
||||
result["diff"] = dict(before="", after="")
|
||||
result["changed"] = False
|
||||
result["end_state"] = {}
|
||||
result["msg"] = "Identity provider does not exist; doing nothing."
|
||||
module.exit_json(**result)
|
||||
|
||||
# Process a creation
|
||||
result["changed"] = True
|
||||
|
||||
if module._diff:
|
||||
result["diff"] = dict(before="", after=sanitize(desired_idp))
|
||||
|
||||
if module.check_mode:
|
||||
module.exit_json(**result)
|
||||
|
||||
# create it
|
||||
desired_idp = desired_idp.copy()
|
||||
mappers = desired_idp.pop("mappers", [])
|
||||
kc.create_identity_provider(desired_idp, realm)
|
||||
for mapper in mappers:
|
||||
if mapper.get("identityProviderAlias") is None:
|
||||
mapper["identityProviderAlias"] = alias
|
||||
kc.create_identity_provider_mapper(mapper, alias, realm)
|
||||
after_idp = get_identity_provider_with_mappers(kc, alias, realm)
|
||||
|
||||
result["end_state"] = sanitize(after_idp)
|
||||
|
||||
result["msg"] = f"Identity provider {alias} has been created"
|
||||
module.exit_json(**result)
|
||||
|
||||
else:
|
||||
if state == "present":
|
||||
# Process an update
|
||||
|
||||
# no changes
|
||||
if desired_idp == before_idp:
|
||||
result["changed"] = False
|
||||
result["end_state"] = sanitize(desired_idp)
|
||||
result["msg"] = f"No changes required to identity provider {alias}."
|
||||
module.exit_json(**result)
|
||||
|
||||
# doing an update
|
||||
result["changed"] = True
|
||||
|
||||
if module._diff:
|
||||
result["diff"] = dict(before=sanitize(before_idp), after=sanitize(desired_idp))
|
||||
|
||||
if module.check_mode:
|
||||
module.exit_json(**result)
|
||||
|
||||
# do the update
|
||||
desired_idp = desired_idp.copy()
|
||||
updated_mappers = desired_idp.pop("mappers", [])
|
||||
original_mappers = list(before_idp.get("mappers", []))
|
||||
|
||||
kc.update_identity_provider(desired_idp, realm)
|
||||
for mapper in updated_mappers:
|
||||
if mapper.get("id") is not None:
|
||||
# only update existing if there is a change
|
||||
for i, orig in enumerate(original_mappers):
|
||||
if mapper["id"] == orig["id"]:
|
||||
del original_mappers[i]
|
||||
if mapper != orig:
|
||||
kc.update_identity_provider_mapper(mapper, alias, realm)
|
||||
else:
|
||||
if mapper.get("identityProviderAlias") is None:
|
||||
mapper["identityProviderAlias"] = alias
|
||||
kc.create_identity_provider_mapper(mapper, alias, realm)
|
||||
for mapper in [
|
||||
x for x in before_idp["mappers"] if [y for y in updated_mappers if y["name"] == x["name"]] == []
|
||||
]:
|
||||
kc.delete_identity_provider_mapper(mapper["id"], alias, realm)
|
||||
|
||||
after_idp = get_identity_provider_with_mappers(kc, alias, realm)
|
||||
|
||||
result["end_state"] = sanitize(after_idp)
|
||||
|
||||
result["msg"] = f"Identity provider {alias} has been updated"
|
||||
module.exit_json(**result)
|
||||
|
||||
elif state == "absent":
|
||||
# Process a deletion
|
||||
result["changed"] = True
|
||||
|
||||
if module._diff:
|
||||
result["diff"] = dict(before=sanitize(before_idp), after="")
|
||||
|
||||
if module.check_mode:
|
||||
module.exit_json(**result)
|
||||
|
||||
# delete it
|
||||
kc.delete_identity_provider(alias, realm)
|
||||
|
||||
result["end_state"] = {}
|
||||
|
||||
result["msg"] = f"Identity provider {alias} has been deleted"
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user