Modules/AzureDevOpsDsc.Common/Resources/Functions/Public/AzDoSecureFilePermission/Get-AzDoSecureFilePermission.ps1
|
<#
.SYNOPSIS Retrieves the current ACL state of an Azure DevOps secure file. .DESCRIPTION Builds the 'Library' ACL token for the secure file and compares its ACL against the desired permissions. Secure files share the Library namespace with variable groups but carry their own token segment: 'Library/Project/{projectId}/SecureFile/{secureFileId}'. Omitting SecureFileName targets the project Library root, which is the parent of every secure file and variable group in the project. .PARAMETER ProjectName The name of the Azure DevOps project. .PARAMETER SecureFileName The name of the secure file. Omit to target the project Library root. .PARAMETER isInherited Whether the ACL inherits permissions from its parent. .PARAMETER Permissions The desired access control entries. .PARAMETER LookupResult The lookup result supplied by the DSC base class. .PARAMETER Ensure The desired state, supplied by the DSC base class. .PARAMETER Force Forces the operation, supplied by the DSC base class. .EXAMPLE Get-AzDoSecureFilePermission -ProjectName 'Contoso' -SecureFileName 'signing.pfx' -isInherited $true #> Function Get-AzDoSecureFilePermission { [CmdletBinding()] [OutputType([System.Management.Automation.PSObject[]])] param ( [Parameter(Mandatory = $true)] [Alias('Name')] [System.String]$ProjectName, [Parameter()] [Alias('FileName')] [System.String]$SecureFileName, [Parameter(Mandatory = $true)] [System.Boolean]$isInherited, [Parameter()] [HashTable[]]$Permissions, [Parameter()] [HashTable]$LookupResult, [Parameter()] [Ensure]$Ensure, [Parameter()] [System.Management.Automation.SwitchParameter]$Force ) Write-Verbose "[Get-AzDoSecureFilePermission] Started." $SecurityNamespace = 'Library' $OrganizationName = Get-AzDoOrganizationName $getResult = @{ Ensure = [Ensure]::Absent propertiesChanged = @() status = $null reason = $null } $projectCache = Resolve-AzDoProject -ProjectName $ProjectName if (-not $projectCache) { $getResult.status = [DSCGetSummaryState]::Error $getResult.reason = "Project not found: $ProjectName" return $getResult } $secureFile = $null if (-not [String]::IsNullOrWhiteSpace($SecureFileName)) { $sfCacheKey = '{0}\{1}' -f $ProjectName, $SecureFileName $secureFile = Get-CacheItem -Key $sfCacheKey -Type 'LiveSecureFiles' if (-not $secureFile) { Write-Verbose "[Get-AzDoSecureFilePermission] Secure file '$SecureFileName' not in cache - falling back to a live API lookup." $allFiles = List-DevOpsSecureFiles -Organization $OrganizationName -ProjectName $ProjectName $secureFile = $allFiles | Where-Object { $_.name -eq $SecureFileName } | Select-Object -First 1 if ($secureFile) { Add-CacheItem -Key $sfCacheKey -Value $secureFile -Type 'LiveSecureFiles' } } if (-not $secureFile) { Write-Warning "[Get-AzDoSecureFilePermission] Secure file '$SecureFileName' not found in project '$ProjectName'." $getResult.status = [DSCGetSummaryState]::NotFound $getResult.reason = "Secure file not found: $SecureFileName" return $getResult } } $namespace = Get-CacheItem -Key $SecurityNamespace -Type 'SecurityNamespaces' if (-not $namespace) { Write-Error "[Get-AzDoSecureFilePermission] Security namespace '$SecurityNamespace' not found." $getResult.status = [DSCGetSummaryState]::Error return $getResult } $getResult.namespace = $namespace $aclToken = if ($secureFile) { 'Library/Project/{0}/SecureFile/{1}' -f $projectCache.id, $secureFile.id } else { 'Library/Project/{0}' -f $projectCache.id } $DevOpsACLs = Get-DevOpsACL -OrganizationName $OrganizationName -SecurityDescriptorId $namespace.namespaceId -Token $aclToken if (-not $DevOpsACLs) { $DevOpsACLs = Get-DevOpsACL -OrganizationName $OrganizationName -SecurityDescriptorId $namespace.namespaceId } # Drop the ACLs this lookup cannot be interested in BEFORE formatting them, exactly as # Get-AzDoProjectPermission and Get-AzDoProcessPermission already do. Formatting resolves every # ACE through Find-Identity, which costs an API round trip for each descriptor that is not # already cached, so formatting a whole namespace only to keep one token is where the time goes. # The fallback above fires whenever the secure file has no explicit ACL - the normal state # once permissions revert to inherited - so the full namespace is the common path, not a rare # one. The Library pattern is anchored, so the parsed filter below can only # keep a token equal to $aclToken: this drops exactly what that filter would have dropped. $DevOpsACLs = @($DevOpsACLs | Where-Object { $_.token -eq $aclToken }) $DifferenceACLs = $DevOpsACLs | ConvertTo-FormattedACL -SecurityNamespace $SecurityNamespace -OrganizationName $OrganizationName if ($secureFile) { $DifferenceACLs = $DifferenceACLs | Where-Object { ($_.Token.Type -eq 'Library') -and ($_.Token.ProjectId -eq $projectCache.id) -and ($_.Token.SecureFileId -eq $secureFile.id) } } else { # The project Library root is the token with neither a secure file nor a variable group # segment - both live in this namespace. $DifferenceACLs = $DifferenceACLs | Where-Object { ($_.Token.Type -eq 'Library') -and ($_.Token.ProjectId -eq $projectCache.id) -and (-not $_.Token.SecureFileId) -and (-not $_.Token.VariableGroupId) } } $tokenName = if ($secureFile) { 'Library/Project/{0}/SecureFile/{1}' -f $ProjectName, $SecureFileName } else { 'Library/Project/{0}' -f $ProjectName } $params = @{ Permissions = $Permissions SecurityNamespace = $SecurityNamespace isInherited = $isInherited OrganizationName = $OrganizationName TokenName = $tokenName } $ReferenceACLs = ConvertTo-ACL @params $compareResult = Test-ACLListforChanges -ReferenceACLs $ReferenceACLs -DifferenceACLs $DifferenceACLs $getResult.propertiesChanged = $compareResult.propertiesChanged $getResult.status = [DSCGetSummaryState]::"$($compareResult.status)" $getResult.reason = $compareResult.reason $getResult.ReferenceACLs = $ReferenceACLs $getResult.DifferenceACLs = $DifferenceACLs $getResult.aclToken = $aclToken return $getResult } |