def main(): """ Module execution :return: """ argument_spec = keycloak_argument_spec() protmapper_spec = dict( consentRequired=dict(type='bool'), consentText=dict(type='str'), id=dict(type='str'), name=dict(type='str'), protocol=dict(type='str', choices=['openid-connect', 'saml']), protocolMapper=dict(type='str'), config=dict(type='dict'), ) meta_args = dict( state=dict(default='present', choices=['present', 'absent']), realm=dict(type='str', default='master'), id=dict(type='str'), client_id=dict(type='str', aliases=['clientId']), name=dict(type='str'), description=dict(type='str'), root_url=dict(type='str', aliases=['rootUrl']), admin_url=dict(type='str', aliases=['adminUrl']), base_url=dict(type='str', aliases=['baseUrl']), surrogate_auth_required=dict(type='bool', aliases=['surrogateAuthRequired']), enabled=dict(type='bool'), client_authenticator_type=dict(type='str', choices=['client-secret', 'client-jwt'], aliases=['clientAuthenticatorType']), secret=dict(type='str', no_log=True), registration_access_token=dict(type='str', aliases=['registrationAccessToken'], no_log=True), default_roles=dict(type='list', elements='str', aliases=['defaultRoles']), redirect_uris=dict(type='list', elements='str', aliases=['redirectUris']), web_origins=dict(type='list', elements='str', aliases=['webOrigins']), not_before=dict(type='int', aliases=['notBefore']), bearer_only=dict(type='bool', aliases=['bearerOnly']), consent_required=dict(type='bool', aliases=['consentRequired']), standard_flow_enabled=dict(type='bool', aliases=['standardFlowEnabled']), implicit_flow_enabled=dict(type='bool', aliases=['implicitFlowEnabled']), direct_access_grants_enabled=dict( type='bool', aliases=['directAccessGrantsEnabled']), service_accounts_enabled=dict(type='bool', aliases=['serviceAccountsEnabled']), authorization_services_enabled=dict( type='bool', aliases=['authorizationServicesEnabled']), public_client=dict(type='bool', aliases=['publicClient']), frontchannel_logout=dict(type='bool', aliases=['frontchannelLogout']), protocol=dict(type='str', choices=['openid-connect', 'saml']), attributes=dict(type='dict'), full_scope_allowed=dict(type='bool', aliases=['fullScopeAllowed']), node_re_registration_timeout=dict( type='int', aliases=['nodeReRegistrationTimeout']), registered_nodes=dict(type='dict', aliases=['registeredNodes']), client_template=dict(type='str', aliases=['clientTemplate']), use_template_config=dict(type='bool', aliases=['useTemplateConfig']), use_template_scope=dict(type='bool', aliases=['useTemplateScope']), use_template_mappers=dict(type='bool', aliases=['useTemplateMappers']), authentication_flow_binding_overrides=dict( type='dict', aliases=['authenticationFlowBindingOverrides']), protocol_mappers=dict(type='list', elements='dict', options=protmapper_spec, aliases=['protocolMappers']), authorization_settings=dict(type='dict', aliases=['authorizationSettings']), ) argument_spec.update(meta_args) module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True, required_one_of=([['client_id', 'id'], [ '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') cid = module.params.get('id') state = module.params.get('state') # convert module parameters to client representation parameters (if they belong in there) client_params = [ x for x in module.params if x not in list(keycloak_argument_spec().keys()) + ['state', 'realm'] and module.params.get(x) is not None ] keycloak_argument_spec().keys() # See whether the client already exists in Keycloak if cid is None: before_client = kc.get_client_by_clientid( module.params.get('client_id'), realm=realm) if before_client is not None: cid = before_client['id'] else: before_client = kc.get_client_by_id(cid, realm=realm) if before_client is None: before_client = dict() # Build a proposed changeset from parameters given to this module changeset = dict() for client_param in client_params: new_param_value = module.params.get(client_param) # some lists in the Keycloak API are sorted, some are not. if isinstance(new_param_value, list): if client_param in ['attributes']: try: new_param_value = sorted(new_param_value) except TypeError: pass # Unfortunately, the ansible argument spec checker introduces variables with null values when # they are not specified if client_param == 'protocol_mappers': new_param_value = [ dict((k, v) for k, v in x.items() if x[k] is not None) for x in new_param_value ] changeset[camel(client_param)] = new_param_value # Whether creating or updating a client, take the before-state and merge the changeset into it updated_client = before_client.copy() updated_client.update(changeset) result['proposed'] = sanitize_cr(changeset) result['existing'] = sanitize_cr(before_client) # If the client does not exist yet, before_client is still empty if before_client == dict(): if state == 'absent': # do nothing and exit if module._diff: result['diff'] = dict(before='', after='') result['msg'] = 'Client does not exist, doing nothing.' module.exit_json(**result) # create new client result['changed'] = True if 'clientId' not in updated_client: module.fail_json( msg='client_id needs to be specified when creating a new client' ) if module._diff: result['diff'] = dict(before='', after=sanitize_cr(updated_client)) if module.check_mode: module.exit_json(**result) kc.create_client(updated_client, realm=realm) after_client = kc.get_client_by_clientid(updated_client['clientId'], realm=realm) result['end_state'] = sanitize_cr(after_client) result[ 'msg'] = 'Client %s has been created.' % updated_client['clientId'] module.exit_json(**result) else: if state == 'present': # update existing client result['changed'] = True if module.check_mode: # We can only compare the current client with the proposed updates we have if module._diff: result['diff'] = dict(before=sanitize_cr(before_client), after=sanitize_cr(updated_client)) result['changed'] = (before_client != updated_client) module.exit_json(**result) kc.update_client(cid, updated_client, realm=realm) after_client = kc.get_client_by_id(cid, realm=realm) if before_client == after_client: result['changed'] = False if module._diff: result['diff'] = dict(before=sanitize_cr(before_client), after=sanitize_cr(after_client)) result['end_state'] = sanitize_cr(after_client) result['msg'] = 'Client %s has been updated.' % updated_client[ 'clientId'] module.exit_json(**result) else: # Delete existing client result['changed'] = True if module._diff: result['diff']['before'] = sanitize_cr(before_client) result['diff']['after'] = '' if module.check_mode: module.exit_json(**result) kc.delete_client(cid, realm=realm) result['proposed'] = dict() result['end_state'] = dict() result['msg'] = 'Client %s has been deleted.' % before_client[ 'clientId'] module.exit_json(**result) module.exit_json(**result)
def main(): """ Module execution :return: """ argument_spec = keycloak_argument_spec() protmapper_spec = dict( consentRequired=dict(type='bool'), consentText=dict(type='str'), id=dict(type='str'), name=dict(type='str'), protocol=dict(type='str', choices=['openid-connect', 'saml']), protocolMapper=dict(type='str'), config=dict(type='dict'), ) meta_args = dict( realm=dict(type='str', default='master'), state=dict(default='present', choices=['present', 'absent']), id=dict(type='str'), name=dict(type='str'), description=dict(type='str'), protocol=dict(type='str', choices=['openid-connect', 'saml']), attributes=dict(type='dict'), full_scope_allowed=dict(type='bool'), protocol_mappers=dict(type='list', elements='dict', options=protmapper_spec), ) argument_spec.update(meta_args) module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_one_of=([['id', 'name']])) result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={}) # Obtain access token, initialize API try: connection_header = get_token( base_url=module.params.get('auth_keycloak_url'), validate_certs=module.params.get('validate_certs'), auth_realm=module.params.get('auth_realm'), client_id=module.params.get('auth_client_id'), auth_username=module.params.get('auth_username'), auth_password=module.params.get('auth_password'), client_secret=module.params.get('auth_client_secret'), ) 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') cid = module.params.get('id') # convert module parameters to client representation parameters (if they belong in there) clientt_params = [ x for x in module.params if x not in [ 'state', 'auth_keycloak_url', 'auth_client_id', 'auth_realm', 'auth_client_secret', 'auth_username', 'auth_password', 'validate_certs', 'realm' ] and module.params.get(x) is not None ] # See whether the client template already exists in Keycloak if cid is None: before_clientt = kc.get_client_template_by_name( module.params.get('name'), realm=realm) if before_clientt is not None: cid = before_clientt['id'] else: before_clientt = kc.get_client_template_by_id(cid, realm=realm) if before_clientt is None: before_clientt = dict() result['existing'] = before_clientt # Build a proposed changeset from parameters given to this module changeset = dict() for clientt_param in clientt_params: # lists in the Keycloak API are sorted new_param_value = module.params.get(clientt_param) if isinstance(new_param_value, list): try: new_param_value = sorted(new_param_value) except TypeError: pass changeset[camel(clientt_param)] = new_param_value # Whether creating or updating a client, take the before-state and merge the changeset into it updated_clientt = before_clientt.copy() updated_clientt.update(changeset) result['proposed'] = changeset # If the client template does not exist yet, before_client is still empty if before_clientt == dict(): if state == 'absent': # do nothing and exit if module._diff: result['diff'] = dict(before='', after='') result['msg'] = 'Client template does not exist, doing nothing.' module.exit_json(**result) # create new client template result['changed'] = True if 'name' not in updated_clientt: module.fail_json( msg='name needs to be specified when creating a new client') if module._diff: result['diff'] = dict(before='', after=updated_clientt) if module.check_mode: module.exit_json(**result) kc.create_client_template(updated_clientt, realm=realm) after_clientt = kc.get_client_template_by_name(updated_clientt['name'], realm=realm) result['end_state'] = after_clientt result[ 'msg'] = 'Client template %s has been created.' % updated_clientt[ 'name'] module.exit_json(**result) else: if state == 'present': # update existing client template result['changed'] = True if module.check_mode: # We can only compare the current client template with the proposed updates we have if module._diff: result['diff'] = dict(before=before_clientt, after=updated_clientt) module.exit_json(**result) kc.update_client_template(cid, updated_clientt, realm=realm) after_clientt = kc.get_client_template_by_id(cid, realm=realm) if before_clientt == after_clientt: result['changed'] = False if module._diff: result['diff'] = dict(before=before_clientt, after=after_clientt) result['end_state'] = after_clientt result[ 'msg'] = 'Client template %s has been updated.' % updated_clientt[ 'name'] module.exit_json(**result) else: # Delete existing client result['changed'] = True if module._diff: result['diff']['before'] = before_clientt result['diff']['after'] = '' if module.check_mode: module.exit_json(**result) kc.delete_client_template(cid, realm=realm) result['proposed'] = dict() result['end_state'] = dict() result[ 'msg'] = 'Client template %s has been deleted.' % before_clientt[ 'name'] module.exit_json(**result) module.exit_json(**result)
def main(): """ Module execution :return: """ argument_spec = keycloak_argument_spec() roles_spec = dict( name=dict(type='str'), id=dict(type='str'), ) meta_args = dict( state=dict(default='present', choices=['present', 'absent']), realm=dict(default='master'), gid=dict(type='str'), group_name=dict(type='str'), cid=dict(type='str'), client_id=dict(type='str'), roles=dict(type='list', elements='dict', options=roles_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' ]]), 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') cid = module.params.get('cid') client_id = module.params.get('client_id') gid = module.params.get('gid') group_name = module.params.get('group_name') roles = module.params.get('roles') # Check the parameters if cid is None and client_id is None: module.fail_json( msg='Either the `client_id` or `cid` has to be specified.') if gid is None and group_name is None: module.fail_json( msg='Either the `group_name` or `gid` has to be specified.') # Get the potential missing parameters if gid is None: group_rep = kc.get_group_by_name(group_name, realm=realm) if group_rep is not None: gid = group_rep['id'] else: module.fail_json(msg='Could not fetch group %s:' % group_name) if cid is None: cid = kc.get_client_id(client_id, realm=realm) if cid is None: module.fail_json(msg='Could not fetch client %s:' % client_id) if roles is None: module.exit_json(msg="Nothing to do (no roles specified).") else: for role_index, role in enumerate(roles, start=0): if role['name'] is None and role['id'] is None: module.fail_json( msg= 'Either the `name` or `id` has to be specified on each role.' ) # Fetch missing role_id if role['id'] is None: role_id = kc.get_client_role_by_name(gid, cid, role['name'], realm=realm) if role_id is not None: role['id'] = role_id else: module.fail_json(msg='Could not fetch role %s:' % (role['name'])) # Fetch missing role_name else: role['name'] = kc.get_client_rolemapping_by_id( gid, cid, role['id'], realm=realm)['name'] if role['name'] is None: module.fail_json(msg='Could not fetch role %s' % (role['id'])) # Get effective client-level role mappings available_roles_before = kc.get_client_available_rolemappings(gid, cid, realm=realm) assigned_roles_before = kc.get_client_composite_rolemappings(gid, cid, realm=realm) result['existing'] = assigned_roles_before result['proposed'] = roles update_roles = [] for role_index, role in enumerate(roles, start=0): # Fetch roles to assign if state present if state == 'present': for available_role in available_roles_before: if role['name'] == available_role['name']: update_roles.append({ 'id': role['id'], 'name': role['name'], }) # Fetch roles to remove if state absent else: for assigned_role in assigned_roles_before: if role['name'] == assigned_role['name']: update_roles.append({ 'id': role['id'], 'name': role['name'], }) if len(update_roles): if state == 'present': # Assign roles result['changed'] = True if module._diff: result['diff'] = dict(before=assigned_roles_before, after=update_roles) if module.check_mode: module.exit_json(**result) kc.add_group_rolemapping(gid, cid, update_roles, realm=realm) result['msg'] = 'Roles %s assigned to group %s.' % (update_roles, group_name) assigned_roles_after = kc.get_client_composite_rolemappings( gid, cid, realm=realm) result['end_state'] = assigned_roles_after module.exit_json(**result) else: # Remove mapping of role result['changed'] = True if module._diff: result['diff'] = dict(before=assigned_roles_before, after=update_roles) if module.check_mode: module.exit_json(**result) kc.delete_group_rolemapping(gid, cid, update_roles, realm=realm) result['msg'] = 'Roles %s removed from group %s.' % (update_roles, group_name) assigned_roles_after = kc.get_client_composite_rolemappings( gid, cid, realm=realm) result['end_state'] = assigned_roles_after module.exit_json(**result) # Do nothing else: result['changed'] = False result[ 'msg'] = 'Nothing to do, roles %s are correctly mapped with group %s.' % ( roles, group_name) module.exit_json(**result)
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']), default_default_client_scopes=dict( type='list', elements='dict', aliases=['defaultDefaultClientScopes']), default_groups=dict(type='list', elements='dict', aliases=['defaultGroups']), default_locale=dict(type='str', aliases=['defaultLocale']), default_optional_client_scopes=dict( type='list', elements='dict', aliases=['defaultOptionalClientScopes']), default_roles=dict(type='list', elements='dict', aliases=['defaultRoles']), 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']), events_enabled=dict(type='bool', aliases=['eventsEnabled']), events_expiration=dict(type='int', aliases=['eventsExpiration']), events_listeners=dict(type='list', elements='str', aliases=['eventsListeners']), 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']), reset_password_allowed=dict(type='bool', aliases=['resetPasswordAllowed'], no_log=False), revoke_refresh_token=dict(type='bool', aliases=['revokeRefreshToken']), smtp_server=dict(type='dict', aliases=['smtpServer']), ssl_required=dict(choices=["external", "all", "none"], aliases=['sslRequired']), 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']), ) 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'] 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 before_realm = kc.get_realm_by_id(realm=realm) or {} # Build a proposed changeset from parameters given to this module changeset = dict() for realm_param in realm_params: new_param_value = module.params.get(realm_param) changeset[camel(realm_param)] = new_param_value # Whether creating or updating a realm, take the before-state and merge the changeset into it updated_realm = before_realm.copy() updated_realm.update(changeset) result['proposed'] = sanitize_cr(changeset) before_realm_sanitized = sanitize_cr(before_realm) result['existing'] = before_realm_sanitized # If the realm does not exist yet, before_realm is still empty if not before_realm: if state == 'absent': # do nothing and exit if module._diff: result['diff'] = dict(before='', after='') result['msg'] = 'Realm does not exist, doing nothing.' module.exit_json(**result) # create new realm result['changed'] = True if 'id' not in updated_realm: module.fail_json( msg='id needs to be specified when creating a new realm') if module._diff: result['diff'] = dict(before='', after=sanitize_cr(updated_realm)) if module.check_mode: module.exit_json(**result) kc.create_realm(updated_realm) after_realm = kc.get_realm_by_id(updated_realm['id']) result['end_state'] = sanitize_cr(after_realm) result['msg'] = 'Realm %s has been created.' % updated_realm['id'] module.exit_json(**result) else: if state == 'present': # update existing realm result['changed'] = True if module.check_mode: # We can only compare the current realm with the proposed updates we have if module._diff: result['diff'] = dict(before=before_realm_sanitized, after=sanitize_cr(updated_realm)) result['changed'] = (before_realm != updated_realm) module.exit_json(**result) kc.update_realm(updated_realm, realm=realm) after_realm = kc.get_realm_by_id(realm=realm) if before_realm == after_realm: result['changed'] = False if module._diff: result['diff'] = dict(before=before_realm_sanitized, after=sanitize_cr(after_realm)) result['end_state'] = sanitize_cr(after_realm) result['msg'] = 'Realm %s has been updated.' % updated_realm['id'] module.exit_json(**result) else: # Delete existing realm result['changed'] = True if module._diff: result['diff']['before'] = before_realm_sanitized result['diff']['after'] = '' if module.check_mode: module.exit_json(**result) kc.delete_realm(realm=realm) result['proposed'] = dict() result['end_state'] = dict() result['msg'] = 'Realm %s has been deleted.' % before_realm['id'] module.exit_json(**result) module.exit_json(**result)
def main(): """ Module execution :return: """ argument_spec = keycloak_argument_spec() config_spec = dict( allowKerberosAuthentication=dict(type='bool', default=False), allowPasswordAuthentication=dict(type='bool'), authType=dict(type='str', choices=['none', 'simple'], default='none'), batchSizeForSync=dict(type='int', default=1000), bindCredential=dict(type='str', no_log=True), bindDn=dict(type='str'), cachePolicy=dict(type='str', choices=['DEFAULT', 'EVICT_DAILY', 'EVICT_WEEKLY', 'MAX_LIFESPAN', 'NO_CACHE'], default='DEFAULT'), changedSyncPeriod=dict(type='int', default=-1), connectionPooling=dict(type='bool', default=True), connectionPoolingAuthentication=dict(type='str', choices=['none', 'simple', 'DIGEST-MD5']), connectionPoolingDebug=dict(type='str'), connectionPoolingInitSize=dict(type='int'), connectionPoolingMaxSize=dict(type='int'), connectionPoolingPrefSize=dict(type='int'), connectionPoolingProtocol=dict(type='str'), connectionPoolingTimeout=dict(type='int'), connectionTimeout=dict(type='int'), connectionUrl=dict(type='str'), customUserSearchFilter=dict(type='str'), debug=dict(type='bool'), editMode=dict(type='str', choices=['READ_ONLY', 'WRITABLE', 'UNSYNCED']), enabled=dict(type='bool', default=True), evictionDay=dict(type='str'), evictionHour=dict(type='str'), evictionMinute=dict(type='str'), fullSyncPeriod=dict(type='int', default=-1), importEnabled=dict(type='bool', default=True), kerberosRealm=dict(type='str'), keyTab=dict(type='str', no_log=False), maxLifespan=dict(type='int'), pagination=dict(type='bool', default=True), priority=dict(type='int', default=0), rdnLDAPAttribute=dict(type='str'), readTimeout=dict(type='int'), searchScope=dict(type='str', choices=['1', '2'], default='1'), serverPrincipal=dict(type='str'), startTls=dict(type='bool', default=False), syncRegistrations=dict(type='bool', default=False), trustEmail=dict(type='bool', default=False), updateProfileFirstLogin=dict(type='bool'), useKerberosForPasswordAuthentication=dict(type='bool', default=False), usePasswordModifyExtendedOp=dict(type='bool', default=False, no_log=False), useTruststoreSpi=dict(type='str', choices=['always', 'ldapsOnly', 'never'], default='ldapsOnly'), userObjectClasses=dict(type='str'), usernameLDAPAttribute=dict(type='str'), usersDn=dict(type='str'), uuidLDAPAttribute=dict(type='str'), validatePasswordPolicy=dict(type='bool', default=False), vendor=dict(type='str'), ) mapper_spec = dict( id=dict(type='str'), name=dict(type='str'), parentId=dict(type='str'), providerId=dict(type='str'), providerType=dict(type='str'), config=dict(type='dict'), ) meta_args = dict( config=dict(type='dict', options=config_spec), state=dict(type='str', default='present', choices=['present', 'absent']), realm=dict(type='str', default='master'), id=dict(type='str'), name=dict(type='str'), provider_id=dict(type='str', aliases=['providerId'], choices=['ldap', 'kerberos']), provider_type=dict(type='str', aliases=['providerType'], default='org.keycloak.storage.UserStorageProvider'), parent_id=dict(type='str', aliases=['parentId']), 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=([['id', 'name'], ['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') config = module.params.get('config') mappers = module.params.get('mappers') cid = module.params.get('id') name = module.params.get('name') # Keycloak API expects config parameters to be arrays containing a single string element if config is not None: module.params['config'] = dict((k, [str(v).lower() if not isinstance(v, str) else v]) for k, v in config.items() if config[k] is not None) if mappers is not None: for mapper in mappers: if mapper.get('config') is not None: mapper['config'] = dict((k, [str(v).lower() if not isinstance(v, str) else v]) for k, v in mapper['config'].items() if mapper['config'][k] is not None) # Filter and map the parameters names that apply comp_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 if cid is None: found = kc.get_components(urlencode(dict(type='org.keycloak.storage.UserStorageProvider', parent=realm, name=name)), realm) if len(found) > 1: module.fail_json(msg='No ID given and found multiple user federations with name `{name}`. Cannot continue.'.format(name=name)) before_comp = next(iter(found), None) if before_comp is not None: cid = before_comp['id'] else: before_comp = kc.get_component(cid, realm) if before_comp is None: before_comp = {} # if user federation exists, get associated mappers if cid is not None: before_comp['mappers'] = sorted(kc.get_components(urlencode(dict(parent=cid)), realm), key=lambda x: x.get('name')) # Build a proposed changeset from parameters given to this module changeset = {} for param in comp_params: new_param_value = module.params.get(param) old_value = before_comp[camel(param)] if camel(param) in before_comp else None if param == 'mappers': new_param_value = [dict((k, v) for k, v in x.items() if x[k] is not None) for x in new_param_value] 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: if module.params['provider_id'] == 'kerberos': module.fail_json(msg='Cannot configure mappers for Kerberos federations.') for change in module.params['mappers']: change = dict((k, v) for k, v in change.items() if change[k] 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 cid is None: old_mapper = {} elif change.get('id') is not None: old_mapper = kc.get_component(change['id'], realm) if old_mapper is None: old_mapper = {} else: found = kc.get_components(urlencode(dict(parent=cid, name=change['name'])), realm) if len(found) > 1: module.fail_json(msg='Found multiple mappers with name `{name}`. Cannot continue.'.format(name=change['name'])) if len(found) == 1: old_mapper = found[0] else: old_mapper = {} new_mapper = old_mapper.copy() new_mapper.update(change) if new_mapper != old_mapper: if changeset.get('mappers') is None: changeset['mappers'] = list() changeset['mappers'].append(new_mapper) # Prepare the desired values using the existing values (non-existence results in a dict that is save to use as a basis) desired_comp = before_comp.copy() desired_comp.update(changeset) result['proposed'] = sanitize(changeset) result['existing'] = sanitize(before_comp) # Cater for when it doesn't exist (an empty dict) if not before_comp: if state == 'absent': # Do nothing and exit if module._diff: result['diff'] = dict(before='', after='') result['changed'] = False result['end_state'] = {} result['msg'] = 'User federation 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_comp)) if module.check_mode: module.exit_json(**result) # create it desired_comp = desired_comp.copy() updated_mappers = desired_comp.pop('mappers', []) after_comp = kc.create_component(desired_comp, realm) for mapper in updated_mappers: if mapper.get('id') is not None: kc.update_component(mapper, realm) else: if mapper.get('parentId') is None: mapper['parentId'] = after_comp['id'] mapper = kc.create_component(mapper, realm) after_comp['mappers'] = updated_mappers result['end_state'] = sanitize(after_comp) result['msg'] = "User federation {id} has been created".format(id=after_comp['id']) module.exit_json(**result) else: if state == 'present': # Process an update # no changes if desired_comp == before_comp: result['changed'] = False result['end_state'] = sanitize(desired_comp) result['msg'] = "No changes required to user federation {id}.".format(id=cid) module.exit_json(**result) # doing an update result['changed'] = True if module._diff: result['diff'] = dict(before=sanitize(before_comp), after=sanitize(desired_comp)) if module.check_mode: module.exit_json(**result) # do the update desired_comp = desired_comp.copy() updated_mappers = desired_comp.pop('mappers', []) kc.update_component(desired_comp, realm) after_comp = kc.get_component(cid, realm) for mapper in updated_mappers: if mapper.get('id') is not None: kc.update_component(mapper, realm) else: if mapper.get('parentId') is None: mapper['parentId'] = desired_comp['id'] mapper = kc.create_component(mapper, realm) after_comp['mappers'] = updated_mappers result['end_state'] = sanitize(after_comp) result['msg'] = "User federation {id} has been updated".format(id=cid) module.exit_json(**result) elif state == 'absent': # Process a deletion result['changed'] = True if module._diff: result['diff'] = dict(before=sanitize(before_comp), after='') if module.check_mode: module.exit_json(**result) # delete it kc.delete_component(cid, realm) result['end_state'] = {} result['msg'] = "User federation {id} has been deleted".format(id=cid) module.exit_json(**result)
def main(): """ Module execution :return: """ argument_spec = keycloak_argument_spec() protmapper_spec = dict( id=dict(type='str'), name=dict(type='str'), protocol=dict(type='str', choices=['openid-connect', 'saml', 'wsfed']), protocolMapper=dict(type='str'), config=dict(type='dict'), ) meta_args = dict( state=dict(default='present', choices=['present', 'absent']), realm=dict(default='master'), id=dict(type='str'), name=dict(type='str'), description=dict(type='str'), protocol=dict(type='str', choices=['openid-connect', 'saml', 'wsfed']), attributes=dict(type='dict'), protocol_mappers=dict(type='list', elements='dict', options=protmapper_spec, aliases=['protocolMappers']), ) argument_spec.update(meta_args) module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True, required_one_of=([['id', 'name'], [ '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') cid = module.params.get('id') name = module.params.get('name') protocol_mappers = module.params.get('protocol_mappers') # Filter and map the parameters names that apply to the client scope clientscope_params = [ x for x in module.params if x not in list(keycloak_argument_spec().keys()) + ['state', 'realm'] and module.params.get(x) is not None ] # See if it already exists in Keycloak if cid is None: before_clientscope = kc.get_clientscope_by_name(name, realm=realm) else: before_clientscope = kc.get_clientscope_by_clientscopeid(cid, realm=realm) if before_clientscope is None: before_clientscope = {} # Build a proposed changeset from parameters given to this module changeset = {} for clientscope_param in clientscope_params: new_param_value = module.params.get(clientscope_param) # some lists in the Keycloak API are sorted, some are not. if isinstance(new_param_value, list): if clientscope_param in ['attributes']: try: new_param_value = sorted(new_param_value) except TypeError: pass # Unfortunately, the ansible argument spec checker introduces variables with null values when # they are not specified if clientscope_param == 'protocol_mappers': new_param_value = [ dict((k, v) for k, v in x.items() if x[k] is not None) for x in new_param_value ] changeset[camel(clientscope_param)] = new_param_value # Prepare the desired values using the existing values (non-existence results in a dict that is save to use as a basis) desired_clientscope = before_clientscope.copy() desired_clientscope.update(changeset) # Cater for when it doesn't exist (an empty dict) if not before_clientscope: if state == 'absent': # Do nothing and exit if module._diff: result['diff'] = dict(before='', after='') result['changed'] = False result['end_state'] = {} result['msg'] = 'Clientscope does not exist; doing nothing.' module.exit_json(**result) # Process a creation result['changed'] = True if name is None: module.fail_json( msg='name must be specified when creating a new clientscope') if module._diff: result['diff'] = dict(before='', after=sanitize_cr(desired_clientscope)) if module.check_mode: module.exit_json(**result) # create it kc.create_clientscope(desired_clientscope, realm=realm) after_clientscope = kc.get_clientscope_by_name(name, realm) result['end_state'] = sanitize_cr(after_clientscope) result[ 'msg'] = 'Clientscope {name} has been created with ID {id}'.format( name=after_clientscope['name'], id=after_clientscope['id']) else: if state == 'present': # Process an update # no changes if desired_clientscope == before_clientscope: result['changed'] = False result['end_state'] = sanitize_cr(desired_clientscope) result[ 'msg'] = "No changes required to clientscope {name}.".format( name=before_clientscope['name']) module.exit_json(**result) # doing an update result['changed'] = True if module._diff: result['diff'] = dict(before=sanitize_cr(before_clientscope), after=sanitize_cr(desired_clientscope)) if module.check_mode: module.exit_json(**result) # do the update kc.update_clientscope(desired_clientscope, realm=realm) # do the protocolmappers update if protocol_mappers is not None: for protocol_mapper in protocol_mappers: # update if protocolmapper exist current_protocolmapper = kc.get_clientscope_protocolmapper_by_name( desired_clientscope['id'], protocol_mapper['name'], realm=realm) if current_protocolmapper is not None: protocol_mapper['id'] = current_protocolmapper['id'] kc.update_clientscope_protocolmappers( desired_clientscope['id'], protocol_mapper, realm=realm) # create otherwise else: kc.create_clientscope_protocolmapper( desired_clientscope['id'], protocol_mapper, realm=realm) after_clientscope = kc.get_clientscope_by_clientscopeid( desired_clientscope['id'], realm=realm) result['end_state'] = after_clientscope result['msg'] = "Clientscope {id} has been updated".format( id=after_clientscope['id']) module.exit_json(**result) else: # Process a deletion (because state was not 'present') result['changed'] = True if module._diff: result['diff'] = dict(before=sanitize_cr(before_clientscope), after='') if module.check_mode: module.exit_json(**result) # delete it cid = before_clientscope['id'] kc.delete_clientscope(cid=cid, realm=realm) result['end_state'] = {} result['msg'] = "Clientscope {name} has been deleted".format( name=before_clientscope['name']) module.exit_json(**result)
def main(): """ Module execution :return: """ argument_spec = keycloak_argument_spec() meta_args = dict( state=dict(default='present', choices=['present', 'absent']), realm=dict(default='master'), id=dict(type='str'), name=dict(type='str'), attributes=dict(type='dict') ) argument_spec.update(meta_args) module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_one_of=([['id', 'name']])) result = dict(changed=False, msg='', diff={}, group='') # Obtain access token, initialize API try: connection_header = get_token( base_url=module.params.get('auth_keycloak_url'), validate_certs=module.params.get('validate_certs'), auth_realm=module.params.get('auth_realm'), client_id=module.params.get('auth_client_id'), auth_username=module.params.get('auth_username'), auth_password=module.params.get('auth_password'), client_secret=module.params.get('auth_client_secret'), ) 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') gid = module.params.get('id') name = module.params.get('name') attributes = module.params.get('attributes') before_group = None # current state of the group, for merging. # does the group already exist? if gid is None: before_group = kc.get_group_by_name(name, realm=realm) else: before_group = kc.get_group_by_groupid(gid, realm=realm) before_group = {} if before_group is None else before_group # attributes in Keycloak have their values returned as lists # via the API. attributes is a dict, so we'll transparently convert # the values to lists. if attributes is not None: for key, val in module.params['attributes'].items(): module.params['attributes'][key] = [val] if not isinstance(val, list) else val group_params = [x for x in module.params if x not in list(keycloak_argument_spec().keys()) + ['state', 'realm'] and module.params.get(x) is not None] # build a changeset changeset = {} for param in group_params: new_param_value = module.params.get(param) old_value = before_group[param] if param in before_group else None if new_param_value != old_value: changeset[camel(param)] = new_param_value # prepare the new group updated_group = before_group.copy() updated_group.update(changeset) # if before_group is none, the group doesn't exist. if before_group == {}: if state == 'absent': # nothing to do. if module._diff: result['diff'] = dict(before='', after='') result['msg'] = 'Group does not exist; doing nothing.' result['group'] = dict() module.exit_json(**result) # for 'present', create a new group. result['changed'] = True if name is None: module.fail_json(msg='name must be specified when creating a new group') if module._diff: result['diff'] = dict(before='', after=updated_group) if module.check_mode: module.exit_json(**result) # do it for real! kc.create_group(updated_group, realm=realm) after_group = kc.get_group_by_name(name, realm) result['group'] = after_group result['msg'] = 'Group {name} has been created with ID {id}'.format(name=after_group['name'], id=after_group['id']) else: if state == 'present': # no changes if updated_group == before_group: result['changed'] = False result['group'] = updated_group result['msg'] = "No changes required to group {name}.".format(name=before_group['name']) module.exit_json(**result) # update the existing group result['changed'] = True if module._diff: result['diff'] = dict(before=before_group, after=updated_group) if module.check_mode: module.exit_json(**result) # do the update kc.update_group(updated_group, realm=realm) after_group = kc.get_group_by_groupid(updated_group['id'], realm=realm) result['group'] = after_group result['msg'] = "Group {id} has been updated".format(id=after_group['id']) module.exit_json(**result) elif state == 'absent': result['group'] = dict() if module._diff: result['diff'] = dict(before=before_group, after='') if module.check_mode: module.exit_json(**result) # delete for real gid = before_group['id'] kc.delete_group(groupid=gid, realm=realm) result['changed'] = True result['msg'] = "Group {name} has been deleted".format(name=before_group['name']) module.exit_json(**result) module.exit_json(**result)
def main(): """ Module execution :return: """ argument_spec = keycloak_argument_spec() meta_args = dict( realm=dict(type='str', required=True), alias=dict(type='str', required=True), providerId=dict(type='str'), description=dict(type='str'), copyFrom=dict(type='str'), authenticationExecutions=dict( type='list', elements='dict', options=dict( providerId=dict(type='str'), displayName=dict(type='str'), requirement=dict(choices=[ "REQUIRED", "ALTERNATIVE", "DISABLED", "CONDITIONAL" ], type='str'), flowAlias=dict(type='str'), authenticationConfig=dict(type='dict'), index=dict(type='int'), )), state=dict(choices=["absent", "present"], default='present'), force=dict(type='bool', default=False), ) 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' ]]), required_together=([['auth_realm', 'auth_username', 'auth_password']])) result = dict(changed=False, msg='', flow={}) # 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') force = module.params.get('force') new_auth_repr = { "alias": module.params.get("alias"), "copyFrom": module.params.get("copyFrom"), "providerId": module.params.get("providerId"), "authenticationExecutions": module.params.get("authenticationExecutions"), "description": module.params.get("description"), "builtIn": module.params.get("builtIn"), "subflow": module.params.get("subflow"), } auth_repr = kc.get_authentication_flow_by_alias( alias=new_auth_repr["alias"], realm=realm) if auth_repr == {}: # Authentication flow does not exist if state == 'present': # If desired state is present result['changed'] = True if module._diff: result['diff'] = dict(before='', after=new_auth_repr) if module.check_mode: module.exit_json(**result) # If copyFrom is defined, create authentication flow from a copy if "copyFrom" in new_auth_repr and new_auth_repr[ "copyFrom"] is not None: auth_repr = kc.copy_auth_flow(config=new_auth_repr, realm=realm) else: # Create an empty authentication flow auth_repr = kc.create_empty_auth_flow(config=new_auth_repr, realm=realm) # If the authentication still not exist on the server, raise an exception. if auth_repr is None: result[ 'msg'] = "Authentication just created not found: " + str( new_auth_repr) module.fail_json(**result) # Configure the executions for the flow create_or_update_executions(kc=kc, config=new_auth_repr, realm=realm) # Get executions created exec_repr = kc.get_executions_representation(config=new_auth_repr, realm=realm) if exec_repr is not None: auth_repr["authenticationExecutions"] = exec_repr result['flow'] = auth_repr elif state == 'absent': # If desired state is absent. if module._diff: result['diff'] = dict(before='', after='') result['msg'] = new_auth_repr["alias"] + ' absent' else: # The authentication flow already exist if state == 'present': # if desired state is present if force: # If force option is true # Delete the actual authentication flow result['changed'] = True if module._diff: result['diff'] = dict(before=auth_repr, after=new_auth_repr) if module.check_mode: module.exit_json(**result) kc.delete_authentication_flow_by_id(id=auth_repr["id"], realm=realm) # If copyFrom is defined, create authentication flow from a copy if "copyFrom" in new_auth_repr and new_auth_repr[ "copyFrom"] is not None: auth_repr = kc.copy_auth_flow(config=new_auth_repr, realm=realm) else: # Create an empty authentication flow auth_repr = kc.create_empty_auth_flow(config=new_auth_repr, realm=realm) # If the authentication still not exist on the server, raise an exception. if auth_repr is None: result[ 'msg'] = "Authentication just created not found: " + str( new_auth_repr) module.fail_json(**result) # Configure the executions for the flow if module.check_mode: module.exit_json(**result) changed, diff = create_or_update_executions(kc=kc, config=new_auth_repr, realm=realm) result['changed'] |= changed if module._diff: result['diff'] = diff # Get executions created exec_repr = kc.get_executions_representation(config=new_auth_repr, realm=realm) if exec_repr is not None: auth_repr["authenticationExecutions"] = exec_repr result['flow'] = auth_repr elif state == 'absent': # If desired state is absent result['changed'] = True # Delete the authentication flow alias. if module._diff: result['diff'] = dict(before=auth_repr, after='') if module.check_mode: module.exit_json(**result) kc.delete_authentication_flow_by_id(id=auth_repr["id"], realm=realm) result[ 'msg'] = 'Authentication flow: {alias} id: {id} is deleted'.format( alias=new_auth_repr['alias'], id=auth_repr["id"]) module.exit_json(**result)