win_domain_object_info: new module (#67450)
* win_domain_object_info: new module * Added basic integration tests
This commit is contained in:
parent
be26f4916f
commit
38f26ffcc7
5 changed files with 564 additions and 0 deletions
271
lib/ansible/modules/windows/win_domain_object_info.ps1
Normal file
271
lib/ansible/modules/windows/win_domain_object_info.ps1
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
#!powershell
|
||||||
|
|
||||||
|
# Copyright: (c) 2020, Ansible Project
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
#AnsibleRequires -CSharpUtil Ansible.Basic
|
||||||
|
#Requires -Module Ansible.ModuleUtils.AddType
|
||||||
|
|
||||||
|
$spec = @{
|
||||||
|
options = @{
|
||||||
|
domain_password = @{ type = 'str'; no_log = $true }
|
||||||
|
domain_server = @{ type = 'str' }
|
||||||
|
domain_username = @{ type = 'str' }
|
||||||
|
filter = @{ type = 'str' }
|
||||||
|
identity = @{ type = 'str' }
|
||||||
|
include_deleted = @{ type = 'bool'; default = $false }
|
||||||
|
ldap_filter = @{ type = 'str' }
|
||||||
|
properties = @{ type = 'list'; elements = 'str' }
|
||||||
|
search_base = @{ type = 'str' }
|
||||||
|
search_scope = @{ type = 'str'; choices = @('base', 'one_level', 'subtree') }
|
||||||
|
}
|
||||||
|
supports_check_mode = $true
|
||||||
|
mutually_exclusive = @(
|
||||||
|
@('filter', 'identity', 'ldap_filter'),
|
||||||
|
@('identity', 'search_base'),
|
||||||
|
@('identity', 'search_scope')
|
||||||
|
)
|
||||||
|
required_one_of = @(
|
||||||
|
,@('filter', 'identity', 'ldap_filter')
|
||||||
|
)
|
||||||
|
required_together = @(,@('domain_username', 'domain_password'))
|
||||||
|
}
|
||||||
|
|
||||||
|
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
|
||||||
|
|
||||||
|
$module.Result.objects = @() # Always ensure this is returned even in a failure.
|
||||||
|
|
||||||
|
$domainServer = $module.Params.domain_server
|
||||||
|
$domainPassword = $module.Params.domain_password
|
||||||
|
$domainUsername = $module.Params.domain_username
|
||||||
|
$filter = $module.Params.filter
|
||||||
|
$identity = $module.Params.identity
|
||||||
|
$includeDeleted = $module.Params.include_deleted
|
||||||
|
$ldapFilter = $module.Params.ldap_filter
|
||||||
|
$properties = $module.Params.properties
|
||||||
|
$searchBase = $module.Params.search_base
|
||||||
|
$searchScope = $module.Params.search_scope
|
||||||
|
|
||||||
|
$credential = $null
|
||||||
|
if ($domainUsername) {
|
||||||
|
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList @(
|
||||||
|
$domainUsername,
|
||||||
|
(ConvertTo-SecureString -AsPlainText -Force -String $domainPassword)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Add-CSharpType -References @'
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ansible.WinDomainObjectInfo
|
||||||
|
{
|
||||||
|
[Flags]
|
||||||
|
public enum UserAccountControl : int
|
||||||
|
{
|
||||||
|
ADS_UF_SCRIPT = 0x00000001,
|
||||||
|
ADS_UF_ACCOUNTDISABLE = 0x00000002,
|
||||||
|
ADS_UF_HOMEDIR_REQUIRED = 0x00000008,
|
||||||
|
ADS_UF_LOCKOUT = 0x00000010,
|
||||||
|
ADS_UF_PASSWD_NOTREQD = 0x00000020,
|
||||||
|
ADS_UF_PASSWD_CANT_CHANGE = 0x00000040,
|
||||||
|
ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED = 0x00000080,
|
||||||
|
ADS_UF_TEMP_DUPLICATE_ACCOUNT = 0x00000100,
|
||||||
|
ADS_UF_NORMAL_ACCOUNT = 0x00000200,
|
||||||
|
ADS_UF_INTERDOMAIN_TRUST_ACCOUNT = 0x00000800,
|
||||||
|
ADS_UF_WORKSTATION_TRUST_ACCOUNT = 0x00001000,
|
||||||
|
ADS_UF_SERVER_TRUST_ACCOUNT = 0x00002000,
|
||||||
|
ADS_UF_DONT_EXPIRE_PASSWD = 0x00010000,
|
||||||
|
ADS_UF_MNS_LOGON_ACCOUNT = 0x00020000,
|
||||||
|
ADS_UF_SMARTCARD_REQUIRED = 0x00040000,
|
||||||
|
ADS_UF_TRUSTED_FOR_DELEGATION = 0x00080000,
|
||||||
|
ADS_UF_NOT_DELEGATED = 0x00100000,
|
||||||
|
ADS_UF_USE_DES_KEY_ONLY = 0x00200000,
|
||||||
|
ADS_UF_DONT_REQUIRE_PREAUTH = 0x00400000,
|
||||||
|
ADS_UF_PASSWORD_EXPIRED = 0x00800000,
|
||||||
|
ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = 0x01000000,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum sAMAccountType : int
|
||||||
|
{
|
||||||
|
SAM_DOMAIN_OBJECT = 0x00000000,
|
||||||
|
SAM_GROUP_OBJECT = 0x10000000,
|
||||||
|
SAM_NON_SECURITY_GROUP_OBJECT = 0x10000001,
|
||||||
|
SAM_ALIAS_OBJECT = 0x20000000,
|
||||||
|
SAM_NON_SECURITY_ALIAS_OBJECT = 0x20000001,
|
||||||
|
SAM_USER_OBJECT = 0x30000000,
|
||||||
|
SAM_NORMAL_USER_ACCOUNT = 0x30000000,
|
||||||
|
SAM_MACHINE_ACCOUNT = 0x30000001,
|
||||||
|
SAM_TRUST_ACCOUNT = 0x30000002,
|
||||||
|
SAM_APP_BASIC_GROUP = 0x40000000,
|
||||||
|
SAM_APP_QUERY_GROUP = 0x40000001,
|
||||||
|
SAM_ACCOUNT_TYPE_MAX = 0x7fffffff,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'@
|
||||||
|
|
||||||
|
Function ConvertTo-OutputValue {
|
||||||
|
[CmdletBinding()]
|
||||||
|
Param (
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[AllowNull()]
|
||||||
|
[Object]
|
||||||
|
$InputObject
|
||||||
|
)
|
||||||
|
|
||||||
|
if ($InputObject -is [System.Security.Principal.SecurityIdentifier]) {
|
||||||
|
# Syntax: SID - Only serialize the SID as a string and not the other metadata properties.
|
||||||
|
$sidInfo = @{
|
||||||
|
Sid = $InputObject.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
# Try and map the SID to the account name, this may fail if the SID is invalid or not mappable.
|
||||||
|
try {
|
||||||
|
$sidInfo.Name = $InputObject.Translate([System.Security.Principal.NTAccount]).Value
|
||||||
|
} catch [System.Security.Principal.IdentityNotMappedException] {
|
||||||
|
$sidInfo.Name = $null
|
||||||
|
}
|
||||||
|
|
||||||
|
$sidInfo
|
||||||
|
} elseif ($InputObject -is [Byte[]]) {
|
||||||
|
# Syntax: Octet String - By default will serialize as a list of decimal values per byte, instead return a
|
||||||
|
# Base64 string as Ansible can easily parse that.
|
||||||
|
[System.Convert]::ToBase64String($InputObject)
|
||||||
|
} elseif ($InputObject -is [DateTime]) {
|
||||||
|
# Syntax: UTC Coded Time - .NET DateTimes serialized as in the form "Date(FILETIME)" which isn't easily
|
||||||
|
# parsable by Ansible, instead return as an ISO 8601 string in the UTC timezone.
|
||||||
|
[TimeZoneInfo]::ConvertTimeToUtc($InputObject).ToString("o")
|
||||||
|
} elseif ($InputObject -is [System.Security.AccessControl.ObjectSecurity]) {
|
||||||
|
# Complex object which isn't easily serializable. Instead we should just return the SDDL string. If a user
|
||||||
|
# needs to parse this then they really need to reprocess the SDDL string and process their results on another
|
||||||
|
# win_shell task.
|
||||||
|
$InputObject.GetSecurityDescriptorSddlForm(([System.Security.AccessControl.AccessControlSections]::All))
|
||||||
|
} else {
|
||||||
|
# Syntax: (All Others) - The default serialization handling of other syntaxes are fine, don't do anything.
|
||||||
|
$InputObject
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
Calling Get-ADObject that returns multiple objects with -Properties * will only return the properties that were set on
|
||||||
|
the first found object. To counter this problem we will first call Get-ADObject to list all the objects that match the
|
||||||
|
filter specified then get the properties on each object.
|
||||||
|
#>
|
||||||
|
|
||||||
|
$commonParams = @{
|
||||||
|
IncludeDeletedObjects = $includeDeleted
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($credential) {
|
||||||
|
$commonParams.Credential = $credential
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($domainServer) {
|
||||||
|
$commonParams.Server = $domainServer
|
||||||
|
}
|
||||||
|
|
||||||
|
# First get the IDs for all the AD objects that match the filter specified.
|
||||||
|
$getParams = @{
|
||||||
|
Properties = @('DistinguishedName', 'ObjectGUID')
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($filter) {
|
||||||
|
$getParams.Filter = $filter
|
||||||
|
} elseif ($identity) {
|
||||||
|
$getParams.Identity = $identity
|
||||||
|
} elseif ($ldapFilter) {
|
||||||
|
$getParams.LDAPFilter = $ldapFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
# Explicit check on $null as an empty string is different from not being set.
|
||||||
|
if ($null -ne $searchBase) {
|
||||||
|
$getParams.SearchBase = $searchbase
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($searchScope) {
|
||||||
|
$getParams.SearchScope = switch($searchScope) {
|
||||||
|
base { 'Base' }
|
||||||
|
one_level { 'OneLevel' }
|
||||||
|
subtree { 'Subtree' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
# We run this in a custom PowerShell pipeline so that users of this module can't use any of the variables defined
|
||||||
|
# above in their filter. While the cmdlet won't execute sub expressions we don't want anyone implicitly relying on
|
||||||
|
# a defined variable in this module in case we ever change the name or remove it.
|
||||||
|
$ps = [PowerShell]::Create()
|
||||||
|
$null = $ps.AddCommand('Get-ADObject').AddParameters($commonParams).AddParameters($getParams)
|
||||||
|
$null = $ps.AddCommand('Select-Object').AddParameter('Property', @('DistinguishedName', 'ObjectGUID'))
|
||||||
|
|
||||||
|
$foundGuids = @($ps.Invoke())
|
||||||
|
} catch {
|
||||||
|
# Because we ran in a pipeline we can't catch ADIdentityNotFoundException. Instead just get the base exception and
|
||||||
|
# do the error checking on that.
|
||||||
|
if ($_.Exception.GetBaseException() -is [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]) {
|
||||||
|
$foundGuids = @()
|
||||||
|
} else {
|
||||||
|
# The exception is from the .Invoke() call, compare on the InnerException which was what was actually raised by
|
||||||
|
# the pipeline.
|
||||||
|
$innerException = $_.Exception.InnerException.InnerException
|
||||||
|
if ($innerException -is [Microsoft.ActiveDirectory.Management.ADServerDownException]) {
|
||||||
|
# Point users in the direction of the double hop problem as that is what is typically the cause of this.
|
||||||
|
$msg = "Failed to contact the AD server, this could be caused by the double hop problem over WinRM. "
|
||||||
|
$msg += "Try using the module with auth as Kerberos with credential delegation or CredSSP, become, or "
|
||||||
|
$msg += "defining the domain_username and domain_password module parameters."
|
||||||
|
$module.FailJson($msg, $innerException)
|
||||||
|
} else {
|
||||||
|
throw $innerException
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$getParams = @{}
|
||||||
|
if ($properties) {
|
||||||
|
$getParams.Properties = $properties
|
||||||
|
}
|
||||||
|
$module.Result.objects = @(foreach ($adId in $foundGuids) {
|
||||||
|
try {
|
||||||
|
$adObject = Get-ADObject @commonParams @getParams -Identity $adId.ObjectGUID
|
||||||
|
} catch {
|
||||||
|
$msg = "Failed to retrieve properties for AD Object '$($adId.DistinguishedName)': $($_.Exception.Message)"
|
||||||
|
$module.Warn($msg)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
$propertyNames = $adObject.PropertyNames
|
||||||
|
$propertyNames += ($properties | Where-Object { $_ -ne '*' })
|
||||||
|
|
||||||
|
# Now process each property to an easy to represent string
|
||||||
|
$filteredObject = [Ordered]@{}
|
||||||
|
foreach ($name in ($propertyNames | Sort-Object)) {
|
||||||
|
# In the case of explicit properties that were asked for but weren't set, Get-ADObject won't actually return
|
||||||
|
# the property so this is a defensive check against that scenario.
|
||||||
|
if (-not $adObject.PSObject.Properties.Name.Contains($name)) {
|
||||||
|
$filteredObject.$name = $null
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = $adObject.$name
|
||||||
|
if ($value -is [Microsoft.ActiveDirectory.Management.ADPropertyValueCollection]) {
|
||||||
|
$value = foreach ($v in $value) {
|
||||||
|
ConvertTo-OutputValue -InputObject $v
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$value = ConvertTo-OutputValue -InputObject $value
|
||||||
|
}
|
||||||
|
$filteredObject.$name = $value
|
||||||
|
|
||||||
|
# For these 2 properties, add an _AnsibleFlags attribute which contains the enum strings that are set.
|
||||||
|
if ($name -eq 'sAMAccountType') {
|
||||||
|
$enumValue = [Ansible.WinDomainObjectInfo.sAMAccountType]$value
|
||||||
|
$filteredObject.'sAMAccountType_AnsibleFlags' = $enumValue.ToString() -split ', '
|
||||||
|
} elseif ($name -eq 'userAccountControl') {
|
||||||
|
$enumValue = [Ansible.WinDomainObjectInfo.UserAccountControl]$value
|
||||||
|
$filteredObject.'userAccountControl_AnsibleFlags' = $enumValue.ToString() -split ', '
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$filteredObject
|
||||||
|
})
|
||||||
|
|
||||||
|
$module.ExitJson()
|
162
lib/ansible/modules/windows/win_domain_object_info.py
Normal file
162
lib/ansible/modules/windows/win_domain_object_info.py
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright: (c) 2020, Ansible Project
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||||
|
'status': ['preview'],
|
||||||
|
'supported_by': 'community'}
|
||||||
|
|
||||||
|
DOCUMENTATION = r'''
|
||||||
|
---
|
||||||
|
module: win_domain_object_info
|
||||||
|
version_added: '2.10'
|
||||||
|
short_description: Gather information an Active Directory object
|
||||||
|
description:
|
||||||
|
- Gather information about multiple Active Directory object(s).
|
||||||
|
options:
|
||||||
|
domain_password:
|
||||||
|
description:
|
||||||
|
- The password for C(domain_username).
|
||||||
|
type: str
|
||||||
|
domain_server:
|
||||||
|
description:
|
||||||
|
- Specified the Active Directory Domain Services instance to connect to.
|
||||||
|
- Can be in the form of an FQDN or NetBIOS name.
|
||||||
|
- If not specified then the value is based on the default domain of the computer running PowerShell.
|
||||||
|
type: str
|
||||||
|
domain_username:
|
||||||
|
description:
|
||||||
|
- The username to use when interacting with AD.
|
||||||
|
- If this is not set then the user that is used for authentication will be the connection user.
|
||||||
|
- Ansible will be unable to use the connection user unless auth is Kerberos with credential delegation or CredSSP,
|
||||||
|
or become is used on the task.
|
||||||
|
type: str
|
||||||
|
filter:
|
||||||
|
description:
|
||||||
|
- Specifies a query string using the PowerShell Expression Language syntax.
|
||||||
|
- This follows the same rules and formatting as the C(-Filter) parameter for the PowerShell AD cmdlets exception
|
||||||
|
there is no variable substitutions.
|
||||||
|
- This is mutually exclusive with I(identity) and I(ldap_filter).
|
||||||
|
type: str
|
||||||
|
identity:
|
||||||
|
description:
|
||||||
|
- Specifies a single Active Directory object by its distinguished name or its object GUID.
|
||||||
|
- This is mutually exclusive with I(filter) and I(ldap_filter).
|
||||||
|
- This cannot be used with either the I(search_base) or I(search_scope) options.
|
||||||
|
type: str
|
||||||
|
include_deleted:
|
||||||
|
description:
|
||||||
|
- Also search for deleted Active Directory objects.
|
||||||
|
default: no
|
||||||
|
type: bool
|
||||||
|
ldap_filter:
|
||||||
|
description:
|
||||||
|
- Like I(filter) but this is a tradiitional LDAP query string to filter the objects to return.
|
||||||
|
- This is mutually exclusive with I(filter) and I(identity).
|
||||||
|
type: str
|
||||||
|
properties:
|
||||||
|
description:
|
||||||
|
- A list of properties to return.
|
||||||
|
- If a property is C(*), all properties that have a set value on the AD object will be returned.
|
||||||
|
- If a property is valid on the object but not set, it is only returned if defined explicitly in this option list.
|
||||||
|
- The properties C(DistinguishedName), C(Name), C(ObjectClass), and C(ObjectGUID) are always returned.
|
||||||
|
- Specifying multiple properties can have a performance impact, it is best to only return what is needed.
|
||||||
|
- If an invalid property is specified then the module will display a warning for each object it is invalid on.
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
|
search_base:
|
||||||
|
description:
|
||||||
|
- Specify the Active Directory path to search for objects in.
|
||||||
|
- This cannot be set with I(identity).
|
||||||
|
- By default the search base is the default naming context of the target AD instance which is the DN returned by
|
||||||
|
"(Get-ADRootDSE).defaultNamingContext".
|
||||||
|
type: str
|
||||||
|
search_scope:
|
||||||
|
description:
|
||||||
|
- Specify the scope of when searching for an object in the C(search_base).
|
||||||
|
- C(base) will limit the search to the base object so the maximum number of objects returned is always one. This
|
||||||
|
will not search any objects inside a container..
|
||||||
|
- C(one_level) will search the current path and any immediate objects in that path.
|
||||||
|
- C(subtree) will search the current path and all objects of that path recursively.
|
||||||
|
- This cannot be set with I(identity).
|
||||||
|
choices:
|
||||||
|
- base
|
||||||
|
- one_level
|
||||||
|
- subtree
|
||||||
|
type: str
|
||||||
|
notes:
|
||||||
|
- The C(sAMAccountType_AnsibleFlags) and C(userAccountControl_AnsibleFlags) return property is something set by the
|
||||||
|
module itself as an easy way to view what those flags represent. These properties cannot be used as part of the
|
||||||
|
I(filter) or I(ldap_filter) and are automatically added if those properties were requested.
|
||||||
|
author:
|
||||||
|
- Jordan Borean (@jborean93)
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = r'''
|
||||||
|
- name: Get all properties for the specified account using its DistinguishedName
|
||||||
|
win_domain_object_info:
|
||||||
|
identity: CN=Username,CN=Users,DC=domain,DC=com
|
||||||
|
properties: '*'
|
||||||
|
|
||||||
|
- name: Get the SID for all user accounts as a filter
|
||||||
|
win_domain_object_info:
|
||||||
|
filter: ObjectClass -eq 'user' -and objectCategory -eq 'Person'
|
||||||
|
properties:
|
||||||
|
- objectSid
|
||||||
|
|
||||||
|
- name: Get the SID for all user accounts as a LDAP filter
|
||||||
|
win_domain_object_info:
|
||||||
|
ldap_filter: (&(objectClass=user)(objectCategory=Person))
|
||||||
|
properties:
|
||||||
|
- objectSid
|
||||||
|
|
||||||
|
- name: Search all computer accounts in a specific path that were added after February 1st
|
||||||
|
win_domain_object_info:
|
||||||
|
filter: objectClass -eq 'computer' -and whenCreated -gt '20200201000000.0Z'
|
||||||
|
properties: '*'
|
||||||
|
search_scope: one_level
|
||||||
|
search_base: CN=Computers,DC=domain,DC=com
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = r'''
|
||||||
|
objects:
|
||||||
|
description:
|
||||||
|
- A list of dictionaries that are the Active Directory objects found and the properties requested.
|
||||||
|
- The dict's keys are the property name and the value is the value for the property.
|
||||||
|
- All date properties are return in the ISO 8601 format in the UTC timezone.
|
||||||
|
- All SID properties are returned as a dict with the keys C(Sid) as the SID string and C(Name) as the translated SID
|
||||||
|
account name.
|
||||||
|
- All byte properties are returned as a base64 string.
|
||||||
|
- All security descriptor properties are returned as the SDDL string of that descriptor.
|
||||||
|
- The properties C(DistinguishedName), C(Name), C(ObjectClass), and C(ObjectGUID) are always returned.
|
||||||
|
returned: always
|
||||||
|
type: list
|
||||||
|
elements: dict
|
||||||
|
sample: |
|
||||||
|
[{
|
||||||
|
"accountExpires": 0,
|
||||||
|
"adminCount": 1,
|
||||||
|
"CanonicalName": "domain.com/Users/Administrator",
|
||||||
|
"CN": "Administrator",
|
||||||
|
"Created": "2020-01-13T09:03:22.0000000Z",
|
||||||
|
"Description": "Built-in account for administering computer/domain",
|
||||||
|
"DisplayName": null,
|
||||||
|
"DistinguishedName": "CN=Administrator,CN=Users,DC=domain,DC=com",
|
||||||
|
"memberOf": [
|
||||||
|
"CN=Group Policy Creator Owners,CN=Users,DC=domain,DC=com",
|
||||||
|
"CN=Domain Admins",CN=Users,DC=domain,DC=com"
|
||||||
|
],
|
||||||
|
"Name": "Administrator",
|
||||||
|
"nTSecurityDescriptor": "O:DAG:DAD:PAI(A;;LCRPLORC;;;AU)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;SY)(A;;CCDCLCSWRPWPLOCRSDRCWDWO;;;BA)",
|
||||||
|
"ObjectCategory": "CN=Person,CN=Schema,CN=Configuration,DC=domain,DC=com",
|
||||||
|
"ObjectClass": "user",
|
||||||
|
"ObjectGUID": "c8c6569e-4688-4f3c-8462-afc4ff60817b",
|
||||||
|
"objectSid": {
|
||||||
|
"Sid": "S-1-5-21-2959096244-3298113601-420842770-500",
|
||||||
|
"Name": "DOMAIN\Administrator"
|
||||||
|
},
|
||||||
|
"sAMAccountName": "Administrator",
|
||||||
|
}]
|
||||||
|
'''
|
1
test/integration/targets/win_domain_object_info/aliases
Normal file
1
test/integration/targets/win_domain_object_info/aliases
Normal file
|
@ -0,0 +1 @@
|
||||||
|
unsupported
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
- name: remove test domain user
|
||||||
|
win_domain_user:
|
||||||
|
name: '{{ test_user.distinguished_name }}'
|
||||||
|
state: absent
|
125
test/integration/targets/win_domain_object_info/tasks/main.yml
Normal file
125
test/integration/targets/win_domain_object_info/tasks/main.yml
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
# These tests can't run in CI, this is really just a basic smoke tests for local runs.
|
||||||
|
---
|
||||||
|
- name: assert better error message on auth failure
|
||||||
|
win_domain_object_info:
|
||||||
|
identity: id
|
||||||
|
register: fail_auth
|
||||||
|
failed_when: '"Failed to contact the AD server, this could be caused by the double hop problem" not in fail_auth.msg'
|
||||||
|
vars:
|
||||||
|
ansible_winrm_transport: ntlm
|
||||||
|
ansible_psrp_auth: ntlm
|
||||||
|
|
||||||
|
- name: create test ad user
|
||||||
|
win_domain_user:
|
||||||
|
name: Ansible Test
|
||||||
|
firstname: Ansible
|
||||||
|
surname: Test
|
||||||
|
company: Contoso R Us
|
||||||
|
password: Password01
|
||||||
|
state: present
|
||||||
|
password_never_expires: yes
|
||||||
|
groups:
|
||||||
|
- Domain Users
|
||||||
|
enabled: false
|
||||||
|
register: test_user
|
||||||
|
notify: remove test domain user
|
||||||
|
|
||||||
|
- name: set a binary attribute and return other useful info missing from above
|
||||||
|
win_shell: |
|
||||||
|
Set-ADUser -Identity '{{ test_user.sid }}' -Replace @{ audio = @([byte[]]@(1, 2, 3, 4), [byte[]]@(5, 6, 7, 8)) }
|
||||||
|
|
||||||
|
$user = Get-ADUser -Identity '{{ test_user.sid }}' -Properties modifyTimestamp, ObjectGUID
|
||||||
|
|
||||||
|
[TimeZoneInfo]::ConvertTimeToUtc($user.modifyTimestamp).ToString('o')
|
||||||
|
$user.ObjectGUID.ToString()
|
||||||
|
([System.Security.Principal.SecurityIdentifier]'{{ test_user.sid }}').Translate([System.Security.Principal.NTAccount]).Value
|
||||||
|
register: test_user_extras
|
||||||
|
|
||||||
|
- name: set other test info for easier access
|
||||||
|
set_fact:
|
||||||
|
test_user_mod_date: '{{ test_user_extras.stdout_lines[0] }}'
|
||||||
|
test_user_id: '{{ test_user_extras.stdout_lines[1] }}'
|
||||||
|
test_user_name: '{{ test_user_extras.stdout_lines[2] }}'
|
||||||
|
|
||||||
|
- name: get properties for single user by DN
|
||||||
|
win_domain_object_info:
|
||||||
|
identity: '{{ test_user.distinguished_name }}'
|
||||||
|
register: by_identity
|
||||||
|
check_mode: yes # Just verifies it runs in check mode
|
||||||
|
|
||||||
|
- name: assert get properties for single user by DN
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not by_identity is changed
|
||||||
|
- by_identity.objects | length == 1
|
||||||
|
- by_identity.objects[0].keys() | list | length == 4
|
||||||
|
- by_identity.objects[0].DistinguishedName == test_user.distinguished_name
|
||||||
|
- by_identity.objects[0].Name == 'Ansible Test'
|
||||||
|
- by_identity.objects[0].ObjectClass == 'user'
|
||||||
|
- by_identity.objects[0].ObjectGUID == test_user_id
|
||||||
|
|
||||||
|
- name: get specific properties by GUID
|
||||||
|
win_domain_object_info:
|
||||||
|
identity: '{{ test_user_id }}'
|
||||||
|
properties:
|
||||||
|
- audio # byte[]
|
||||||
|
- company # string
|
||||||
|
- department # not set
|
||||||
|
- logonCount # int
|
||||||
|
- modifyTimestamp # DateTime
|
||||||
|
- nTSecurityDescriptor # SecurityDescriptor as SDDL
|
||||||
|
- objectSID # SID
|
||||||
|
- ProtectedFromAccidentalDeletion # bool
|
||||||
|
- sAMAccountType # Test out the enum string attribute that we add
|
||||||
|
- userAccountControl # Test ou the enum string attribute that we add
|
||||||
|
register: by_guid_custom_props
|
||||||
|
|
||||||
|
- name: assert get specific properties by GUID
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not by_guid_custom_props is changed
|
||||||
|
- by_guid_custom_props.objects | length == 1
|
||||||
|
- by_guid_custom_props.objects[0].DistinguishedName == test_user.distinguished_name
|
||||||
|
- by_guid_custom_props.objects[0].Name == 'Ansible Test'
|
||||||
|
- by_guid_custom_props.objects[0].ObjectClass == 'user'
|
||||||
|
- by_guid_custom_props.objects[0].ObjectGUID == test_user_id
|
||||||
|
- not by_guid_custom_props.objects[0].ProtectedFromAccidentalDeletion
|
||||||
|
- by_guid_custom_props.objects[0].audio == ['BQYHCA==', 'AQIDBA==']
|
||||||
|
- by_guid_custom_props.objects[0].company == 'Contoso R Us'
|
||||||
|
- by_guid_custom_props.objects[0].department == None
|
||||||
|
- by_guid_custom_props.objects[0].logonCount == 0
|
||||||
|
- by_guid_custom_props.objects[0].modifyTimestamp == test_user_mod_date
|
||||||
|
- by_guid_custom_props.objects[0].nTSecurityDescriptor.startswith('O:DAG:DAD:AI(')
|
||||||
|
- by_guid_custom_props.objects[0].objectSID.Name == test_user_name
|
||||||
|
- by_guid_custom_props.objects[0].objectSID.Sid == test_user.sid
|
||||||
|
- by_guid_custom_props.objects[0].sAMAccountType == 805306368
|
||||||
|
- by_guid_custom_props.objects[0].sAMAccountType_AnsibleFlags == ['SAM_USER_OBJECT']
|
||||||
|
- by_guid_custom_props.objects[0].userAccountControl == 66050
|
||||||
|
- by_guid_custom_props.objects[0].userAccountControl_AnsibleFlags == ['ADS_UF_ACCOUNTDISABLE', 'ADS_UF_NORMAL_ACCOUNT', 'ADS_UF_DONT_EXPIRE_PASSWD']
|
||||||
|
|
||||||
|
- name: get invalid property
|
||||||
|
win_domain_object_info:
|
||||||
|
filter: sAMAccountName -eq 'Ansible Test'
|
||||||
|
properties:
|
||||||
|
- FakeProperty
|
||||||
|
register: invalid_prop_warning
|
||||||
|
|
||||||
|
- name: assert get invalid property
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not invalid_prop_warning is changed
|
||||||
|
- invalid_prop_warning.objects | length == 0
|
||||||
|
- invalid_prop_warning.warnings | length == 1
|
||||||
|
- '"Failed to retrieve properties for AD object" not in invalid_prop_warning.warnings[0]'
|
||||||
|
|
||||||
|
- name: get by ldap filter returning multiple
|
||||||
|
win_domain_object_info:
|
||||||
|
ldap_filter: (&(objectClass=computer)(objectCategory=computer))
|
||||||
|
properties: '*'
|
||||||
|
register: multiple_ldap
|
||||||
|
|
||||||
|
- name: assert get by ldap filter returning multiple
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- not multiple_ldap is changed
|
||||||
|
- multiple_ldap.objects | length > 1
|
Loading…
Reference in a new issue