Skip to content

Commit

Permalink
ipauser: Add support for SMB attributes.
Browse files Browse the repository at this point in the history
Since FreeIPA version 4.8.0 ipauser has support for smb-logon-script,
smb-profile-path, smb-home-dir, and smb-home-drive drive attributes.

On FreeIPA, these attributes are only available when modifying a user,
so if the user defined in the playbook does not exist, two calls to IPA
API are executed, a 'user_add' followed by a 'user_mod'.
(see https://github.com/freeipa/freeipa/blob/master/doc/designs/adtrust/samba-domain-controller.md

A new example playbook can be found at:

     playbooks/user/smb-attributes.yml

A new test playbook can be found at:

     tests/user/test_user_smb_attrs.yml
  • Loading branch information
rjeffman committed Jul 14, 2023
1 parent 51ddaa6 commit d6efb7a
Show file tree
Hide file tree
Showing 4 changed files with 431 additions and 7 deletions.
31 changes: 31 additions & 0 deletions README-user.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,33 @@ Example playbook to ensure users are absent:
state: absent
```

When using FreeIPA 4.8.0+, SMB logon script, profile, home directory and home drive can be set for users.

In the example playbook to set SMB attributes note that `smb_profile_path` and `smb_home_dir` use paths in UNC format, which includes backslashes ('\\`). If the paths are quoted, the backslash needs to be escaped becoming "\\", so the path `\\server\dir` becomes `"\\\\server\\dir"`. If the paths are unquoted the slashes do not have to be escaped.

The YAML specification states that a colon (':') is a key separator and a dashe ('-') is an item marker, only with a space after them, so using both unquoted as part of a path should not be a problem. If a space is needed after a colon or a dash, then a quoted string must be used as in `"user - home"`. For the `smb_home_drive` attribute is is recomended that a quoted string is used, to improve readability.

Example playbook to set SMB attributes:

```yaml
---
- name: Plabook to handle users
hosts: ipaserver
become: false
tasks:
- name: Ensure user 'smbuser' is present with smb attributes
ipauser:
ipaadmin_password: SomeADMINpassword
name: smbuser
first: SMB
last: User
smb_logon_script: N:\logonscripts\startup
smb_profile_path: \\server\profiles\some_profile
smb_home_dir: \\users\home\smbuser
smb_home_drive: "U:"
```


Variables
=========
Expand Down Expand Up @@ -425,6 +452,10 @@ Variable | Description | Required
  | `subject` - Subject of the certificate, only usable together with `issuer` option. | no
  | `data` - Certmap data, not usable with other certmapdata options. | no
`noprivate` | Do not create user private group. (bool) | no
`smb_logon_script` \| `ipantlogonscript` | SMB logon script path. Requires FreeIPA version 4.8.0+. | no
`smb_profile_path:` \| `ipantprofilepath` | SMB profile path, in UNC format. Requires FreeIPA version 4.8.0+. | no
`smb_home_dir` \| `ipanthomedirectory` | SMB Home Directory, in UNC format. Requires FreeIPA version 4.8.0+. | no
`smb_home_drive` \| `ipanthomedirectorydrive` | SMB Home Directory Drive, a single upercase letter (A-Z) followed by a colon (:), for example "U:". Requires FreeIPA version 4.8.0+. | no
`nomembers` | Suppress processing of membership attributes. (bool) | no


Expand Down
17 changes: 17 additions & 0 deletions playbooks/user/smb-attributes.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
- name: Plabook to handle users
hosts: ipaserver
become: false
gather_facts: false

tasks:
- name: Ensure user 'smbuser' is present with smb attributes
ipauser:
ipaadmin_password: SomeADMINpassword
name: smbuser
first: SMB
last: User
smb_logon_script: N:\logonscripts\startup
smb_profile_path: \\server\profiles\some_profile
smb_home_dir: \\users\home\smbuser
smb_home_drive: "U:"
137 changes: 130 additions & 7 deletions plugins/modules/ipauser.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,31 @@
description: Employee Type
type: str
required: false
smb_logon_script:
description: SMB logon script path
type: str
required: false
aliases: ["ipantlogonscript"]
smb_profile_path:
description: SMB profile path
type: str
required: false
aliases: ["ipantprofilepath"]
smb_home_dir:
description: SMB Home Directory
type: str
required: false
aliases: ["ipanthomedirectory"]
smb_home_drive:
description: SMB Home Directory Drive
type: str
required: false
choices: [
'A:', 'B:', 'C:', 'D:', 'E:', 'F:', 'G:', 'H:', 'I:', 'J:',
'K:', 'L:', 'M:', 'N:', 'O:', 'P:', 'Q:', 'R:', 'S:', 'T:',
'U:', 'V:', 'W:', 'X:', 'Y:', 'Z:', ''
]
aliases: ["ipanthomedirectorydrive"]
preferredlanguage:
description: Preferred Language
type: str
Expand Down Expand Up @@ -474,6 +499,31 @@
description: Employee Type
type: str
required: false
smb_logon_script:
description: SMB logon script path
type: str
required: false
aliases: ["ipantlogonscript"]
smb_profile_path:
description: SMB profile path
type: str
required: false
aliases: ["ipantprofilepath"]
smb_home_dir:
description: SMB Home Directory
type: str
required: false
aliases: ["ipanthomedirectory"]
smb_home_drive:
description: SMB Home Directory Drive
type: str
required: false
choices: [
'A:', 'B:', 'C:', 'D:', 'E:', 'F:', 'G:', 'H:', 'I:', 'J:',
'K:', 'L:', 'M:', 'N:', 'O:', 'P:', 'Q:', 'R:', 'S:', 'T:',
'U:', 'V:', 'W:', 'X:', 'Y:', 'Z:', ''
]
aliases: ["ipanthomedirectorydrive"]
preferredlanguage:
description: Preferred Language
type: str
Expand Down Expand Up @@ -613,6 +663,17 @@
ipaadmin_password: SomeADMINpassword
name: pinky,brain
state: disabled
# Ensure a user has SMB attributes
- ipauser:
ipaadmin_password: SomeADMINpassword
name: smbuser
first: SMB
last: User
smb_logon_script: N:\\logonscripts\\startup
smb_profile_path: \\\\server\\profiles\\some_profile
smb_home_dir: \\\\users\\home\\smbuser
smb_home_drive: "U:"
"""

RETURN = """
Expand Down Expand Up @@ -673,7 +734,8 @@ def gen_args(first, last, fullname, displayname, initials, homedir, gecos,
random, uid, gid, street, city, userstate, postalcode, phone,
mobile, pager, fax, orgunit, title, carlicense, sshpubkey,
userauthtype, userclass, radius, radiususer, departmentnumber,
employeenumber, employeetype, preferredlanguage, noprivate,
employeenumber, employeetype, preferredlanguage, smb_logon_script,
smb_profile_path, smb_home_dir, smb_home_drive, noprivate,
nomembers):
# principal, manager, certificate and certmapdata are handled not in here
_args = {}
Expand Down Expand Up @@ -751,6 +813,14 @@ def gen_args(first, last, fullname, displayname, initials, homedir, gecos,
_args["noprivate"] = noprivate
if nomembers is not None:
_args["no_members"] = nomembers
if smb_logon_script is not None:
_args["ipantlogonscript"] = smb_logon_script
if smb_profile_path is not None:
_args["ipantprofilepath"] = smb_profile_path
if smb_home_dir is not None:
_args["ipanthomedirectory"] = smb_home_dir
if smb_home_drive is not None:
_args["ipanthomedirectorydrive"] = smb_home_drive
return _args


Expand All @@ -761,7 +831,9 @@ def check_parameters( # pylint: disable=unused-argument
mobile, pager, fax, orgunit, title, manager, carlicense, sshpubkey,
userauthtype, userclass, radius, radiususer, departmentnumber,
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,
):
if state == "present" and action == "user":
invalid = ["preserve"]
else:
Expand All @@ -773,7 +845,8 @@ def check_parameters( # pylint: disable=unused-argument
"sshpubkey", "userauthtype", "userclass", "radius", "radiususer",
"departmentnumber", "employeenumber", "employeetype",
"preferredlanguage", "noprivate", "nomembers", "update_password",
"gecos",
"gecos", "smb_logon_script", "smb_profile_path", "smb_home_dir",
"smb_home_drive",
]

if state == "present" and action == "member":
Expand Down Expand Up @@ -961,6 +1034,17 @@ def main():
departmentnumber=dict(type="list", elements="str", default=None),
employeenumber=dict(type="str", default=None),
employeetype=dict(type="str", default=None),
smb_logon_script=dict(type="str", default=None,
aliases=["ipantlogonscript"]),
smb_profile_path=dict(type="str", default=None,
aliases=["ipantprofilepath"]),
smb_home_dir=dict(type="str", default=None,
aliases=["ipanthomedirectory"]),
smb_home_drive=dict(type="str", default=None,
choices=[
("%c:" % chr(x))
for x in range(ord('A'), ord('Z') + 1)
] + [""], aliases=["ipanthomedirectorydrive"]),
preferredlanguage=dict(type="str", default=None),
certificate=dict(type="list", elements="str",
aliases=["usercertificate"], default=None),
Expand Down Expand Up @@ -1073,6 +1157,10 @@ def main():
employeenumber = ansible_module.params_get("employeenumber")
employeetype = ansible_module.params_get("employeetype")
preferredlanguage = ansible_module.params_get("preferredlanguage")
smb_logon_script = ansible_module.params_get("smb_logon_script")
smb_profile_path = ansible_module.params_get("smb_profile_path")
smb_home_dir = ansible_module.params_get("smb_home_dir")
smb_home_drive = ansible_module.params_get("smb_home_drive")
certificate = ansible_module.params_get("certificate")
certmapdata = ansible_module.params_get("certmapdata")
noprivate = ansible_module.params_get("noprivate")
Expand Down Expand Up @@ -1105,7 +1193,8 @@ def main():
manager, carlicense, sshpubkey, userauthtype, userclass, radius,
radiususer, departmentnumber, employeenumber, employeetype,
preferredlanguage, certificate, certmapdata, noprivate, nomembers,
preserve, update_password)
preserve, update_password, smb_logon_script, smb_profile_path,
smb_home_dir, smb_home_drive)
certmapdata = convert_certmapdata(certmapdata)

# Use users if names is None
Expand Down Expand Up @@ -1191,6 +1280,10 @@ def main():
employeenumber = user.get("employeenumber")
employeetype = user.get("employeetype")
preferredlanguage = user.get("preferredlanguage")
smb_logon_script = user.get("smb_logon_script")
smb_profile_path = user.get("smb_profile_path")
smb_home_dir = user.get("smb_home_dir")
smb_home_drive = user.get("smb_home_drive")
certificate = user.get("certificate")
certmapdata = user.get("certmapdata")
noprivate = user.get("noprivate")
Expand All @@ -1206,7 +1299,8 @@ def main():
radiususer, departmentnumber, employeenumber,
employeetype, preferredlanguage, certificate,
certmapdata, noprivate, nomembers, preserve,
update_password)
update_password, smb_logon_script, smb_profile_path,
smb_home_dir, smb_home_drive)
certmapdata = convert_certmapdata(certmapdata)

# Extend email addresses
Expand Down Expand Up @@ -1248,6 +1342,21 @@ def main():
msg="The use of certmapdata is not supported by "
"your IPA version")

# Check if SMB attributes are available
if (
any([
smb_logon_script, smb_profile_path, smb_home_dir,
smb_home_drive
])
and not ansible_module.ipa_command_param_exists(
"user_mod", "ipanthomedirectory"
)
):
ansible_module.fail_json(
msg="The use of smb_logon_script, smb_profile_path, "
"smb_profile_path, and smb_home_drive is not supported "
"by your IPA version")

# Make sure user exists
res_find = find_user(ansible_module, name)

Expand All @@ -1262,7 +1371,8 @@ def main():
postalcode, phone, mobile, pager, fax, orgunit, title,
carlicense, sshpubkey, userauthtype, userclass, radius,
radiususer, departmentnumber, employeenumber, employeetype,
preferredlanguage, noprivate, nomembers)
preferredlanguage, smb_logon_script, smb_profile_path,
smb_home_dir, smb_home_drive, noprivate, nomembers)

if action == "user":
# Found the user
Expand Down Expand Up @@ -1298,8 +1408,21 @@ def main():
ansible_module.fail_json(
msg="Last name is needed")

smb_attrs = {
k: args[k]
for k in [
"ipanthomedirectory",
"ipanthomedirectorydrive",
"ipantlogonscript",
"ipantprofilepath",
]
if k in args
}
for key in smb_attrs.keys():
del args[key]
commands.append([name, "user_add", args])

if smb_attrs:
commands.append([name, "user_mod", smb_attrs])
# Handle members: principal, manager, certificate and
# certmapdata
if res_find is not None:
Expand Down
Loading

0 comments on commit d6efb7a

Please sign in to comment.