1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

New module: Add module or managing Windows Active Directory users (windows/win_domain_user) (#24075)

* Initial win_domain_user module support

* Add return information

* Update return values

* Add try/catch for PS module import

* Improve win_domain_user module

* Fix bad merge

* Fix pep8 failure

* Actually fix pep8 failure

* Update win_domain_user.py to meet standards

* Add check_mode support for win_domain_user

* Updated documentation before merge
This commit is contained in:
Nick Chandler 2017-08-10 20:23:09 -04:00 committed by Jordan Borean
parent 7c59b66802
commit 22533c0932
2 changed files with 590 additions and 0 deletions

View file

@ -0,0 +1,280 @@
#!powershell
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# WANT_JSON
# POWERSHELL_COMMON
########
try {
Import-Module ActiveDirectory
}
catch {
Fail-Json $result "Failed to import ActiveDirectory PowerShell module. This module should be run on a domain controller, and the ActiveDirectory module must be available."
}
$result = @{
changed = $false
password_updated = $false
}
$params = Parse-Args $args -supports_check_mode $true
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -default $false
# Module control parameters
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "present","absent","query"
$update_password = Get-AnsibleParam -obj $params -name "update_password" -type "str" -default "always" -validateset "always","on_create"
$groups_action = Get-AnsibleParam -obj $params -name "groups_action" -type "str" -default "replace" -validateset "add","remove","replace"
# User account parameters
$username = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true
$description = Get-AnsibleParam -obj $params -name "description" -type "str"
$password = Get-AnsibleParam -obj $params -name "password" -type "str"
$password_expired = Get-AnsibleParam -obj $params -name "password_expired" -type "bool"
$password_never_expires = Get-AnsibleParam -obj $params -name "password_never_expires" -type "bool"
$user_cannot_change_password = Get-AnsibleParam -obj $params -name "user_cannot_change_password" -type "bool"
$account_locked = Get-AnsibleParam -obj $params -name "account_locked" -type "bool"
$groups = Get-AnsibleParam -obj $params -name "groups" -type "list"
$enabled = Get-AnsibleParam -obj $params -name "enabled" -type "bool" -default $true
$path = Get-AnsibleParam -obj $params -name "path" -type "str"
$upn = Get-AnsibleParam -obj $params -name "upn" -type "str"
# User informational parameters
$user_info = @{
GivenName = Get-AnsibleParam -obj $params -name "firstname" -type "str"
Surname = Get-AnsibleParam -obj $params -name "surname" -type "str"
Company = Get-AnsibleParam -obj $params -name "company" -type "str"
EmailAddress = Get-AnsibleParam -obj $params -name "email" -type "str"
StreetAddress = Get-AnsibleParam -obj $params -name "street" -type "str"
City = Get-AnsibleParam -obj $params -name "city" -type "str"
State = Get-AnsibleParam -obj $params -name "state_province" -type "str"
PostalCode = Get-AnsibleParam -obj $params -name "postal_code" -type "str"
Country = Get-AnsibleParam -obj $params -name "country" -type "str"
}
# Parameter validation
If ($account_locked -ne $null -and $account_locked) {
Fail-Json $result "account_locked must be set to 'no' if provided"
}
If (($password_expired -ne $null) -and ($password_never_expires -ne $null)) {
Fail-Json $result "password_expired and password_never_expires are mutually exclusive but have both been set"
}
try {
$user_obj = Get-ADUser -Identity $username -Properties *
}
catch {
$user_obj = $null
}
If ($state -eq 'present') {
# Ensure user exists
try {
$new_user = $false
# If the account does not exist, create it
If (-not $user_obj) {
If ($path -ne $null){
New-ADUser -Name $username -Path $path -WhatIf:$check_mode
}
Else {
New-ADUser -Name $username -WhatIf:$check_mode
}
$new_user = $true
$result.changed = $true
If ($check_mode) {
Exit-Json $result
}
$user_obj = Get-ADUser -Identity $username -Properties *
}
# Set the password if required
If ($password -and (($new_user -and $update_password -eq "on_create") -or $update_password -eq "always")) {
$secure_password = ConvertTo-SecureString $password -AsPlainText -Force
Set-ADAccountPassword -Identity $username -Reset:$true -Confirm:$false -NewPassword $secure_password -WhatIf:$check_mode
$user_obj = Get-ADUser -Identity $username -Properties *
$result.password_updated = $true
$result.changed = $true
}
# Configure password policies
If (($password_never_expires -ne $null) -and ($password_never_expires -ne $user_obj.PasswordNeverExpires)) {
Set-ADUser -Identity $username -PasswordNeverExpires $password_never_expires -WhatIf:$check_mode
$user_obj = Get-ADUser -Identity $username -Properties *
$result.changed = $true
}
If (($password_expired -ne $null) -and ($password_expired -ne $user_obj.PasswordExpired)) {
Set-ADUser -Identity $username -ChangePasswordAtLogon $password_expired -WhatIf:$check_mode
$user_obj = Get-ADUser -Identity $username -Properties *
$result.changed = $true
}
If (($user_cannot_change_password -ne $null) -and ($user_cannot_change_password -ne $user_obj.CannotChangePassword)) {
Set-ADUser -Identity $username -CannotChangePassword $user_cannot_change_password -WhatIf:$check_mode
$user_obj = Get-ADUser -Identity $username -Properties *
$result.changed = $true
}
# Assign other account settings
If (($upn -ne $null) -and ($upn -ne $user_obj.UserPrincipalName)) {
Set-ADUser -Identity $username -UserPrincipalName $upn -WhatIf:$check_mode
$user_obj = Get-ADUser -Identity $username -Properties *
$result.changed = $true
}
If (($description -ne $null) -and ($description -ne $user_obj.Description)) {
Set-ADUser -Identity $username -description $description -WhatIf:$check_mode
$user_obj = Get-ADUser -Identity $username -Properties *
$result.changed = $true
}
If ($enabled -ne $user_obj.Enabled) {
Set-ADUser -Identity $username -Enabled $enabled -WhatIf:$check_mode
$user_obj = Get-ADUser -Identity $username -Properties *
$result.changed = $true
}
If ((-not $account_locked) -and ($user_obj.LockedOut -eq $true)) {
Unlock-ADAccount -Identity $username -WhatIf:$check_mode
$user_obj = Get-ADUser -Identity $username -Properties *
$result.changed = $true
}
# Set user information
Foreach ($key in $user_info.Keys) {
If ($user_info[$key] -eq $null) {
continue
}
$value = $user_info[$key]
If ($value -ne $user_obj.$key) {
$expression = "Set-ADUser -Identity $username -$key '$value'"
If (-not $check_mode) {
Invoke-Expression $expression
}
$result.changed = $true
$user_obj = Get-ADUser -Identity $username -Properties *
}
}
# Configure group assignment
If ($groups -ne $null) {
$group_list = $groups
$groups = @()
Foreach ($group in $group_list) {
$groups += (Get-ADGroup -Identity $group).DistinguishedName
}
$assigned_groups = @()
Foreach ($group in (Get-ADPrincipalGroupMembership -Identity $username)) {
$assigned_groups += $group.DistinguishedName
}
switch ($groups_action) {
"add" {
Foreach ($group in $groups) {
If (-not ($assigned_groups -Contains $group)) {
Add-ADGroupMember -Identity $group -Members $username -WhatIf:$check_mode
$user_obj = Get-ADUser -Identity $username -Properties *
$result.changed = $true
}
}
}
"remove" {
Foreach ($group in $groups) {
If ($assigned_groups -Contains $group) {
Remove-ADGroupMember -Identity $group -Members $username -Confirm:$false -WhatIf:$check_mode
$user_obj = Get-ADUser -Identity $username -Properties *
$result.changed = $true
}
}
}
"replace" {
Foreach ($group in $assigned_groups) {
If (($group -ne $user_obj.PrimaryGroup) -and -not ($groups -Contains $group)) {
Remove-ADGroupMember -Identity $group -Members $username -Confirm:$false -WhatIf:$check_mode
$user_obj = Get-ADUser -Identity $username -Properties *
$result.changed = $true
}
}
Foreach ($group in $groups) {
If (-not ($assigned_groups -Contains $group)) {
Add-ADGroupMember -Identity $group -Members $username -WhatIf:$check_mode
$user_obj = Get-ADUser -Identity $username -Properties *
$result.changed = $true
}
}
}
}
}
}
catch {
Fail-Json $result $_.Exception.Message
}
} ElseIf ($state -eq 'absent') {
# Ensure user does not exist
try {
If ($user_obj) {
Remove-ADUser $user_obj -Confirm:$false -WhatIf:$check_mode
$result.changed = $true
If ($check_mode) {
Exit-Json $result
}
$user_obj = $null
}
}
catch {
Fail-Json $result $_.Exception.Message
}
}
try {
If ($user_obj) {
$user_obj = Get-ADUser -Identity $username -Properties *
$result.name = $user_obj.Name
$result.firstname = $user_obj.GivenName
$result.surname = $user_obj.Surname
$result.enabled = $user_obj.Enabled
$result.company = $user_obj.Company
$result.street = $user_obj.StreetAddress
$result.email = $user_obj.EmailAddress
$result.city = $user_obj.City
$result.state_province = $user_obj.State
$result.country = $user_obj.Country
$result.postal_code = $user_obj.PostalCode
$result.distinguished_name = $user_obj.DistinguishedName
$result.description = $user_obj.Description
$result.password_expired = $user_obj.PasswordExpired
$result.password_never_expires = $user_obj.PasswordNeverExpires
$result.user_cannot_change_password = $user_obj.CannotChangePassword
$result.account_locked = $user_obj.LockedOut
$result.sid = [string]$user_obj.SID
$result.upn = $user_obj.UserPrincipalName
$user_groups = @()
Foreach ($group in (Get-ADPrincipalGroupMembership $username)) {
$user_groups += $group.name
}
$result.groups = $user_groups
$result.msg = "User '$username' is present"
$result.state = "present"
}
Else {
$result.name = $username
$result.msg = "User '$username' is absent"
$result.state = "absent"
}
}
catch {
Fail-Json $result $_.Exception.Message
}
Exit-Json $result

View file

@ -0,0 +1,310 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# this is a windows documentation stub. actual code lives in the .ps1
# file of the same name
ANSIBLE_METADATA = {'metadata_version': '1.0',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: win_domain_user
version_added: '2.4'
short_description: Manages Windows Active Directory user accounts
description:
- Manages Windows Active Directory user accounts.
options:
name:
description:
- Name of the user to create, remove or modify.
required: true
state:
description:
- When C(present), creates or updates the user account. When C(absent),
removes the user account if it exists. When C(query),
retrieves the user account details without making any changes.
choices:
- present
- absent
- query
default: present
enabled:
description:
- C(yes) will enable the user account. C(no) will disable the account.
type: bool
default: 'yes'
account_locked:
description:
- C(no) will unlock the user account if locked. Note that there is not a
way to lock an account as an administrator. Accounts are locked due to
user actions; as an admin, you may only unlock a locked account. If you
wish to administratively disable an account, set 'enabled' to 'no'.
choices: [ 'no' ]
type: bool
description:
description:
- Description of the user
groups:
description:
- Adds or removes the user from this list of groups,
depending on the value of I(groups_action). To remove all but the
Principal Group, set C(groups=<principal group name>) and
I(groups_action=replace). Note that users cannot be removed from
their principal group (for example, "Domain Users").
groups_action:
description:
- If C(replace), the user is added as a member of each group in
I(groups) and removed from any other groups. If C(add), the user is
added to each group in I(groups) where not already a member. If
C(remove), the user is removed from each group in I(groups).
choices: [ 'replace', 'add', 'remove' ]
default: replace
password:
description:
- Optionally set the user's password to this (plain text) value. In order
to enable an account - I(enabled) - a password must already be
configured on the account, or you must provide a password here.
update_password:
description:
- C(always) will update passwords if they differ. C(on_create) will
only set the password for newly created users. Note that C(always) will
always report an Ansible status of 'changed' because we cannot
determine whether the new password differs from the old password.
choices: [ 'always', 'on_create' ]
default: always
password_expired:
description:
- C(yes) will require the user to change their password at next login.
C(no) will clear the expired password flag. This is mutually exclusive
with I(password_never_expires).
type: bool
password_never_expires:
description:
- C(yes) will set the password to never expire. C(no) will allow the
password to expire. This is mutually exclusive with I(password_expired)
type: bool
user_cannot_change_password:
description:
- C(yes) will prevent the user from changing their password. C(no) will
allow the user to change their password.
type: bool
firstname:
description:
- Configures the user's first name (given name)
surname:
description:
- Configures the user's last name (surname)
company:
description:
- Configures the user's company name
upn:
description:
- Configures the User Principal Name (UPN) for the account. This is not
required, but is best practice to configure for modern versions of
Active Directory. The format is "<username>@<domain>".
email:
description:
- Configures the user's email address. This is a record in AD and does
not do anything to configure any email servers or systems.
street:
description:
- Configures the user's street address
city:
description:
- Configures the user's city
state_province:
description:
- Configures the user's state or province
postal_code:
description:
- Configures the user's postal code / zip code
country:
description:
- Configures the user's country code. Note that this is a two-character
ISO 3166 code.
path:
description:
- Container or OU for the new user; if you do not specify this, the
user will be placed in the default container for users in the domain.
Setting the path is only available when a new user is created;
if you specify a path on an existing user, the user's path will not
be updated - you must delete (e.g., state=absent) the user and
then re-add the user with the appropriate path.
notes:
- Works with Windows 2012R2 and newer.
- If running on a server that is not a Domain Controller, credential
delegation through CredSSP or Kerberos with delegation must be used.
- Note that some individuals have confirmed successful operation on Windows
2008R2 servers with AD and AD Web Services enabled, but this has not
received the same degree of testing as Windows 2012R2.
author:
- Nick Chandler (@nwchandler)
'''
EXAMPLES = r'''
- name: Ensure user bob is present with address information
win_domain_user:
name: bob
firstname: Bob
surname: Smith
company: BobCo
password: B0bP4ssw0rd
state: present
groups:
- Domain Admins
street: 123 4th St.
city: Sometown
state_province: IN
postal_code: 12345
country: US
- name: Ensure user bob is present in OU ou=test,dc=domain,dc=local
win_domain_user:
name: bob
password: B0bP4ssw0rd
state: present
path: ou=test,dc=domain,dc=local
groups:
- Domain Admins
- name: Ensure user bob is absent
win_domain_user:
name: bob
state: absent
'''
RETURN = r'''
account_locked:
description: true if the account is locked
returned: always
type: boolean
sample: false
changed:
description: true if the account changed during execution
returned: always
type: boolean
sample: false
city:
description: The user city
returned: always
type: string
sample: Indianapolis
company:
description: The user company
returned: always
type: string
sample: RedHat
country:
description: The user country
returned: always
type: string
sample: US
description:
description: A description of the account
returned: always
type: string
sample: Server Administrator
distinguished_name:
description: DN of the user account
returned: always
type: string
sample: CN=nick,OU=test,DC=domain,DC=local
email:
description: The user email address
returned: always
type: string
sample: nick@domain.local
enabled:
description: true if the account is enabled and false if disabled
returned: always
type: string
sample: true
firstname:
description: The user first name
returned: always
type: string
sample: Nick
groups:
description: AD Groups to which the account belongs
returned: always
type: list
sample: [ "Domain Admins", "Domain Users" ]
msg:
description: Summary message of whether the user is present or absent
returned: always
type: string
sample: User nick is present
name:
description: The username on the account
returned: always
type: string
sample: nick
password_expired:
description: true if the account password has expired
returned: always
type: boolean
sample: false
password_updated:
description: true if the password changed during this execution
returned: always
type: boolean
sample: true
postal_code:
description: The user postal code
returned: always
type: string
sample: 46033
sid:
description: The SID of the account
returned: always
type: string
sample: S-1-5-21-2752426336-228313920-2202711348-1175
state:
description: The state of the user account
returned: always
type: string
sample: present
state_province:
description: The user state or province
returned: always
type: string
sample: IN
street:
description: The user street address
returned: always
type: string
sample: 123 4th St.
surname:
description: The user last name
returned: always
type: string
sample: Doe
upn:
description: The User Principal Name of the account
returned: always
type: string
sample: nick@domain.local
user_cannot_change_password:
description: true if the user is not allowed to change password
returned: always
type: string
sample: false
'''