From 6da6b80a9a26aa840fa31482d87eb55f24d32db0 Mon Sep 17 00:00:00 2001 From: Nik Charlebois Date: Tue, 19 Nov 2024 11:27:57 -0500 Subject: [PATCH 1/2] AADRoleEligibilityScheduleRequest - Improvements --- CHANGELOG.md | 1 + ...SFT_AADRoleEligibilityScheduleRequest.psm1 | 270 ++++++------------ ...DRoleEligibilityScheduleRequest.schema.mof | 2 +- 3 files changed, 91 insertions(+), 182 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90aa028eef..384174c018 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ * Initial release. * AADRoleEligibilityScheduleRequest * Adds support for custom role assignments at app scope. + FIXES [#5415](https://github.com/microsoft/Microsoft365DSC/issues/5415) * AzureBillingAccountPolicy * Initial release. * IntuneDeviceConfigurationPolicyAndroidDeviceOwner diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleEligibilityScheduleRequest/MSFT_AADRoleEligibilityScheduleRequest.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleEligibilityScheduleRequest/MSFT_AADRoleEligibilityScheduleRequest.psm1 index 51c06e3cf2..6ebb336f0d 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleEligibilityScheduleRequest/MSFT_AADRoleEligibilityScheduleRequest.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleEligibilityScheduleRequest/MSFT_AADRoleEligibilityScheduleRequest.psm1 @@ -12,16 +12,16 @@ [System.String] $RoleDefinition, - [Parameter()] - [ValidateSet('User', 'Group')] + [Parameter(Mandatory = $true)] + [ValidateSet('User', 'Group', 'ServicePrincipal')] [System.String] - $PrincipalType = 'User', + $PrincipalType, [Parameter()] [System.String] $Id, - [Parameter()] + [Parameter(Mandatory = $true)] [System.String] $DirectoryScopeId, @@ -83,18 +83,11 @@ [System.String[]] $AccessTokens ) - try - { - $ConnectionMode = New-M365DSCConnection -Workload 'MicrosoftGraph' ` - -InboundParameters $PSBoundParameters - } - catch - { - Write-Verbose -Message ($_) - } + $ConnectionMode = New-M365DSCConnection -Workload 'MicrosoftGraph' ` + -InboundParameters $PSBoundParameters - #Ensure the proper dependencies are installed in the current environment. - Confirm-M365DSCDependencies + #Ensure the proper dependencies are installed in the current environment. + Confirm-M365DSCDependencies #region Telemetry $ResourceName = $MyInvocation.MyCommand.ModuleName.Replace('MSFT_', '') @@ -124,137 +117,58 @@ } } - if ($null -eq $request) + Write-Verbose -Message "Getting Role Eligibility by PrincipalId and RoleDefinitionId" + $PrincipalValue = $null + if ($PrincipalType -eq 'User') { - if ($null -ne $Script:exportedInstances -and $Script:ExportMode) - { - Write-Verbose -Message "Getting Role Eligibility by PrincipalId and RoleDefinitionId" - $PrincipalTypeValue = $null - if ($PrincipalType -eq 'User') - { - Write-Verbose -Message "Retrieving Principal by UserPrincipalName {$Principal}" - $PrincipalIdValue = Get-MgUser -Filter "UserPrincipalName eq '$Principal'" -ErrorAction SilentlyContinue - $PrincipalTypeValue = 'User' - } - if ($null -eq $PrincipalIdValue -or $PrincipalType -eq 'Group') - { - Write-Verbose -Message "Retrieving Principal by DisplayName {$Principal}" - $PrincipalIdValue = Get-MgGroup -Filter "DisplayName eq '$Principal'" -ErrorAction SilentlyContinue - $PrincipalTypeValue = 'Group' - } - - if ($null -ne $PrincipalIdValue) - { - $PrincipalId = $PrincipalIdValue.Id - } - else - { - return $nullResult - } - Write-Verbose -Message "Found Principal {$PrincipalId}" - $RoleDefinitionId = (Get-MgBetaRoleManagementDirectoryRoleDefinition -Filter "DisplayName eq '$RoleDefinition'").Id - $request = $Script:exportedInstances | Where-Object -FilterScript {$_.PrincipalId -eq $PrincipalId -and $_.RoleDefinitionId -eq $RoleDefinition} | Sort-Object -Property CompletedDateTime -Descending - } - else - { - Write-Verbose -Message "Getting Role Eligibility by PrincipalId and RoleDefinitionId" - if ($PrincipalType -eq 'User') - { - Write-Verbose -Message "Retrieving principal {$Principal} of type {$PrincipalType}" - $PrincipalIdValue = Get-MgUser -Filter "UserPrincipalName eq '$Principal'" -ErrorAction SilentlyContinue - $PrincipalTypeValue = 'User' - } - - if ($null -eq $PrincipalIdValue -or $PrincipalType -eq 'Group') - { - Write-Verbose -Message "Retrieving principal {$Principal} of type {$PrincipalType}" - $PrincipalIdValue = Get-MgGroup -Filter "DisplayName eq '$Principal'" -ErrorAction SilentlyContinue - $PrincipalTypeValue = 'Group' - } - - if ($null -ne $PrincipalIdValue) - { - $PrincipalId = $PrincipalIdValue.Id - } - else - { - return $nullResult - } - Write-Verbose -Message "Found Principal {$PrincipalId}" - $schedulesForPrincipal = Get-MgBetaRoleManagementDirectoryRoleEligibilitySchedule -Filter "PrincipalId eq '$PrincipalId'" - foreach ($instance in $schedulesForPrincipal) - { - $roleInfo = Get-MgBetaRoleManagementDirectoryRoleDefinition -UnifiedRoleDefinitionId $instance.RoleDefinitionId - if ($roleInfo.DisplayName -eq $RoleDefinition) - { - $schedule = $instance - } - } - [Array]$request = Get-MgBetaRoleManagementDirectoryRoleEligibilityScheduleRequest -Filter "PrincipalId eq '$PrincipalId'" | Where-Object -FilterScript {$_.RoleDefinitionId -eq $schedule.RoleDefinitionId} | Sort-Object -Property CompletedDateTime -Descending -` - if ($request.Length -gt 1) - { - $request = $request[0] - } - } + Write-Verbose -Message "Retrieving Principal by UserPrincipalName {$Principal}" + $PrincipalInstance = Get-MgUser -Filter "UserPrincipalName eq '$Principal'" -ErrorAction SilentlyContinue + $PrincipalValue = $PrincipalInstance.UserPrincipalName + } + elseif ($null -eq $PrincipalIdValue -and $PrincipalType -eq 'Group') + { + Write-Verbose -Message "Retrieving Principal by DisplayName {$Principal}" + $PrincipalInstance = Get-MgGroup -Filter "DisplayName eq '$Principal'" -ErrorAction SilentlyContinue + $PrincipalValue = $PrincipalInstance.DisplayName } else { - Write-Verbose -Message "Request is not null: $request" - $ObjectGuid = [System.Guid]::empty - if ($PrincipalType -eq 'User') - { - Write-Verbose -Message "Retrieving principal {$Principal} of type {$PrincipalType}" - - if ([System.Guid]::TryParse($Principal,[System.Management.Automation.PSReference]$ObjectGuid)) - { - $PrincipalIdValue = Get-MgUser -UserId $Principal -ErrorAction SilentlyContinue - } - else - { - $PrincipalIdValue = Get-MgUser -Filter "UserPrincipalName eq '$Principal'" -ErrorAction SilentlyContinue - } - $PrincipalTypeValue = 'User' - } + Write-Verbose -Message "Retrieving Principal by DisplayName {$Principal}" + $PrincipalInstance = Get-MgServicePrincipal -Filter "DisplayName eq '$Principal'" -ErrorAction SilentlyContinue + $PrincipalValue = $PrincipalInstance.DisplayName + } - if ($null -eq $PrincipalIdValue -or $PrincipalType -eq 'Group') - { - Write-Verbose -Message "Retrieving principal {$Principal} of type {$PrincipalType}" - if ([System.Guid]::TryParse($Principal,[System.Management.Automation.PSReference]$ObjectGuid)) - { - $PrincipalIdValue = Get-MgGroup -GroupId $Principal -ErrorAction SilentlyContinue - } - else - { - $PrincipalIdValue = Get-MgGroup -Filter "DisplayName eq '$Principal'" -ErrorAction SilentlyContinue - } - $PrincipalTypeValue = 'Group' - } + Write-Verbose -Message "Found Principal" + $RoleDefinitionId = (Get-MgBetaRoleManagementDirectoryRoleDefinition -Filter "DisplayName eq '$RoleDefinition'").Id + Write-Verbose -Message "Retrieved role definition {$RoleDefinition} with ID {$RoleDefinitionId}" - if ($null -ne $PrincipalIdValue) - { - $PrincipalId = $PrincipalIdValue.Id - } - else + if ($null -eq $request) + { + Write-Verbose -Message "Retrieving the request by PrincipalId {$($PrincipalInstance.Id)}, RoleDefinitionId {$($RoleDefinitionId)} and DirectoryScopeId {$($DirectoryScopeId)}" + [Array] $requests = Get-MgBetaRoleManagementDirectoryRoleEligibilityScheduleRequest -Filter "PrincipalId eq '$($PrincipalInstance.Id)' and RoleDefinitionId eq '$($RoleDefinitionId)' and DirectoryScopeId eq '$($DirectoryScopeId)'" + if ($requests.Length -eq 0) { return $nullResult } - $RoleDefinitionId = (Get-MgBetaRoleManagementDirectoryRoleDefinition -Filter "DisplayName eq '$RoleDefinition'").Id - $schedules = Get-MgBetaRoleManagementDirectoryRoleEligibilitySchedule -Filter "PrincipalId eq '$($request.PrincipalId)'" - $schedule = $schedules | Where-Object -FilterScript {$_.RoleDefinitionId -eq $RoleDefinitionId} - if ($null -eq $schedule) + + $request = $requests[0] + } + + $schedules = Get-MgBetaRoleManagementDirectoryRoleEligibilitySchedule -Filter "PrincipalId eq '$($request.PrincipalId)'" + $schedule = $schedules | Where-Object -FilterScript {$_.RoleDefinitionId -eq $RoleDefinitionId} + if ($null -eq $schedule) + { + foreach ($instance in $schedules) { - foreach ($instance in $schedules) + $roleDefinitionInfo = Get-MgBetaRoleManagementDirectoryRoleDefinition -UnifiedRoleDefinitionId $instance.RoleDefinitionId + if ($null -ne $roleDefinitionInfo -and $RoleDefinitionInfo.DisplayName -eq $RoleDefinition) { - $roleDefinitionInfo = Get-MgBetaRoleManagementDirectoryRoleDefinition -UnifiedRoleDefinitionId $instance.RoleDefinitionId - if ($null -ne $roleDefinitionInfo -and $RoleDefinitionInfo.DisplayName -eq $RoleDefinition) - { - $schedule = $instance - break - } + $schedule = $instance + break } } } + if ($null -eq $schedule -or $null -eq $request) { if ($null -eq $schedule) @@ -268,32 +182,6 @@ return $nullResult } - Write-Verbose -Message "Found existing AADRolelLigibilityScheduleRequest" - if ($PrincipalType -eq 'User') - { - Write-Verbose -Message "Retrieving Principal by UserId {$($request.PrincipalId)}" - $PrincipalInstance = Get-MgUser -UserId $request.PrincipalId -ErrorAction SilentlyContinue - $PrincipalTypeValue = 'User' - } - if ($null -eq $PrincipalInstance -or $PrincipalType -eq 'Group') - { - Write-Verbose -Message "Retrieving Principal by GroupId {$($request.PrincipalId)}" - $requestArray = [Array]$request - if ($requestArray.Count -gt 1) - { - $requestArray = $requestArray | Sort-Object -Property CreatedDateTime -Descending - $request = $requestArray[0] - } - $PrincipalInstance = Get-MGGroup -GroupId $request.PrincipalId -ErrorAction SilentlyContinue - $PrincipalTypeValue = 'Group' - } - - if ($null -eq $PrincipalInstance) - { - Write-Verbose -Message "Couldn't retrieve Principal {$($request.PrincipalId)}" - return $nullResult - } - $ScheduleInfoValue = @{} if ($null -ne $schedule.ScheduleInfo.Expiration) @@ -344,19 +232,9 @@ } } - $PrincipalValue = $null - if ($PrincipalType -eq 'User') - { - $PrincipalValue = $PrincipalInstance.UserPrincipalName - } - if ($null -eq $PrincipalValue -or $PrincipalTypeValue -eq 'Group') - { - $PrincipalValue = $PrincipalInstance.DisplayName - } - $results = @{ Principal = $PrincipalValue - PrincipalType = $PrincipalTypeValue + PrincipalType = $PrincipalType RoleDefinition = $RoleDefinition DirectoryScopeId = $request.DirectoryScopeId AppScopeId = $request.AppScopeId @@ -403,16 +281,16 @@ function Set-TargetResource [System.String] $RoleDefinition, - [Parameter()] - [ValidateSet('User', 'Group')] + [Parameter(Mandatory = $true)] + [ValidateSet('User', 'Group', 'ServicePrincipal')] [System.String] - $PrincipalType = 'User', + $PrincipalType, [Parameter()] [System.String] $Id, - [Parameter()] + [Parameter(Mandatory = $true)] [System.String] $DirectoryScopeId, @@ -518,6 +396,10 @@ function Set-TargetResource { [Array]$PrincipalIdValue = (Get-MgGroup -Filter "DisplayName eq '$Principal'").Id } + elseif ($PrincipalType -eq 'ServicePrincipal') + { + [Array]$PrincipalIdValue = (Get-MgServicePrincipal -Filter "DisplayName eq '$Principal'").Id + } if ($null -eq $PrincipalIdValue) { @@ -598,21 +480,21 @@ function Set-TargetResource $ParametersOps.Remove("PrincipalType") | Out-Null if ($Ensure -eq 'Present' -and $currentInstance.Ensure -eq 'Absent') { - Write-Verbose -Message "Creating a Role Eligibility Schedule Request for user {$Principal} and role {$RoleDefinition}" + Write-Verbose -Message "Creating a Role Assignment Schedule Request for principal {$Principal} and role {$RoleDefinition}" $ParametersOps.Remove("Id") | Out-Null Write-Verbose -Message "Values: $(Convert-M365DscHashtableToString -Hashtable $ParametersOps)" New-MgBetaRoleManagementDirectoryRoleEligibilityScheduleRequest @ParametersOps } elseif ($Ensure -eq 'Present' -and $currentInstance.Ensure -eq 'Present') { - Write-Verbose -Message "Updating the Role Eligibility Schedule Request for user {$Principal} and role {$RoleDefinition}" + Write-Verbose -Message "Updating the Role Assignment Schedule Request for principal {$Principal} and role {$RoleDefinition}" $ParametersOps.Remove("Id") | Out-Null $ParametersOps.Action = 'AdminUpdate' New-MgBetaRoleManagementDirectoryRoleEligibilityScheduleRequest @ParametersOps } elseif ($Ensure -eq 'Absent' -and $currentInstance.Ensure -eq 'Present') { - Write-Verbose -Message "Removing the Role Eligibility Schedule Request for user {$Principal} and role {$RoleDefinition}" + Write-Verbose -Message "Removing the Role Assignment Schedule Request for principal {$Principal} and role {$RoleDefinition}" $ParametersOps.Remove("Id") | Out-Null $ParametersOps.Action = 'AdminRemove' New-MgBetaRoleManagementDirectoryRoleEligibilityScheduleRequest @ParametersOps @@ -633,16 +515,16 @@ function Test-TargetResource [System.String] $RoleDefinition, - [Parameter()] - [ValidateSet('User', 'Group')] + [Parameter(Mandatory = $true)] + [ValidateSet('User', 'Group', 'ServicePrincipal')] [System.String] - $PrincipalType = 'User', + $PrincipalType, [Parameter()] [System.String] $Id, - [Parameter()] + [Parameter(Mandatory = $true)] [System.String] $DirectoryScopeId, @@ -862,10 +744,36 @@ function Export-TargetResource $displayedKey = $request.Id Write-Host " |---[$i/$($Script:exportedInstances.Count)] $displayedKey" -NoNewline + # Find the Principal Type + $principalType = 'User' + $userInfo = Get-MgUser -UserId $request.PrincipalId -ErrorAction SilentlyContinue + + if ($null -eq $userInfo) + { + $principalType = 'Group' + $groupInfo = Get-MgGroup -GroupId $request.PrincipalId -ErrorAction SilentlyContinue + if ($null -eq $groupInfo) + { + $principalType = 'ServicePrincipal' + $spnInfo = Get-MgServicePrincipal -ServicePrincipalId $request.PrincipalId + $PrincipalValue = $spnInfo.DisplayName + } + else + { + $PrincipalValue = $groupInfo.DisplayName + } + } + else + { + $PrincipalValue = $userInfo.UserPrincipalName + } + $RoleDefinitionId = Get-MgBetaRoleManagementDirectoryRoleDefinition -UnifiedRoleDefinitionId $request.RoleDefinitionId $params = @{ Id = $request.Id - Principal = $request.PrincipalId + Principal = $PrincipalValue + PrincipalType = $principalType + DirectoryScopeId = $request.DirectoryScopeId RoleDefinition = $RoleDefinitionId.DisplayName Ensure = 'Present' Credential = $Credential diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleEligibilityScheduleRequest/MSFT_AADRoleEligibilityScheduleRequest.schema.mof b/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleEligibilityScheduleRequest/MSFT_AADRoleEligibilityScheduleRequest.schema.mof index e679dd288e..7e81112f40 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleEligibilityScheduleRequest/MSFT_AADRoleEligibilityScheduleRequest.schema.mof +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleEligibilityScheduleRequest/MSFT_AADRoleEligibilityScheduleRequest.schema.mof @@ -56,7 +56,7 @@ class MSFT_AADRoleEligibilityScheduleRequest : OMI_BaseResource [Key, Description("User Principal Name of the eligibility request.")] String Principal; [Key, Description("Role associated with the eligibility request.")] String RoleDefinition; [Write, Description("Represented the type of principal to assign the request to. Accepted values are: Group and User."), ValueMap{"Group","User"}, Values{"Group","User"}] String PrincipalType; - [Write, Description("Identifier of the directory object representing the scope of the role eligibility. The scope of an role eligibility determines the set of resources for which the principal has been granted access. Directory scopes are shared scopes stored in the directory that are understood by multiple applications. Use / for tenant-wide scope. Use appScopeId to limit the scope to an application only. Either directoryScopeId or appScopeId is required.")] String DirectoryScopeId; + [Key, Description("Identifier of the directory object representing the scope of the role eligibility. The scope of an role eligibility determines the set of resources for which the principal has been granted access. Directory scopes are shared scopes stored in the directory that are understood by multiple applications. Use / for tenant-wide scope. Use appScopeId to limit the scope to an application only. Either directoryScopeId or appScopeId is required.")] String DirectoryScopeId; [Write, Description("Identifier for the Role Eligibility Schedule Request.")] String Id; [Write, Description("Identifier of the app-specific scope when the role eligibility is scoped to an app. The scope of a role eligibility determines the set of resources for which the principal is eligible to access. App scopes are scopes that are defined and understood by this application only. Use / for tenant-wide app scopes. Use directoryScopeId to limit the scope to particular directory objects, for example, administrative units. Either directoryScopeId or appScopeId is required.")] String AppScopeId; [Write, Description("Represents the type of operation on the role eligibility request.The possible values are: adminAssign, adminUpdate, adminRemove, selfActivate, selfDeactivate, adminExtend, adminRenew, selfExtend, selfRenew, unknownFutureValue."), ValueMap{"adminAssign","adminUpdate","adminRemove","selfActivate","selfDeactivate","adminExtend","adminRenew","selfExtend","selfRenew","unknownFutureValue"}, Values{"adminAssign","adminUpdate","adminRemove","selfActivate","selfDeactivate","adminExtend","adminRenew","selfExtend","selfRenew","unknownFutureValue"}] String Action; From 2b349d7a33d4da94db3b239423a9e7ce908889d4 Mon Sep 17 00:00:00 2001 From: Nik Charlebois Date: Tue, 19 Nov 2024 11:49:38 -0500 Subject: [PATCH 2/2] Update Microsoft365DSC.AADRoleEligibilityScheduleRequest.Tests.ps1 --- ...icrosoft365DSC.AADRoleEligibilityScheduleRequest.Tests.ps1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADRoleEligibilityScheduleRequest.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADRoleEligibilityScheduleRequest.Tests.ps1 index be713c3108..6f84a2bcd2 100644 --- a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADRoleEligibilityScheduleRequest.Tests.ps1 +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADRoleEligibilityScheduleRequest.Tests.ps1 @@ -74,6 +74,7 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Ensure = "Present"; IsValidationOnly = $False; Principal = "John.Smith@contoso.com"; + PrincipalType = "User" RoleDefinition = "Teams Communications Administrator"; ScheduleInfo = New-CimInstance -ClassName MSFT_AADRoleEligibilityScheduleRequestSchedule -Property @{ startDateTime = '2023-09-01T02:40:44Z' @@ -108,6 +109,7 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { DirectoryScopeId = "/"; Ensure = "Absent"; IsValidationOnly = $False; + PrincipalType = "User" Principal = "John.Smith@contoso.com"; RoleDefinition = "Teams Communications Administrator"; ScheduleInfo = New-CimInstance -ClassName MSFT_AADRoleEligibilityScheduleRequestSchedule -Property @{ @@ -159,6 +161,7 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { DirectoryScopeId = "/"; Ensure = "Present"; IsValidationOnly = $False; + PrincipalType = "User" Principal = "John.Smith@contoso.com"; RoleDefinition = "Teams Communications Administrator"; ScheduleInfo = New-CimInstance -ClassName MSFT_AADRoleEligibilityScheduleRequestSchedule -Property @{ @@ -217,6 +220,7 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { DirectoryScopeId = "/"; Ensure = "Present"; IsValidationOnly = $False; + PrincipalType = "User" Principal = "John.Smith@contoso.com"; RoleDefinition = "Teams Communications Administrator"; ScheduleInfo = New-CimInstance -ClassName MSFT_AADRoleEligibilityScheduleRequestSchedule -Property @{