Modules/AzureDevOpsDsc.Common/Resources/Functions/Public/AzDoTaskGroupPermission/Get-AzDoTaskGroupPermission.ps1
|
<#
.SYNOPSIS Retrieves the current state of task group permissions. .DESCRIPTION Compares the desired ACEs against the 'MetaTask' ACL for a task group ('{projectId}/{taskGroupId}'), or for the project level ('{projectId}') when TaskGroupName is omitted. Only the identities named in Permissions are compared. A missing project or task group is NotFound. New refuses to write in that case, since there is no token to write to. .PARAMETER ProjectName The name of the Azure DevOps project. .PARAMETER TaskGroupName The name of the task group. Omit to target the project level. .PARAMETER isInherited Whether the ACL inherits permissions from the project level. .PARAMETER Permissions The desired ACEs (Identity + Permission map). .PARAMETER LookupResult Supplied by the DSC base class. .PARAMETER Ensure Supplied by the DSC base class. .PARAMETER Force Supplied by the DSC base class. .OUTPUTS System.Collections.Hashtable #> Function Get-AzDoTaskGroupPermission { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [Parameter(Mandatory = $true)][string]$ProjectName, [Parameter()][string]$TaskGroupName, [Parameter(Mandatory = $true)][bool]$isInherited, [Parameter()][HashTable[]]$Permissions, [Parameter()][HashTable]$LookupResult, [Parameter()][Ensure]$Ensure, [Parameter()][System.Management.Automation.SwitchParameter]$Force ) Write-Verbose "[Get-AzDoTaskGroupPermission] Started." $SecurityNamespace = 'MetaTask' $OrganizationName = Get-AzDoOrganizationName $getResult = @{ Ensure = [Ensure]::Absent propertiesChanged = @() project = $ProjectName taskGroupName = $TaskGroupName status = $null reason = $null } # Resolve-AzDoProject returns $null on a 404 (a deleted project) and rethrows anything else. $project = Resolve-AzDoProject -ProjectName $ProjectName if (-not $project) { $getResult.status = [DSCGetSummaryState]::NotFound $getResult.reason = "Project not found: $ProjectName" return $getResult } $taskGroup = $null if (-not [String]::IsNullOrWhiteSpace($TaskGroupName)) { $cacheKey = '{0}\{1}' -f $ProjectName, $TaskGroupName $taskGroup = Get-CacheItem -Key $cacheKey -Type 'LiveTaskGroups' if (-not $taskGroup) { # The task group cache is only filled by AzDoTaskGroup's own New and Set. Write-Verbose "[Get-AzDoTaskGroupPermission] Task group '$TaskGroupName' not in cache - falling back to a live API lookup." $apiUri = 'https://dev.azure.com/{0}/' -f $OrganizationName $taskGroup = List-DevOpsTaskGroups -ApiUri $apiUri -ProjectName $ProjectName | Where-Object { $_.name -eq $TaskGroupName } | Select-Object -First 1 if ($taskGroup) { Add-CacheItem -Key $cacheKey -Value $taskGroup -Type 'LiveTaskGroups' } } if (-not $taskGroup) { $getResult.status = [DSCGetSummaryState]::NotFound $getResult.reason = "Task group not found: $TaskGroupName" return $getResult } } $namespace = Get-CacheItem -Key $SecurityNamespace -Type 'SecurityNamespaces' if (-not $namespace) { Write-Error "[Get-AzDoTaskGroupPermission] Security namespace '$SecurityNamespace' not found." -ErrorAction Continue $getResult.status = [DSCGetSummaryState]::Error $getResult.reason = "Security namespace '$SecurityNamespace' not found." return $getResult } $getResult.namespace = $namespace $aclToken = if ($taskGroup) { '{0}/{1}' -f $project.id, $taskGroup.id } else { '{0}' -f $project.id } # A token with no explicit ACL yet comes back empty, which is not an error. $DevOpsACLs = @(Get-DevOpsACL -OrganizationName $OrganizationName -SecurityDescriptorId $namespace.namespaceId -Token $aclToken | Where-Object { $_.token -eq $aclToken }) $DifferenceACLs = @($DevOpsACLs | ConvertTo-FormattedACL -SecurityNamespace $SecurityNamespace -OrganizationName $OrganizationName) $params = @{ Permissions = $Permissions SecurityNamespace = $SecurityNamespace isInherited = $isInherited OrganizationName = $OrganizationName TokenName = $aclToken } $ReferenceACLs = @(ConvertTo-ACL @params | Where-Object { $_.token.Type -ne 'TaskGroupUnknown' }) # Compare only the identities this resource manages; a task group's creator holds an ACE too. if ($ReferenceACLs.Count -gt 0 -and $DifferenceACLs.Count -gt 0) { $desiredOriginIds = @($ReferenceACLs[0].aces | ForEach-Object { $_.Identity.value.originId } | Where-Object { $_ }) if ($desiredOriginIds.Count -gt 0) { $DifferenceACLs[0]['aces'] = @($DifferenceACLs[0].aces | Where-Object { $_.Identity.value.originId -in $desiredOriginIds }) } else { $DifferenceACLs[0]['aces'] = @() } } $compareResult = Test-ACLListforChanges -ReferenceACLs $ReferenceACLs -DifferenceACLs $DifferenceACLs $getResult.propertiesChanged = $compareResult.propertiesChanged $getResult.status = [DSCGetSummaryState]::"$($compareResult.status)" $getResult.reason = $compareResult.reason # Absent means none of the managed identities holds an entry on the token. The base class only # treats NotFound as absent, so report that once they are all gone; otherwise Test stays False # and Remove runs on every pass. # With no identity resolved there is nothing managed to look for, so the live ACEs do not count. $managedAceCount = if ($ReferenceACLs.Count -eq 0) { 0 } else { @($DifferenceACLs | ForEach-Object { $_.aces } | Where-Object { $_ }).Count } if ($Ensure -eq [Ensure]::Absent -and $managedAceCount -eq 0) { $getResult.status = [DSCGetSummaryState]::NotFound $getResult.reason = 'None of the managed identities has an entry on the token.' } $getResult.ReferenceACLs = $ReferenceACLs $getResult.DifferenceACLs = $DifferenceACLs $getResult.aclToken = $aclToken # The raw ACL, so Set can carry every ACE it does not manage through the replacing POST. $getResult.LiveACLs = $DevOpsACLs return $getResult } |