mirror of
https://github.com/freeipa/ansible-freeipa.git
synced 2026-03-26 21:33:05 +00:00
ipauser: Support for External IdP attributes.
Add support for 'idp' and 'idp_user_id' to ipauser plugin. FreeIPA 4.10.0 is required for both attributes.
This commit is contained in:
@@ -445,6 +445,8 @@ Variable | Description | Required
|
|||||||
`employeenumber` | Employee Number | no
|
`employeenumber` | Employee Number | no
|
||||||
`employeetype` | Employee Type | no
|
`employeetype` | Employee Type | no
|
||||||
`preferredlanguage` | Preferred Language | no
|
`preferredlanguage` | Preferred Language | no
|
||||||
|
`idp` \| `ipaidpconfiglink` | External IdP configuration | no
|
||||||
|
`idp_user_id` \| `ipaidpsub` | A string that identifies the user at external IdP | no
|
||||||
`certificate` | List of base-64 encoded user certificates. | no
|
`certificate` | List of base-64 encoded user certificates. | no
|
||||||
`certmapdata` | List of certificate mappings. Either `data` or `certificate` or `issuer` together with `subject` need to be specified. Only usable with IPA versions 4.5 and up. <br>Options: | no
|
`certmapdata` | List of certificate mappings. Either `data` or `certificate` or `issuer` together with `subject` need to be specified. Only usable with IPA versions 4.5 and up. <br>Options: | no
|
||||||
| `certificate` - Base-64 encoded user certificate, not usable with other certmapdata options. | no
|
| `certificate` - Base-64 encoded user certificate, not usable with other certmapdata options. | no
|
||||||
|
|||||||
12
playbooks/user/add-user-external-idp.yml
Normal file
12
playbooks/user/add-user-external-idp.yml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
- name: Playbook to handle users
|
||||||
|
hosts: ipaserver
|
||||||
|
become: true
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Create user associated with an external IdP
|
||||||
|
ipauser:
|
||||||
|
ipaadmin_password: SomeADMINpassword
|
||||||
|
name: idpuser
|
||||||
|
idp: keycloak
|
||||||
|
idp_user_id: idpuser@exemple.com
|
||||||
@@ -271,6 +271,16 @@ options:
|
|||||||
description: Preferred Language
|
description: Preferred Language
|
||||||
type: str
|
type: str
|
||||||
required: false
|
required: false
|
||||||
|
idp:
|
||||||
|
description: External IdP configuration
|
||||||
|
type: str
|
||||||
|
required: false
|
||||||
|
aliases: ["ipaidpconfiglink"]
|
||||||
|
idp_user_id:
|
||||||
|
description: A string that identifies the user at external IdP
|
||||||
|
type: str
|
||||||
|
required: false
|
||||||
|
aliases: ["ipaidpsub"]
|
||||||
certificate:
|
certificate:
|
||||||
description: List of base-64 encoded user certificates
|
description: List of base-64 encoded user certificates
|
||||||
type: list
|
type: list
|
||||||
@@ -528,6 +538,16 @@ options:
|
|||||||
description: Preferred Language
|
description: Preferred Language
|
||||||
type: str
|
type: str
|
||||||
required: false
|
required: false
|
||||||
|
idp:
|
||||||
|
description: External IdP configuration
|
||||||
|
type: str
|
||||||
|
required: false
|
||||||
|
aliases: ["ipaidpconfiglink"]
|
||||||
|
idp_user_id:
|
||||||
|
description: A string that identifies the user at external IdP
|
||||||
|
type: str
|
||||||
|
required: false
|
||||||
|
aliases: ["ipaidpsub"]
|
||||||
certificate:
|
certificate:
|
||||||
description: List of base-64 encoded user certificates
|
description: List of base-64 encoded user certificates
|
||||||
type: list
|
type: list
|
||||||
@@ -735,8 +755,8 @@ def gen_args(first, last, fullname, displayname, initials, homedir, gecos,
|
|||||||
mobile, pager, fax, orgunit, title, carlicense, sshpubkey,
|
mobile, pager, fax, orgunit, title, carlicense, sshpubkey,
|
||||||
userauthtype, userclass, radius, radiususer, departmentnumber,
|
userauthtype, userclass, radius, radiususer, departmentnumber,
|
||||||
employeenumber, employeetype, preferredlanguage, smb_logon_script,
|
employeenumber, employeetype, preferredlanguage, smb_logon_script,
|
||||||
smb_profile_path, smb_home_dir, smb_home_drive, noprivate,
|
smb_profile_path, smb_home_dir, smb_home_drive, idp, idp_user_id,
|
||||||
nomembers):
|
noprivate, nomembers):
|
||||||
# principal, manager, certificate and certmapdata are handled not in here
|
# principal, manager, certificate and certmapdata are handled not in here
|
||||||
_args = {}
|
_args = {}
|
||||||
if first is not None:
|
if first is not None:
|
||||||
@@ -809,6 +829,10 @@ def gen_args(first, last, fullname, displayname, initials, homedir, gecos,
|
|||||||
_args["employeetype"] = employeetype
|
_args["employeetype"] = employeetype
|
||||||
if preferredlanguage is not None:
|
if preferredlanguage is not None:
|
||||||
_args["preferredlanguage"] = preferredlanguage
|
_args["preferredlanguage"] = preferredlanguage
|
||||||
|
if idp is not None:
|
||||||
|
_args["ipaidpconfiglink"] = idp
|
||||||
|
if idp_user_id is not None:
|
||||||
|
_args["ipaidpsub"] = idp_user_id
|
||||||
if noprivate is not None:
|
if noprivate is not None:
|
||||||
_args["noprivate"] = noprivate
|
_args["noprivate"] = noprivate
|
||||||
if nomembers is not None:
|
if nomembers is not None:
|
||||||
@@ -833,6 +857,7 @@ def check_parameters( # pylint: disable=unused-argument
|
|||||||
employeenumber, employeetype, preferredlanguage, certificate,
|
employeenumber, employeetype, preferredlanguage, certificate,
|
||||||
certmapdata, noprivate, nomembers, preserve, update_password,
|
certmapdata, noprivate, nomembers, preserve, update_password,
|
||||||
smb_logon_script, smb_profile_path, smb_home_dir, smb_home_drive,
|
smb_logon_script, smb_profile_path, smb_home_dir, smb_home_drive,
|
||||||
|
idp, ipa_user_id,
|
||||||
):
|
):
|
||||||
if state == "present" and action == "user":
|
if state == "present" and action == "user":
|
||||||
invalid = ["preserve"]
|
invalid = ["preserve"]
|
||||||
@@ -846,7 +871,7 @@ def check_parameters( # pylint: disable=unused-argument
|
|||||||
"departmentnumber", "employeenumber", "employeetype",
|
"departmentnumber", "employeenumber", "employeetype",
|
||||||
"preferredlanguage", "noprivate", "nomembers", "update_password",
|
"preferredlanguage", "noprivate", "nomembers", "update_password",
|
||||||
"gecos", "smb_logon_script", "smb_profile_path", "smb_home_dir",
|
"gecos", "smb_logon_script", "smb_profile_path", "smb_home_dir",
|
||||||
"smb_home_drive",
|
"smb_home_drive", "idp", "idp_user_id"
|
||||||
]
|
]
|
||||||
|
|
||||||
if state == "present" and action == "member":
|
if state == "present" and action == "member":
|
||||||
@@ -1069,6 +1094,9 @@ def main():
|
|||||||
elements='dict', required=False),
|
elements='dict', required=False),
|
||||||
noprivate=dict(type='bool', default=None),
|
noprivate=dict(type='bool', default=None),
|
||||||
nomembers=dict(type='bool', default=None),
|
nomembers=dict(type='bool', default=None),
|
||||||
|
idp=dict(type="str", default=None, aliases=['ipaidpconfiglink']),
|
||||||
|
idp_user_id=dict(type="str", default=None,
|
||||||
|
aliases=['ipaidpconfiglink']),
|
||||||
)
|
)
|
||||||
|
|
||||||
ansible_module = IPAAnsibleModule(
|
ansible_module = IPAAnsibleModule(
|
||||||
@@ -1171,6 +1199,8 @@ def main():
|
|||||||
smb_profile_path = ansible_module.params_get("smb_profile_path")
|
smb_profile_path = ansible_module.params_get("smb_profile_path")
|
||||||
smb_home_dir = ansible_module.params_get("smb_home_dir")
|
smb_home_dir = ansible_module.params_get("smb_home_dir")
|
||||||
smb_home_drive = ansible_module.params_get("smb_home_drive")
|
smb_home_drive = ansible_module.params_get("smb_home_drive")
|
||||||
|
idp = ansible_module.params_get("idp")
|
||||||
|
idp_user_id = ansible_module.params_get("idp_user_id")
|
||||||
certificate = ansible_module.params_get("certificate")
|
certificate = ansible_module.params_get("certificate")
|
||||||
certmapdata = ansible_module.params_get("certmapdata")
|
certmapdata = ansible_module.params_get("certmapdata")
|
||||||
noprivate = ansible_module.params_get("noprivate")
|
noprivate = ansible_module.params_get("noprivate")
|
||||||
@@ -1204,7 +1234,7 @@ def main():
|
|||||||
radiususer, departmentnumber, employeenumber, employeetype,
|
radiususer, departmentnumber, employeenumber, employeetype,
|
||||||
preferredlanguage, certificate, certmapdata, noprivate, nomembers,
|
preferredlanguage, certificate, certmapdata, noprivate, nomembers,
|
||||||
preserve, update_password, smb_logon_script, smb_profile_path,
|
preserve, update_password, smb_logon_script, smb_profile_path,
|
||||||
smb_home_dir, smb_home_drive)
|
smb_home_dir, smb_home_drive, idp, idp_user_id)
|
||||||
certmapdata = convert_certmapdata(certmapdata)
|
certmapdata = convert_certmapdata(certmapdata)
|
||||||
|
|
||||||
# Use users if names is None
|
# Use users if names is None
|
||||||
@@ -1298,6 +1328,8 @@ def main():
|
|||||||
smb_profile_path = user.get("smb_profile_path")
|
smb_profile_path = user.get("smb_profile_path")
|
||||||
smb_home_dir = user.get("smb_home_dir")
|
smb_home_dir = user.get("smb_home_dir")
|
||||||
smb_home_drive = user.get("smb_home_drive")
|
smb_home_drive = user.get("smb_home_drive")
|
||||||
|
idp = user.get("idp")
|
||||||
|
idp_user_id = user.get("idp_user_id")
|
||||||
certificate = user.get("certificate")
|
certificate = user.get("certificate")
|
||||||
certmapdata = user.get("certmapdata")
|
certmapdata = user.get("certmapdata")
|
||||||
noprivate = user.get("noprivate")
|
noprivate = user.get("noprivate")
|
||||||
@@ -1314,7 +1346,7 @@ def main():
|
|||||||
employeetype, preferredlanguage, certificate,
|
employeetype, preferredlanguage, certificate,
|
||||||
certmapdata, noprivate, nomembers, preserve,
|
certmapdata, noprivate, nomembers, preserve,
|
||||||
update_password, smb_logon_script, smb_profile_path,
|
update_password, smb_logon_script, smb_profile_path,
|
||||||
smb_home_dir, smb_home_drive)
|
smb_home_dir, smb_home_drive, idp, idp_user_id)
|
||||||
certmapdata = convert_certmapdata(certmapdata)
|
certmapdata = convert_certmapdata(certmapdata)
|
||||||
|
|
||||||
# Check API specific parameters
|
# Check API specific parameters
|
||||||
@@ -1375,6 +1407,19 @@ def main():
|
|||||||
"smb_profile_path, and smb_home_drive is not supported "
|
"smb_profile_path, and smb_home_drive is not supported "
|
||||||
"by your IPA version")
|
"by your IPA version")
|
||||||
|
|
||||||
|
# Check if IdP support is available
|
||||||
|
require_idp = (
|
||||||
|
idp is not None
|
||||||
|
or idp_user_id is not None
|
||||||
|
or userauthtype == "idp"
|
||||||
|
)
|
||||||
|
has_idp_support = ansible_module.ipa_command_param_exists(
|
||||||
|
"user_add", "ipaidpconfiglink"
|
||||||
|
)
|
||||||
|
if require_idp and not has_idp_support:
|
||||||
|
ansible_module.fail_json(
|
||||||
|
msg="Your IPA version does not support External IdP.")
|
||||||
|
|
||||||
# Make sure user exists
|
# Make sure user exists
|
||||||
res_find = find_user(ansible_module, name)
|
res_find = find_user(ansible_module, name)
|
||||||
|
|
||||||
@@ -1390,7 +1435,9 @@ def main():
|
|||||||
carlicense, sshpubkey, userauthtype, userclass, radius,
|
carlicense, sshpubkey, userauthtype, userclass, radius,
|
||||||
radiususer, departmentnumber, employeenumber, employeetype,
|
radiususer, departmentnumber, employeenumber, employeetype,
|
||||||
preferredlanguage, smb_logon_script, smb_profile_path,
|
preferredlanguage, smb_logon_script, smb_profile_path,
|
||||||
smb_home_dir, smb_home_drive, noprivate, nomembers)
|
smb_home_dir, smb_home_drive, idp, idp_user_id, noprivate,
|
||||||
|
nomembers,
|
||||||
|
)
|
||||||
|
|
||||||
if action == "user":
|
if action == "user":
|
||||||
# Found the user
|
# Found the user
|
||||||
|
|||||||
107
tests/user/test_user_idp_attrs.yml
Normal file
107
tests/user/test_user_idp_attrs.yml
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
---
|
||||||
|
- name: Test user
|
||||||
|
hosts: "{{ ipa_test_host | default('ipaserver') }}"
|
||||||
|
become: false
|
||||||
|
gather_facts: false
|
||||||
|
module_defaults:
|
||||||
|
ipauser:
|
||||||
|
ipaadmin_password: SomeADMINpassword
|
||||||
|
ipaapi_context: "{{ ipa_context | default(omit) }}"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Include tasks ../env_freeipa_facts.yml
|
||||||
|
ansible.builtin.include_tasks: ../env_freeipa_facts.yml
|
||||||
|
|
||||||
|
# CLEANUP TEST ITEMS
|
||||||
|
|
||||||
|
- name: Ensure user idpuser is absent
|
||||||
|
ipauser:
|
||||||
|
name: idpuser
|
||||||
|
state: absent
|
||||||
|
|
||||||
|
# CREATE TEST ITEMS
|
||||||
|
- name: Run tests if FreeIPA 4.10.0+ is installed
|
||||||
|
when: ipa_version is version('4.10.0', '>=')
|
||||||
|
block:
|
||||||
|
- name: Ensure IDP provider is present
|
||||||
|
# TODO: Use an ansible-freeipa plugin instead of 'shell'
|
||||||
|
ansible.builtin.shell:
|
||||||
|
cmd: |
|
||||||
|
kinit -c test_krb5_cache admin <<< SomeADMINpassword
|
||||||
|
KRB5CCNAME=test_krb5_cache ipa idp-add keycloak --provider keycloak \
|
||||||
|
--org master \
|
||||||
|
--base-url https://client.ipademo.local:8443/auth \
|
||||||
|
--client-id ipa_oidc_client \
|
||||||
|
--secret <<< $(echo -e "Secret123\nSecret123")
|
||||||
|
kdestroy -c test_krb5_cache -q -A
|
||||||
|
register: addidp
|
||||||
|
failed_when:
|
||||||
|
- '"Added Identity Provider" not in addidp.stdout'
|
||||||
|
- '"already exists" not in addidp.stderr'
|
||||||
|
|
||||||
|
# TESTS
|
||||||
|
|
||||||
|
- name: Ensure user idpuser is present
|
||||||
|
ipauser:
|
||||||
|
name: idpuser
|
||||||
|
first: IDP
|
||||||
|
last: User
|
||||||
|
userauthtype: idp
|
||||||
|
idp: keycloak
|
||||||
|
idp_user_id: "idpuser@ipademo.local"
|
||||||
|
register: result
|
||||||
|
failed_when: not result.changed or result.failed
|
||||||
|
|
||||||
|
- name: Ensure user idpuser is present again
|
||||||
|
ipauser:
|
||||||
|
name: idpuser
|
||||||
|
first: IDP
|
||||||
|
last: User
|
||||||
|
userauthtype: idp
|
||||||
|
idp: keycloak
|
||||||
|
idp_user_id: "idpuser@ipademo.local"
|
||||||
|
register: result
|
||||||
|
failed_when: result.changed or result.failed
|
||||||
|
|
||||||
|
- name: Clear 'idp_user_id'
|
||||||
|
ipauser:
|
||||||
|
name: idpuser
|
||||||
|
idp_user_id: ""
|
||||||
|
register: result
|
||||||
|
failed_when: not result.changed or result.failed
|
||||||
|
|
||||||
|
- name: Clear 'idp'
|
||||||
|
ipauser:
|
||||||
|
name: idpuser
|
||||||
|
idp: ""
|
||||||
|
register: result
|
||||||
|
failed_when: not result.changed or result.failed
|
||||||
|
|
||||||
|
- name: Ensure user idpuser is absent
|
||||||
|
ipauser:
|
||||||
|
name: idpuser
|
||||||
|
state: absent
|
||||||
|
register: result
|
||||||
|
failed_when: not result.changed or result.failed
|
||||||
|
|
||||||
|
- name: Ensure user idpuser is absent again
|
||||||
|
ipauser:
|
||||||
|
name: idpuser
|
||||||
|
state: absent
|
||||||
|
register: result
|
||||||
|
failed_when: result.changed or result.failed
|
||||||
|
|
||||||
|
|
||||||
|
# CLEANUP TEST ITEMS
|
||||||
|
- name: Ensure IDP provider is absent
|
||||||
|
# TODO: Use an ansible-freeipa plugin instead of 'shell'
|
||||||
|
ansible.builtin.shell:
|
||||||
|
cmd: |
|
||||||
|
kinit -c test_krb5_cache admin <<< SomeADMINpassword
|
||||||
|
ipa idp-del keycloak
|
||||||
|
kdestroy -c test_krb5_cache -q -A
|
||||||
|
always:
|
||||||
|
- name: Ensure user idpuser is absent
|
||||||
|
ipauser:
|
||||||
|
name: idpuser
|
||||||
|
state: absent
|
||||||
Reference in New Issue
Block a user