Skip to content

Commit

Permalink
microsoft.ad.user - Add group lookup DN logic
Browse files Browse the repository at this point in the history
Adds the same DN lookup logic to the microsoft.ad.user groups option.
This allows the caller to add/remove/set the user's group membership to
groups that are in a different domain. This change also aligns renames
the missing_behaviour option to lookup_failure_action to be consistent
with the other lookup DN options. THe missing_behaviour option is still
present as an alias for backwards compatibility.
  • Loading branch information
jborean93 committed Jun 4, 2024
1 parent fd15a22 commit 8661d6e
Show file tree
Hide file tree
Showing 5 changed files with 313 additions and 38 deletions.
9 changes: 9 additions & 0 deletions changelogs/fragments/user-groups.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
minor_changes:
- >-
microsoft.ad.user - Support group member lookup on alternative server using the DN lookup syntax.
This syntax uses a dictionary where ``name`` defined the group to lookup and ``server`` defines the
server to lookup the group on.
- >-
microsoft.ad.user - Rename the option ``groups.missing_action`` to ``groups.lookup_failure_action``
to make the option more consistent with other modules. The ``missing_action`` option is still
supported as an alias.
96 changes: 67 additions & 29 deletions plugins/modules/user.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,11 @@ $setParams = @{
Option = @{
type = 'dict'
options = @{
add = @{ type = 'list'; elements = 'str' }
remove = @{ type = 'list'; elements = 'str' }
set = @{ type = 'list'; elements = 'str' }
missing_behaviour = @{
add = @{ type = 'list'; elements = 'raw' }
remove = @{ type = 'list'; elements = 'raw' }
set = @{ type = 'list'; elements = 'raw' }
lookup_failure_action = @{
aliases = @('missing_behaviour')
choices = 'fail', 'ignore', 'warn'
default = 'fail'
type = 'str'
Expand Down Expand Up @@ -365,27 +366,19 @@ $setParams = @{
return
}

$groupMissingBehaviour = $Module.Params.groups.missing_behaviour
$lookupGroup = {
try {
(Get-ADGroup -Identity $args[0] @ADParams).DistinguishedName
}
catch {
if ($groupMissingBehaviour -eq "fail") {
$module.FailJson("Failed to locate group $($args[0]): $($_.Exception.Message)", $_)
}
elseif ($groupMissingBehaviour -eq "warn") {
$module.Warn("Failed to locate group $($args[0]) but continuing on: $($_.Exception.Message)")
}
}
}

[string[]]$existingGroups = @(
# In check mode the ADObject won't be given
if ($ADObject) {
try {
Get-ADPrincipalGroupMembership -Identity $ADObject.ObjectGUID @ADParams -ErrorAction Stop |
Select-Object -ExpandProperty DistinguishedName
# Get-ADPrincipalGroupMembership doesn't work well with
# cross domain membership. It also gets the primary group
# so this code reflects that using Get-ADUser instead.
$userMembership = Get-ADUser -Identity $ADObject.ObjectGUID @ADParams -Properties @(
'MemberOf',
'PrimaryGroup'
) -ErrorAction Stop
$userMembership.memberOf
$userMembership.PrimaryGroup
}
catch {
$module.Warn("Failed to enumerate user groups but continuing on: $($_.Exception.Message)")
Expand All @@ -401,14 +394,42 @@ $setParams = @{
CaseInsensitive = $true
Existing = $existingGroups
}
'add', 'remove', 'set' | ForEach-Object -Process {
if ($null -ne $Module.Params.groups[$_]) {
$compareParams[$_] = @(
foreach ($group in $Module.Params.groups[$_]) {
& $lookupGroup $group
$dnServerParams = @{}
foreach ($actionKvp in $Module.Params.groups.GetEnumerator()) {
if ($null -eq $actionKvp.Value -or $actionKvp.Key -in @('lookup_failure_action', 'missing_behaviour')) {
continue
}

$convertParams = @{
Module = $Module
Context = "groups.$($actionKvp.Key)"
FailureAction = $Module.Params.groups.lookup_failure_action
}
$dns = foreach ($lookupId in $actionKvp.Value) {
$dn = $lookupId | ConvertTo-AnsibleADDistinguishedName @ADParams @convertParams
if (-not $dn) {
continue # Warning was written
}

# As membership is done on the group server, we need to store
# correct server and credentials that was used for the lookup.
if ($lookupId -is [System.Collections.IDictionary] -and $lookupId.server) {
$dnServerParams[$dn] = @{
Server = $lookupId.server
}

if ($Module.ServerCredentials.ContainsKey($lookupId.server)) {
$dnServerParams[$dn].Credential = $Module.ServerCredentials[$lookupId.server]
}
)
}
else {
$dnServerParams[$dn] = $ADParams
}

$dn
}

$compareParams[$actionKvp.Key] = @($dns)
}

$res = Compare-AnsibleADIdempotentList @compareParams
Expand All @@ -420,15 +441,32 @@ $setParams = @{
WhatIf = $Module.CheckMode
}
foreach ($member in $res.ToAdd) {
$lookupParams = if ($dnServerParams.ContainsKey($member)) {
$dnServerParams[$member]
}
else {
$ADParams
}
if ($ADObject) {
Add-ADGroupMember -Identity $member -Members $ADObject.ObjectGUID @ADParams @commonParams
Set-ADObject -Identity $member -Add @{
member = $ADObject.DistinguishedName
} @lookupParams @commonParams

}
$Module.Result.changed = $true
}
foreach ($member in $res.ToRemove) {
$lookupParams = if ($dnServerParams.ContainsKey($member)) {
$dnServerParams[$member]
}
else {
$ADParams
}
if ($ADObject) {
try {
Remove-ADGroupMember -Identity $member -Members $ADObject.ObjectGUID @ADParams @commonParams
Set-ADObject -Identity $member -Remove @{
member = $ADObject.DistinguishedName
} @lookupParams @commonParams
}
catch [Microsoft.ActiveDirectory.Management.ADException] {
if ($_.Exception.ErrorCode -eq 0x0000055E) {
Expand Down
20 changes: 16 additions & 4 deletions plugins/modules/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,20 @@
- To clear all group memberships, use I(set) with an empty list.
- Note that users cannot be removed from their principal group (for
example, "Domain Users"). Attempting to do so will display a warning.
- Adding and removing a user from a group is done on the group AD object.
If the group is an object in a different domain, then it may require
explicit I(server) and I(domain_credentials) for it to work.
- Each subkey is set to a list of groups objects to add, remove or
set as the membership of this AD user respectively. A group can be in
the form of a C(distinguishedName), C(objectGUID), C(objectSid), or
C(sAMAccountName).
- Each subkey value is a list of group objects in the form of a
C(distinguishedName), C(objectGUID), C(objectSid), C(sAMAccountName),
or C(userPrincipalName) string or a dictionary with the I(name) and
optional I(server) key.
- See
R(DN Lookup Attributes,ansible_collections.microsoft.ad.docsite.guide_attributes.dn_lookup_attributes)
for more information on how DN lookups work.
- See R(Setting list option values,ansible_collections.microsoft.ad.docsite.guide_list_values)
for more information on how to add/remove/set list options.
type: dict
Expand All @@ -128,20 +138,20 @@
description:
- The groups to add the user to.
type: list
elements: str
elements: raw
remove:
description:
- The groups to remove the user from.
type: list
elements: str
elements: raw
set:
description:
- The only groups the user is a member of.
- This will clear out any existing groups if not in the specified list.
- Set to an empty list to clear all group membership of the user.
type: list
elements: str
missing_behaviour:
elements: raw
lookup_failure_action:
description:
- Controls what happens when a group specified by C(groups) is an
invalid group name.
Expand All @@ -150,6 +160,8 @@
- C(ignore) will ignore any groups that does not exist.
- C(warn) will display a warning for any groups that do not exist but
will continue without failing.
aliases:
- missing_behaviour
choices:
- fail
- ignore
Expand Down
Loading

0 comments on commit 8661d6e

Please sign in to comment.