From 04431216e7cc00f110df7a54752bdc4cf382e2a7 Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Tue, 24 Jul 2018 08:16:42 +1000 Subject: [PATCH] win_user: use different method to validate credentials that does not rely on SMB/RPC (#43059) * win_user: use different method to validate credentials that does not rely on SMB/RPC * Use Add-Type as SetLastError on .net reflection not working on 2012 R2 --- .../fragments/win_user-validate-fixes.yaml | 2 + lib/ansible/modules/windows/win_user.ps1 | 84 +++- .../targets/win_user/tasks/main.yml | 465 ++---------------- .../targets/win_user/tasks/tests.yml | 455 +++++++++++++++++ 4 files changed, 570 insertions(+), 436 deletions(-) create mode 100644 changelogs/fragments/win_user-validate-fixes.yaml create mode 100644 test/integration/targets/win_user/tasks/tests.yml diff --git a/changelogs/fragments/win_user-validate-fixes.yaml b/changelogs/fragments/win_user-validate-fixes.yaml new file mode 100644 index 0000000000..9e97c0d99b --- /dev/null +++ b/changelogs/fragments/win_user-validate-fixes.yaml @@ -0,0 +1,2 @@ +bugfixes: +- win_user - Use LogonUser to validate the password as it does not rely on SMB/RPC to be available https://github.com/ansible/ansible/issues/24884 diff --git a/lib/ansible/modules/windows/win_user.ps1 b/lib/ansible/modules/windows/win_user.ps1 index 4c2267755b..1c3964506a 100644 --- a/lib/ansible/modules/windows/win_user.ps1 +++ b/lib/ansible/modules/windows/win_user.ps1 @@ -8,6 +8,8 @@ ######## $ADS_UF_PASSWD_CANT_CHANGE = 64 $ADS_UF_DONT_EXPIRE_PASSWD = 65536 +$LOGON32_LOGON_NETWORK = 3 +$LOGON32_PROVIDER_DEFAULT = 0 $adsi = [ADSI]"WinNT://$env:COMPUTERNAME" @@ -38,9 +40,81 @@ function Get-Group($grp) { return } +Function Test-LocalCredential { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUserNameAndPassWordParams", "", Justification="We need to use the plaintext pass in the Win32 call, also the source isn't a secure string to using that is just a waste of time/code")] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "", Justification="See above")] + param([String]$Username, [String]$Password) + + $platform_util = @' +using System; +using System.Runtime.InteropServices; + +namespace Ansible +{ + public class WinUserPInvoke + { + [DllImport("advapi32.dll", SetLastError = true)] + public static extern bool LogonUser( + string lpszUsername, + string lpszDomain, + string lpszPassword, + UInt32 dwLogonType, + UInt32 dwLogonProvider, + out IntPtr phToken); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool CloseHandle( + IntPtr hObject); + } +} +'@ + + $original_tmp = $env:TMP + $original_temp = $env:TEMP + $env:TMP = $_remote_tmp + $env:TEMP = $_remote_tmp + Add-Type -TypeDefinition $platform_util + $env:TMP = $original_tmp + $env:TEMP = $original_temp + + $handle = [IntPtr]::Zero + $logon_res = [Ansible.WinUserPInvoke]::LogonUser($Username, $null, $Password, + $LOGON32_LOGON_NETWORK, $LOGON32_PROVIDER_DEFAULT, [Ref]$handle) + + if ($logon_res) { + $valid_credentials = $true + [Ansible.WinUserPInvoke]::CloseHandle($handle) > $null + } else { + $err_code = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() + # following errors indicate the creds are correct but the user was + # unable to log on for other reasons, which we don't care about + $success_codes = @( + 0x0000052F, # ERROR_ACCOUNT_RESTRICTION + 0x00000530, # ERROR_INVALID_LOGON_HOURS + 0x00000531, # ERROR_INVALID_WORKSTATION + 0x00000569 # ERROR_LOGON_TYPE_GRANTED + ) + + if ($err_code -eq 0x0000052E) { + # ERROR_LOGON_FAILURE - the user or pass was incorrect + $valid_credentials = $false + } elseif ($err_code -in $success_codes) { + $valid_credentials = $true + } else { + # an unknown failure, raise an Exception for this + $win32_exp = New-Object -TypeName System.ComponentModel.Win32Exception -ArgumentList $err_code + $err_msg = "LogonUserW failed: $($win32_exp.Message) (Win32ErrorCode: $err_code)" + throw New-Object -TypeName System.ComponentModel.Win32Exception -ArgumentList $err_code, $err_msg + } + } + + return $valid_credentials +} + ######## $params = Parse-Args $args; +$_remote_tmp = Get-AnsibleParam $params "_ansible_remote_tmp" -type "path" -default $env:TMP $result = @{ changed = $false @@ -91,16 +165,16 @@ If ($state -eq 'present') { $result.changed = $true } ElseIf (($password -ne $null) -and ($update_password -eq 'always')) { - [void][system.reflection.assembly]::LoadWithPartialName('System.DirectoryServices.AccountManagement') - $host_name = [System.Net.Dns]::GetHostName() - $pc = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext 'Machine', $host_name - # ValidateCredentials will fail if either of these are true- just force update... If($user_obj.AccountDisabled -or $user_obj.PasswordExpired) { $password_match = $false } Else { - $password_match = $pc.ValidateCredentials($username, $password) + try { + $password_match = Test-LocalCredential -Username $username -Password $password + } catch [System.ComponentModel.Win32Exception] { + Fail-Json -obj $result -message "Failed to validate the user's credentials: $($_.Exception.Message)" + } } If (-not $password_match) { diff --git a/test/integration/targets/win_user/tasks/main.yml b/test/integration/targets/win_user/tasks/main.yml index e255ae41f1..072a381c9e 100644 --- a/test/integration/targets/win_user/tasks/main.yml +++ b/test/integration/targets/win_user/tasks/main.yml @@ -1,431 +1,34 @@ -# test code for the win_user module -# (c) 2014, Chris Church - -# 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 . - -- name: remove existing test user if present - win_user: name="{{ test_win_user_name }}" state="absent" - register: win_user_remove_result - -- name: check user removal result - assert: - that: - - "win_user_remove_result.name" - - "win_user_remove_result.state == 'absent'" - -- name: try to remove test user again - win_user: name="{{ test_win_user_name }}" state="absent" - register: win_user_remove_result_again - -- name: check user removal result again - assert: - that: - - "win_user_remove_result_again is not changed" - - "win_user_remove_result_again.name" - - "win_user_remove_result_again.msg" - - "win_user_remove_result.state == 'absent'" - -- name: test missing user with query state - win_user: name="{{ test_win_user_name }}" state="query" - register: win_user_missing_query_result - -- name: check missing query result - assert: - that: - - "win_user_missing_query_result is not changed" - - "win_user_missing_query_result.name" - - "win_user_missing_query_result.msg" - - "win_user_missing_query_result.state == 'absent'" - -- name: test create user - win_user: name="{{ test_win_user_name }}" password="{{ test_win_user_password }}" fullname="Test User" description="Test user account" groups="Guests" - register: win_user_create_result - -- name: check user creation result - assert: - that: - - "win_user_create_result is changed" - - "win_user_create_result.name == '{{ test_win_user_name }}'" - - "win_user_create_result.fullname == 'Test User'" - - "win_user_create_result.description == 'Test user account'" - - "win_user_create_result.path" - - "win_user_create_result.state == 'present'" - -- name: update user full name and description - win_user: name="{{ test_win_user_name }}" fullname="Test Ansible User" description="Test user account created by Ansible" groups="" - register: win_user_update_result - -- name: check full name and description update result - assert: - that: - - "win_user_update_result is changed" - - "win_user_update_result.fullname == 'Test Ansible User'" - - "win_user_update_result.description == 'Test user account created by Ansible'" - -- name: update user full name and description again with same values - win_user: name="{{ test_win_user_name }}" fullname="Test Ansible User" description="Test user account created by Ansible" - register: win_user_update_result_again - -- name: check full name and description result again - assert: - that: - - "win_user_update_result_again is not changed" - - "win_user_update_result_again.fullname == 'Test Ansible User'" - - "win_user_update_result_again.description == 'Test user account created by Ansible'" - -- name: test again with no options or changes - win_user: name="{{ test_win_user_name }}" - register: win_user_nochange_result - -- name: check no changes result - assert: - that: - - "win_user_nochange_result is not changed" - -- name: test again with query state - win_user: name="{{ test_win_user_name }}" state="query" - register: win_user_query_result - -- name: check query result - assert: - that: - - "win_user_query_result is not changed" - - "win_user_query_result.state == 'present'" - - "win_user_query_result.name == '{{ test_win_user_name }}'" - - "win_user_query_result.fullname == 'Test Ansible User'" - - "win_user_query_result.description == 'Test user account created by Ansible'" - - "win_user_query_result.path" - - "win_user_query_result.sid" - - "win_user_query_result.groups == []" - -- name: change user password - win_user: name="{{ test_win_user_name }}" password="{{ test_win_user_password2 }}" - register: win_user_password_result - -- name: check password change result - assert: - that: - - "win_user_password_result is changed" - -- name: change user password again to same value - win_user: name="{{ test_win_user_name }}" password="{{ test_win_user_password2 }}" - register: win_user_password_result_again - -- name: check password change result again - assert: - that: - - "win_user_password_result_again is not changed" - -- name: check update_password=on_create for existing user - win_user: name="{{ test_win_user_name }}" password="ThisP@ssW0rdShouldNotBeUsed" update_password=on_create - register: win_user_nopasschange_result - -- name: check password change with on_create flag result - assert: - that: - - "win_user_nopasschange_result is not changed" - -- name: set password expired flag - win_user: name="{{ test_win_user_name }}" password_expired=yes - register: win_user_password_expired_result - -- name: check password expired result - assert: - that: - - "win_user_password_expired_result is changed" - - "win_user_password_expired_result.password_expired" - -- name: set password when expired - win_user: name="{{ test_win_user_name }}" password={{ test_win_user_password2 }} update_password=always - register: win_user_can_set_password_on_expired - -- name: check set password on expired result - assert: - that: - - win_user_can_set_password_on_expired is changed - -- name: set password expired flag again - win_user: name="{{ test_win_user_name }}" password_expired=yes - register: win_user_password_expired_result - -- name: check password expired result - assert: - that: - - "win_user_password_expired_result is changed" - - "win_user_password_expired_result.password_expired" - -- name: clear password expired flag - win_user: name="{{ test_win_user_name }}" password_expired=no - register: win_user_clear_password_expired_result - -- name: check clear password expired result - assert: - that: - - "win_user_clear_password_expired_result is changed" - - "not win_user_clear_password_expired_result.password_expired" - -- name: set password never expires flag - win_user: name="{{ test_win_user_name }}" password_never_expires=yes - register: win_user_password_never_expires_result - -- name: check password never expires result - assert: - that: - - "win_user_password_never_expires_result is changed" - - "win_user_password_never_expires_result.password_never_expires" - -- name: clear password never expires flag - win_user: name="{{ test_win_user_name }}" password_never_expires=no - register: win_user_clear_password_never_expires_result - -- name: check clear password never expires result - assert: - that: - - "win_user_clear_password_never_expires_result is changed" - - "not win_user_clear_password_never_expires_result.password_never_expires" - -- name: set user cannot change password flag - win_user: name="{{ test_win_user_name }}" user_cannot_change_password=yes - register: win_user_cannot_change_password_result - -- name: check user cannot change password result - assert: - that: - - "win_user_cannot_change_password_result is changed" - - "win_user_cannot_change_password_result.user_cannot_change_password" - -- name: clear user cannot change password flag - win_user: name="{{ test_win_user_name }}" user_cannot_change_password=no - register: win_user_can_change_password_result - -- name: check clear user cannot change password result - assert: - that: - - "win_user_can_change_password_result is changed" - - "not win_user_can_change_password_result.user_cannot_change_password" - -- name: set account disabled flag - win_user: name="{{ test_win_user_name }}" account_disabled=true - register: win_user_account_disabled_result - -- name: check account disabled result - assert: - that: - - "win_user_account_disabled_result is changed" - - "win_user_account_disabled_result.account_disabled" - -- name: set password on disabled account - win_user: name="{{ test_win_user_name }}" password={{ test_win_user_password2 }} update_password=always - register: win_user_can_set_password_on_disabled - -- name: check set password on disabled result - assert: - that: - - win_user_can_set_password_on_disabled is changed - - win_user_can_set_password_on_disabled.account_disabled - -- name: clear account disabled flag - win_user: name="{{ test_win_user_name }}" account_disabled=false - register: win_user_clear_account_disabled_result - -- name: check clear account disabled result - assert: - that: - - "win_user_clear_account_disabled_result is changed" - - "not win_user_clear_account_disabled_result.account_disabled" - -- name: attempt to set account locked flag - win_user: name="{{ test_win_user_name }}" account_locked=yes - register: win_user_set_account_locked_result - ignore_errors: true - -- name: verify that attempting to set account locked flag fails - assert: - that: - - "win_user_set_account_locked_result is failed" - - "win_user_set_account_locked_result is not changed" - -- name: attempt to lockout test account - script: lockout_user.ps1 "{{ test_win_user_name }}" - -- name: get user to check if account locked flag is set - win_user: name="{{ test_win_user_name }}" state="query" - register: win_user_account_locked_result - -- name: clear account locked flag if set - win_user: name="{{ test_win_user_name }}" account_locked=no - register: win_user_clear_account_locked_result - when: "win_user_account_locked_result.account_locked" - -- name: check clear account lockout result if account was locked - assert: - that: - - "win_user_clear_account_locked_result is changed" - - "not win_user_clear_account_locked_result.account_locked" - when: "win_user_account_locked_result.account_locked" - -- name: assign test user to a group - win_user: name="{{ test_win_user_name }}" groups="Users" - register: win_user_replace_groups_result - -- name: check assign user to group result - assert: - that: - - "win_user_replace_groups_result is changed" - - "win_user_replace_groups_result.groups|length == 1" - - "win_user_replace_groups_result.groups[0]['name'] == 'Users'" - -- name: assign test user to the same group - win_user: - name: "{{ test_win_user_name }}" - groups: ["Users"] - register: win_user_replace_groups_again_result - -- name: check assign user to group again result - assert: - that: - - "win_user_replace_groups_again_result is not changed" - -- name: add user to another group - win_user: name="{{ test_win_user_name }}" groups="Power Users" groups_action="add" - register: win_user_add_groups_result - -- name: check add user to another group result - assert: - that: - - "win_user_add_groups_result is changed" - - "win_user_add_groups_result.groups|length == 2" - - "win_user_add_groups_result.groups[0]['name'] in ('Users', 'Power Users')" - - "win_user_add_groups_result.groups[1]['name'] in ('Users', 'Power Users')" - -- name: add user to another group again - win_user: - name: "{{ test_win_user_name }}" - groups: "Power Users" - groups_action: add - register: win_user_add_groups_again_result - -- name: check add user to another group again result - assert: - that: - - "win_user_add_groups_again_result is not changed" - -- name: remove user from a group - win_user: name="{{ test_win_user_name }}" groups="Users" groups_action="remove" - register: win_user_remove_groups_result - -- name: check remove user from group result - assert: - that: - - "win_user_remove_groups_result is changed" - - "win_user_remove_groups_result.groups|length == 1" - - "win_user_remove_groups_result.groups[0]['name'] == 'Power Users'" - -- name: remove user from a group again - win_user: - name: "{{ test_win_user_name }}" - groups: - - "Users" - groups_action: remove - register: win_user_remove_groups_again_result - -- name: check remove user from group again result - assert: - that: - - "win_user_remove_groups_again_result is not changed" - -- name: reassign test user to multiple groups - win_user: name="{{ test_win_user_name }}" groups="Users, Guests" groups_action="replace" - register: win_user_reassign_groups_result - -- name: check reassign user groups result - assert: - that: - - "win_user_reassign_groups_result is changed" - - "win_user_reassign_groups_result.groups|length == 2" - - "win_user_reassign_groups_result.groups[0]['name'] in ('Users', 'Guests')" - - "win_user_reassign_groups_result.groups[1]['name'] in ('Users', 'Guests')" - -- name: reassign test user to multiple groups again - win_user: - name: "{{ test_win_user_name }}" - groups: - - "Users" - - "Guests" - groups_action: replace - register: win_user_reassign_groups_again_result - -- name: check reassign user groups again result - assert: - that: - - "win_user_reassign_groups_again_result is not changed" - -- name: remove user from all groups - win_user: name="{{ test_win_user_name }}" groups="" - register: win_user_remove_all_groups_result - -- name: check remove user from all groups result - assert: - that: - - "win_user_remove_all_groups_result is changed" - - "win_user_remove_all_groups_result.groups|length == 0" - -- name: remove user from all groups again - win_user: - name: "{{ test_win_user_name }}" - groups: [] - register: win_user_remove_all_groups_again_result - -- name: check remove user from all groups again result - assert: - that: - - "win_user_remove_all_groups_again_result is not changed" - -- name: assign user to invalid group - win_user: name="{{ test_win_user_name }}" groups="Userz" - register: win_user_invalid_group_result - ignore_errors: true - -- name: check invalid group result - assert: - that: - - "win_user_invalid_group_result is failed" - - "win_user_invalid_group_result.msg" - - win_user_invalid_group_result.msg is match("group 'Userz' not found") - -- name: remove test user when finished - win_user: name="{{ test_win_user_name }}" state="absent" - register: win_user_final_remove_result - -- name: check final user removal result - assert: - that: - - "win_user_final_remove_result is changed" - - "win_user_final_remove_result.name" - - "win_user_final_remove_result.msg" - - "win_user_final_remove_result.state == 'absent'" - -- name: test removed user with query state - win_user: name="{{ test_win_user_name }}" state="query" - register: win_user_removed_query_result - -- name: check removed query result - assert: - that: - - "win_user_removed_query_result is not changed" - - "win_user_removed_query_result.name" - - "win_user_removed_query_result.msg" - - "win_user_removed_query_result.state == 'absent'" +--- +- name: create test group that has a network logon denied + win_group: + name: win_user-test + state: present + +- name: add test group to SeDenyNetworkLogonRight + win_user_right: + name: SeDenyNetworkLogonRight + users: + - win_user-test + action: add + +- block: + - name: run tests + include_tasks: tests.yml + + always: + - name: remove SeDenyNetworkLogonRight on test group + win_user_right: + name: SeDenyNetworkLogonRight + users: + - win_user-test + action: remove + + - name: remove test group + win_group: + name: win_user-test + state: absent + + - name: remove the test user + win_user: + name: '{{ test_win_user_name }}' + state: absent diff --git a/test/integration/targets/win_user/tasks/tests.yml b/test/integration/targets/win_user/tasks/tests.yml new file mode 100644 index 0000000000..1c36d32101 --- /dev/null +++ b/test/integration/targets/win_user/tasks/tests.yml @@ -0,0 +1,455 @@ +# test code for the win_user module +# (c) 2014, Chris Church + +# 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 . + +- name: remove existing test user if present + win_user: name="{{ test_win_user_name }}" state="absent" + register: win_user_remove_result + +- name: check user removal result + assert: + that: + - "win_user_remove_result.name" + - "win_user_remove_result.state == 'absent'" + +- name: try to remove test user again + win_user: name="{{ test_win_user_name }}" state="absent" + register: win_user_remove_result_again + +- name: check user removal result again + assert: + that: + - "win_user_remove_result_again is not changed" + - "win_user_remove_result_again.name" + - "win_user_remove_result_again.msg" + - "win_user_remove_result.state == 'absent'" + +- name: test missing user with query state + win_user: name="{{ test_win_user_name }}" state="query" + register: win_user_missing_query_result + +- name: check missing query result + assert: + that: + - "win_user_missing_query_result is not changed" + - "win_user_missing_query_result.name" + - "win_user_missing_query_result.msg" + - "win_user_missing_query_result.state == 'absent'" + +- name: test create user + win_user: name="{{ test_win_user_name }}" password="{{ test_win_user_password }}" fullname="Test User" description="Test user account" groups="Guests" + register: win_user_create_result + +- name: check user creation result + assert: + that: + - "win_user_create_result is changed" + - "win_user_create_result.name == '{{ test_win_user_name }}'" + - "win_user_create_result.fullname == 'Test User'" + - "win_user_create_result.description == 'Test user account'" + - "win_user_create_result.path" + - "win_user_create_result.state == 'present'" + +- name: update user full name and description + win_user: name="{{ test_win_user_name }}" fullname="Test Ansible User" description="Test user account created by Ansible" groups="" + register: win_user_update_result + +- name: check full name and description update result + assert: + that: + - "win_user_update_result is changed" + - "win_user_update_result.fullname == 'Test Ansible User'" + - "win_user_update_result.description == 'Test user account created by Ansible'" + +- name: update user full name and description again with same values + win_user: name="{{ test_win_user_name }}" fullname="Test Ansible User" description="Test user account created by Ansible" + register: win_user_update_result_again + +- name: check full name and description result again + assert: + that: + - "win_user_update_result_again is not changed" + - "win_user_update_result_again.fullname == 'Test Ansible User'" + - "win_user_update_result_again.description == 'Test user account created by Ansible'" + +- name: test again with no options or changes + win_user: name="{{ test_win_user_name }}" + register: win_user_nochange_result + +- name: check no changes result + assert: + that: + - "win_user_nochange_result is not changed" + +- name: test again with query state + win_user: name="{{ test_win_user_name }}" state="query" + register: win_user_query_result + +- name: check query result + assert: + that: + - "win_user_query_result is not changed" + - "win_user_query_result.state == 'present'" + - "win_user_query_result.name == '{{ test_win_user_name }}'" + - "win_user_query_result.fullname == 'Test Ansible User'" + - "win_user_query_result.description == 'Test user account created by Ansible'" + - "win_user_query_result.path" + - "win_user_query_result.sid" + - "win_user_query_result.groups == []" + +- name: change user password + win_user: name="{{ test_win_user_name }}" password="{{ test_win_user_password2 }}" + register: win_user_password_result + +- name: check password change result + assert: + that: + - "win_user_password_result is changed" + +- name: change user password again to same value + win_user: name="{{ test_win_user_name }}" password="{{ test_win_user_password2 }}" + register: win_user_password_result_again + +- name: check password change result again + assert: + that: + - "win_user_password_result_again is not changed" + +- name: check update_password=on_create for existing user + win_user: name="{{ test_win_user_name }}" password="ThisP@ssW0rdShouldNotBeUsed" update_password=on_create + register: win_user_nopasschange_result + +- name: check password change with on_create flag result + assert: + that: + - "win_user_nopasschange_result is not changed" + +- name: set password expired flag + win_user: name="{{ test_win_user_name }}" password_expired=yes + register: win_user_password_expired_result + +- name: check password expired result + assert: + that: + - "win_user_password_expired_result is changed" + - "win_user_password_expired_result.password_expired" + +- name: set password when expired + win_user: name="{{ test_win_user_name }}" password={{ test_win_user_password2 }} update_password=always + register: win_user_can_set_password_on_expired + +- name: check set password on expired result + assert: + that: + - win_user_can_set_password_on_expired is changed + +- name: set password expired flag again + win_user: name="{{ test_win_user_name }}" password_expired=yes + register: win_user_password_expired_result + +- name: check password expired result + assert: + that: + - "win_user_password_expired_result is changed" + - "win_user_password_expired_result.password_expired" + +- name: clear password expired flag + win_user: name="{{ test_win_user_name }}" password_expired=no + register: win_user_clear_password_expired_result + +- name: check clear password expired result + assert: + that: + - "win_user_clear_password_expired_result is changed" + - "not win_user_clear_password_expired_result.password_expired" + +- name: set password never expires flag + win_user: name="{{ test_win_user_name }}" password_never_expires=yes + register: win_user_password_never_expires_result + +- name: check password never expires result + assert: + that: + - "win_user_password_never_expires_result is changed" + - "win_user_password_never_expires_result.password_never_expires" + +- name: clear password never expires flag + win_user: name="{{ test_win_user_name }}" password_never_expires=no + register: win_user_clear_password_never_expires_result + +- name: check clear password never expires result + assert: + that: + - "win_user_clear_password_never_expires_result is changed" + - "not win_user_clear_password_never_expires_result.password_never_expires" + +- name: set user cannot change password flag + win_user: name="{{ test_win_user_name }}" user_cannot_change_password=yes + register: win_user_cannot_change_password_result + +- name: check user cannot change password result + assert: + that: + - "win_user_cannot_change_password_result is changed" + - "win_user_cannot_change_password_result.user_cannot_change_password" + +- name: clear user cannot change password flag + win_user: name="{{ test_win_user_name }}" user_cannot_change_password=no + register: win_user_can_change_password_result + +- name: check clear user cannot change password result + assert: + that: + - "win_user_can_change_password_result is changed" + - "not win_user_can_change_password_result.user_cannot_change_password" + +- name: set account disabled flag + win_user: name="{{ test_win_user_name }}" account_disabled=true + register: win_user_account_disabled_result + +- name: check account disabled result + assert: + that: + - "win_user_account_disabled_result is changed" + - "win_user_account_disabled_result.account_disabled" + +- name: set password on disabled account + win_user: name="{{ test_win_user_name }}" password={{ test_win_user_password2 }} update_password=always + register: win_user_can_set_password_on_disabled + +- name: check set password on disabled result + assert: + that: + - win_user_can_set_password_on_disabled is changed + - win_user_can_set_password_on_disabled.account_disabled + +- name: clear account disabled flag + win_user: name="{{ test_win_user_name }}" account_disabled=false + register: win_user_clear_account_disabled_result + +- name: check clear account disabled result + assert: + that: + - "win_user_clear_account_disabled_result is changed" + - "not win_user_clear_account_disabled_result.account_disabled" + +- name: attempt to set account locked flag + win_user: name="{{ test_win_user_name }}" account_locked=yes + register: win_user_set_account_locked_result + ignore_errors: true + +- name: verify that attempting to set account locked flag fails + assert: + that: + - "win_user_set_account_locked_result is failed" + - "win_user_set_account_locked_result is not changed" + +- name: attempt to lockout test account + script: lockout_user.ps1 "{{ test_win_user_name }}" + +- name: get user to check if account locked flag is set + win_user: name="{{ test_win_user_name }}" state="query" + register: win_user_account_locked_result + +- name: clear account locked flag if set + win_user: name="{{ test_win_user_name }}" account_locked=no + register: win_user_clear_account_locked_result + when: "win_user_account_locked_result.account_locked" + +- name: check clear account lockout result if account was locked + assert: + that: + - "win_user_clear_account_locked_result is changed" + - "not win_user_clear_account_locked_result.account_locked" + when: "win_user_account_locked_result.account_locked" + +- name: assign test user to a group + win_user: name="{{ test_win_user_name }}" groups="Users" + register: win_user_replace_groups_result + +- name: check assign user to group result + assert: + that: + - "win_user_replace_groups_result is changed" + - "win_user_replace_groups_result.groups|length == 1" + - "win_user_replace_groups_result.groups[0]['name'] == 'Users'" + +- name: assign test user to the same group + win_user: + name: "{{ test_win_user_name }}" + groups: ["Users"] + register: win_user_replace_groups_again_result + +- name: check assign user to group again result + assert: + that: + - "win_user_replace_groups_again_result is not changed" + +- name: add user to another group + win_user: name="{{ test_win_user_name }}" groups="Power Users" groups_action="add" + register: win_user_add_groups_result + +- name: check add user to another group result + assert: + that: + - "win_user_add_groups_result is changed" + - "win_user_add_groups_result.groups|length == 2" + - "win_user_add_groups_result.groups[0]['name'] in ('Users', 'Power Users')" + - "win_user_add_groups_result.groups[1]['name'] in ('Users', 'Power Users')" + +- name: add user to another group again + win_user: + name: "{{ test_win_user_name }}" + groups: "Power Users" + groups_action: add + register: win_user_add_groups_again_result + +- name: check add user to another group again result + assert: + that: + - "win_user_add_groups_again_result is not changed" + +- name: remove user from a group + win_user: name="{{ test_win_user_name }}" groups="Users" groups_action="remove" + register: win_user_remove_groups_result + +- name: check remove user from group result + assert: + that: + - "win_user_remove_groups_result is changed" + - "win_user_remove_groups_result.groups|length == 1" + - "win_user_remove_groups_result.groups[0]['name'] == 'Power Users'" + +- name: remove user from a group again + win_user: + name: "{{ test_win_user_name }}" + groups: + - "Users" + groups_action: remove + register: win_user_remove_groups_again_result + +- name: check remove user from group again result + assert: + that: + - "win_user_remove_groups_again_result is not changed" + +- name: reassign test user to multiple groups + win_user: name="{{ test_win_user_name }}" groups="Users, Guests" groups_action="replace" + register: win_user_reassign_groups_result + +- name: check reassign user groups result + assert: + that: + - "win_user_reassign_groups_result is changed" + - "win_user_reassign_groups_result.groups|length == 2" + - "win_user_reassign_groups_result.groups[0]['name'] in ('Users', 'Guests')" + - "win_user_reassign_groups_result.groups[1]['name'] in ('Users', 'Guests')" + +- name: reassign test user to multiple groups again + win_user: + name: "{{ test_win_user_name }}" + groups: + - "Users" + - "Guests" + groups_action: replace + register: win_user_reassign_groups_again_result + +- name: check reassign user groups again result + assert: + that: + - "win_user_reassign_groups_again_result is not changed" + +- name: remove user from all groups + win_user: name="{{ test_win_user_name }}" groups="" + register: win_user_remove_all_groups_result + +- name: check remove user from all groups result + assert: + that: + - "win_user_remove_all_groups_result is changed" + - "win_user_remove_all_groups_result.groups|length == 0" + +- name: remove user from all groups again + win_user: + name: "{{ test_win_user_name }}" + groups: [] + register: win_user_remove_all_groups_again_result + +- name: check remove user from all groups again result + assert: + that: + - "win_user_remove_all_groups_again_result is not changed" + +- name: assign user to invalid group + win_user: name="{{ test_win_user_name }}" groups="Userz" + register: win_user_invalid_group_result + ignore_errors: true + +- name: check invalid group result + assert: + that: + - "win_user_invalid_group_result is failed" + - "win_user_invalid_group_result.msg" + - win_user_invalid_group_result.msg is match("group 'Userz' not found") + +- name: remove test user when finished + win_user: name="{{ test_win_user_name }}" state="absent" + register: win_user_final_remove_result + +- name: check final user removal result + assert: + that: + - "win_user_final_remove_result is changed" + - "win_user_final_remove_result.name" + - "win_user_final_remove_result.msg" + - "win_user_final_remove_result.state == 'absent'" + +- name: test removed user with query state + win_user: name="{{ test_win_user_name }}" state="query" + register: win_user_removed_query_result + +- name: check removed query result + assert: + that: + - "win_user_removed_query_result is not changed" + - "win_user_removed_query_result.name" + - "win_user_removed_query_result.msg" + - "win_user_removed_query_result.state == 'absent'" + +# Tests the Test-Credential path where LogonUser fails if the user does not +# have the right for a network logon +- name: add new user that has the right SeDenyNetworkLogonRight + win_user: + name: '{{ test_win_user_name }}' + password: '{{ test_win_user_password2 }}' + state: present + groups: + - win_user-test + +- name: add new user that has the right SeDenyNetworkLogonRight (idempotent) + win_user: + name: '{{ test_win_user_name }}' + password: '{{ test_win_user_password2 }}' + state: present + groups: + - win_user-test + register: deny_network_idempotent + +- name: assert add new user that has the right SeDenyNetworkLogonRight (idempotent) + assert: + that: + - not deny_network_idempotent is changed