Skip to content

Commit

Permalink
microsoft.ad.user - Add group lookup DN logic (#119)
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 authored Jun 4, 2024
1 parent fd15a22 commit 850ec3a
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 850ec3a

Please sign in to comment.