Skip to content

Commit 1fd16e2

Browse files
authored
Enhance ACL Export and Project Valid Users role assignments (#110)
* Fix #104 and #44
1 parent bad43bd commit 1fd16e2

41 files changed

Lines changed: 1633 additions & 14 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/PSRule.Rules.AzureDevOps/Functions/Common.ps1

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,59 @@ function Get-AzDevOpsProject {
150150
}
151151
# End of Function Get-AzDevOpsProject
152152

153+
<#
154+
.SYNOPSIS
155+
Get all Azure DevOps Project Acls
156+
157+
.DESCRIPTION
158+
Get all Azure DevOps Project Acls using Azure DevOps Rest API
159+
160+
.PARAMETER ProjectId
161+
Project Id for Azure DevOps
162+
163+
.EXAMPLE
164+
Get-AzDevOpsProjectAcls -ProjectId $ProjectId
165+
#>
166+
function Get-AzDevOpsProjectAcls {
167+
[CmdletBinding()]
168+
param (
169+
[Parameter(Mandatory)]
170+
[string]
171+
$ProjectId
172+
)
173+
if ($null -eq $script:connection) {
174+
throw "Not connected to Azure DevOps. Run Connect-AzDevOps first"
175+
}
176+
$Organization = $script:connection.Organization
177+
$header = $script:connection.GetHeader()
178+
$aclInfo = @{
179+
'Environments' = "https://dev.azure.com/$Organization/_apis/accesscontrollists/83d4c2e6-e57d-4d6e-892b-b87222b7ad20?api-version=7.2-preview.1&token=Environments/$ProjectId"
180+
'Pipelines' = "https://dev.azure.com/$Organization/_apis/accesscontrollists/33344d9c-fc72-4d6f-aba5-fa317101a7e9?api-version=7.2-preview.1&token=$ProjectId"
181+
'ReleaseDefinitions' = "https://dev.azure.com/$Organization/_apis/accesscontrollists/c788c23e-1b46-4162-8f5e-d7585343b5de?api-version=7.2-preview.1&token=$ProjectId"
182+
'Repositories' = "https://dev.azure.com/$Organization/_apis/accesscontrollists/2e9eb7ed-3c0a-47d4-87c1-0ffdd275fd87?api-version=7.2-preview.1&token=repoV2/$ProjectId"
183+
'ServiceConnections' = "https://dev.azure.com/$Organization/_apis/accesscontrollists/49b48001-ca20-4adc-8111-5b60c903a50c?api-version=7.2-preview.1&token=endpoints/$ProjectId"
184+
'VariableGroups' = "https://dev.azure.com/$Organization/_apis/accesscontrollists/b7e84409-6553-448a-bbb2-af228e07cbeb?api-version=7.2-preview.1&token=Library/$ProjectId"
185+
}
186+
$acls = @{}
187+
try {
188+
# Walk through the aclInfo hashtable and get the acls for each resource
189+
foreach($key in $aclInfo.Keys) {
190+
$aclUri = $aclInfo[$key]
191+
Write-Verbose "Getting $key ACLs for project $ProjectId"
192+
$aclResponse = Invoke-RestMethod -Uri $aclUri -Method Get -Headers $header
193+
# If the response is not an object but a string, the authentication failed
194+
if ($aclResponse -is [string] -or $null -eq $aclResponse.value) {
195+
throw "Authentication failed or project not found"
196+
}
197+
$acls.Add($key,$aclResponse.value)
198+
}
199+
}
200+
catch {
201+
throw $_.Exception.Message
202+
}
203+
return $acls
204+
}
205+
153206
<#
154207
.SYNOPSIS
155208
Export the Azure DevOps Project
@@ -196,6 +249,10 @@ function Export-AzDevOpsProject {
196249
$response = Get-AzDevOpsProject -Project $Project
197250
$response | Add-Member -MemberType NoteProperty -Name ObjectType -Value "Azure.DevOps.Project"
198251
$response | Add-Member -MemberType NoteProperty -Name ObjectName -Value "$Organization.$Project"
252+
# Add the Project Acls to the response object
253+
$acls = Get-AzDevOpsProjectAcls -ProjectId $response.id
254+
$response | Add-Member -MemberType NoteProperty -Name ProjectAcls -Value $acls
255+
# Add a new id field to the response object
199256
$response.id = @{
200257
originalId = $response.id;
201258
resourceName = $response.name;
@@ -204,13 +261,13 @@ function Export-AzDevOpsProject {
204261
} | ConvertTo-Json -Depth 100
205262
}
206263
catch {
207-
throw "Failed to get project $Project from Azure DevOps"
264+
throw $_.Exception.Message
208265
}
209266
if($PassThru) {
210267
Write-Output $response
211268
} else {
212269
Write-Verbose "Exporting project $Project as file $Project.prj.ado.json"
213-
$response | ConvertTo-Json | Out-File -FilePath "$OutputPath/$Project.prj.ado.json"
270+
$response | ConvertTo-Json -Depth 100 | Out-File -FilePath "$OutputPath/$Project.prj.ado.json"
214271
}
215272
}
216273
# End of Function Export-AzDevOpsProject

src/PSRule.Rules.AzureDevOps/Functions/DevOps.Pipelines.Core.ps1

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,10 @@ function Get-AzDevOpsPipelineAcls {
7777
$ProjectId,
7878
[Parameter(Mandatory=$true)]
7979
[string]
80-
$PipelineId
80+
$PipelineId,
81+
[Parameter(Mandatory=$false)]
82+
[string]
83+
$Folder = ""
8184
)
8285
if ($null -eq $script:connection) {
8386
throw "Not connected to Azure DevOps. Run Connect-AzDevOps first"
@@ -90,11 +93,16 @@ function Get-AzDevOpsPipelineAcls {
9093
return $null
9194
} else {
9295
$header = $script:connection.GetHeader()
93-
$uri = "https://dev.azure.com/$Organization/_apis/accesscontrollists/33344d9c-fc72-4d6f-aba5-fa317101a7e9?api-version=6.0&token=$($ProjectId)/$($PipelineId)"
96+
$uri = "https://dev.azure.com/$Organization/_apis/accesscontrollists/33344d9c-fc72-4d6f-aba5-fa317101a7e9?api-version=7.2-preview.1&token=$($ProjectId)/$($PipelineId)"
9497
Write-Verbose "Getting pipeline ACLs from $uri"
9598
Write-Verbose "PROJECTID: $ProjectId"
9699
try {
97-
$response = (Invoke-RestMethod -Uri $uri -Method Get -Headers $header -ContentType "application/json") #| Where-Object { $_.token -eq "$($ProjectId)/$($PipelineId)" }
100+
if ($Folder -eq "") {
101+
$response = (Invoke-RestMethod -Uri $uri -Method Get -Headers $header -ContentType "application/json") #| Where-Object { $_.token -eq "$($ProjectId)/$($PipelineId)" }
102+
}
103+
else {
104+
$response = (Invoke-RestMethod -Uri $uri -Method Get -Headers $header -ContentType "application/json") #| Where-Object { $_.token -eq "$($ProjectId)/$($Folder)/$($PipelineId)" }
105+
}
98106
# if the response is not an object but a string, the authentication failed
99107
if ($response -is [string]) {
100108
throw "Authentication failed or project not found"
@@ -319,7 +327,14 @@ function Export-AzDevOpsPipelines {
319327
# Add the pipeline ACLs to the pipeline object if the token type is not ReadOnly
320328
if ($TokenType -ne 'ReadOnly') {
321329
Write-Verbose "Getting pipeline ACLs for pipeline $($pipeline.name)"
322-
$pipeline | Add-Member -MemberType NoteProperty -Name Acls -Value (Get-AzDevOpsPipelineAcls -ProjectId $ProjectId -PipelineId $pipelineId)
330+
if ($pipeline.folder -eq '\') {
331+
$Folder = ""
332+
}
333+
else {
334+
$Folder = $pipeline.folder.replace('\','/')
335+
$Folder = $Folder.Trim('/')
336+
}
337+
$pipeline | Add-Member -MemberType NoteProperty -Name Acls -Value (Get-AzDevOpsPipelineAcls -ProjectId $ProjectId -PipelineId $pipelineId -Folder $Folder)
323338
} else {
324339
Write-Verbose "Token Type is set to ReadOnly, no pipeline ACLs will be returned"
325340
}

src/PSRule.Rules.AzureDevOps/Functions/DevOps.Pipelines.Environments.ps1

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,66 @@ function Get-AzDevOpsEnvironmentChecks {
116116
}
117117
}
118118
Export-ModuleMember -Function Get-AzDevOpsEnvironmentChecks
119+
120+
<#
121+
.SYNOPSIS
122+
Get all Azure Pipelines environment ACLs from Azure DevOps project
123+
124+
.DESCRIPTION
125+
Get all Azure Pipelines environment ACLs from Azure DevOps project using Azure DevOps Rest API
126+
127+
.PARAMETER ProjectId
128+
Project Id for Azure DevOps
129+
130+
.PARAMETER EnvironmentId
131+
Environment Id for Azure DevOps
132+
133+
.EXAMPLE
134+
Get-AzDevOpsEnvironmentAcls -Project $Project -Environment $Environment
135+
136+
.NOTES
137+
Requires a connection to Azure DevOps with a token type of FullAccess
138+
#>
139+
function Get-AzDevOpsEnvironmentAcls {
140+
[CmdletBinding()]
141+
param (
142+
[Parameter(Mandatory)]
143+
[string]
144+
$ProjectId,
145+
[Parameter(Mandatory)]
146+
[string]
147+
$EnvironmentId
148+
)
149+
if ($null -eq $script:connection) {
150+
throw "Not connected to Azure DevOps. Run Connect-AzDevOps first"
151+
}
152+
$TokenType = $script:connection.TokenType
153+
# If token type is ReadOnly, write a warning and exit the function returing null
154+
if($TokenType -eq 'ReadOnly') {
155+
Write-Warning "Token type ReadOnly does not have access to Azure DevOps Pipelines Environments"
156+
return $null
157+
} else {
158+
$header = $script:connection.GetHeader()
159+
$Organization = $script:connection.Organization
160+
Write-Verbose "Getting environment ACLs for environment $Environment"
161+
$uri = "https://dev.azure.com/{0}/_apis/accesscontrollists/83d4c2e6-e57d-4d6e-892b-b87222b7ad20?api-version=7.2-preview.1&token=Environments/{1}/{2}" -f $Organization, $ProjectId, $EnvironmentId
162+
Write-Verbose "URI: $uri"
163+
try {
164+
$response = Invoke-RestMethod -Uri $uri -Method Get -Headers $header
165+
# If the response is a string and not an object, throw an exception for authentication failure or project not found
166+
if ($response -is [string]) {
167+
throw "Authentication failed or project not found"
168+
}
169+
}
170+
catch {
171+
throw $_.Exception.Message
172+
}
173+
$acls = @($response.value)
174+
return $acls
175+
}
176+
}
177+
Export-ModuleMember -Function Get-AzDevOpsEnvironmentAcls
178+
119179
<#
120180
.SYNOPSIS
121181
Export all Azure Pipelines environments to JSON files with their checks as nested objects
@@ -169,6 +229,8 @@ function Export-AzDevOpsEnvironmentChecks {
169229

170230
$checks = @(Get-AzDevOpsEnvironmentChecks -Project $Project -Environment $environment.id)
171231
$environment | Add-Member -MemberType NoteProperty -Name checks -Value $checks
232+
$acls = @(Get-AzDevOpsEnvironmentAcls -ProjectId $environment.project.id -EnvironmentId $environment.id)
233+
$environment | Add-Member -MemberType NoteProperty -Name Acls -Value $acls
172234
# Set the id property to a hash table with the original id, organization and project name
173235
$environment.id = @{
174236
originalId = $environment.id

src/PSRule.Rules.AzureDevOps/Functions/DevOps.Pipelines.Releases.ps1

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,11 @@ Function Get-AzDevOpsReleaseDefinitionAcls {
8585
} else {
8686
Write-Verbose "Getting release definition ACLs for release definition $ReleaseDefinitionId"
8787
if ($Folder -eq '') {
88-
$uri = "https://dev.azure.com/{0}/_apis/accesscontrollists/c788c23e-1b46-4162-8f5e-d7585343b5de?api-version=6.0&token={1}/{2}" -f $Organization, $ProjectId, $ReleaseDefinitionId
88+
$uri = "https://dev.azure.com/{0}/_apis/accesscontrollists/c788c23e-1b46-4162-8f5e-d7585343b5de?api-version=7.2-preview.1&token={1}/{2}" -f $Organization, $ProjectId, $ReleaseDefinitionId
8989
}
9090
else {
91-
$uri = "https://dev.azure.com/{0}/_apis/accesscontrollists/c788c23e-1b46-4162-8f5e-d7585343b5de?api-version=6.0&token={1}/{2}/{3}" -f $Organization, $ProjectId, $Folder, $ReleaseDefinitionId
91+
$Folder = $Folder.Trim('/')
92+
$uri = "https://dev.azure.com/{0}/_apis/accesscontrollists/c788c23e-1b46-4162-8f5e-d7585343b5de?api-version=7.2-preview.1&token={1}/{2}/{3}" -f $Organization, $ProjectId, $Folder, $ReleaseDefinitionId
9293
}
9394
Write-Verbose "URI: $uri"
9495
$header = $script:connection.GetHeader()
@@ -167,7 +168,7 @@ Function Export-AzDevOpsReleaseDefinitions {
167168

168169
# Set the id to the to a hashtable of the organization, project and original id
169170
$id = @{
170-
originalId = $null
171+
originalId = $definitionId
171172
resourceName = $definitionName
172173
project = $Project
173174
organization = $Organization

src/PSRule.Rules.AzureDevOps/Functions/DevOps.Repos.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,14 +239,14 @@ Function Get-AzDevOpsRepositoryAcls {
239239
return $null
240240
} else {
241241
$header = $script:connection.GetHeader()
242-
$uri = "https://dev.azure.com/{0}/_apis/accesscontrollists/2e9eb7ed-3c0a-47d4-87c1-0ffdd275fd87?api-version=6.0" -f $Organization
242+
$uri = "https://dev.azure.com/{0}/_apis/accesscontrollists/2e9eb7ed-3c0a-47d4-87c1-0ffdd275fd87?api-version=7.2-preview.1&token=repoV2/{1}/{2}" -f $Organization, $ProjectId, $RepositoryId
243243
try {
244244
$response = Invoke-RestMethod -Uri $uri -Method Get -Headers $header -ContentType "application/json"
245245
# If the response is a string and not an object, throw an exception for authentication failure or project not found
246246
if ($response -is [string]) {
247247
throw "Authentication failed or project not found"
248248
}
249-
$thisRepoPerms = $response.value | where-object {($_.token -eq "repoV2/$($ProjectId)/$($RepositoryId)")}
249+
$thisRepoPerms = $response.value
250250
}
251251
catch {
252252
throw $_.Exception.Message

src/PSRule.Rules.AzureDevOps/Functions/DevOps.ServiceConnections.ps1

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,52 @@ function Get-AzDevOpsServiceConnectionChecks {
9191
Export-ModuleMember -Function Get-AzDevOpsServiceConnectionChecks
9292
# End of Function Get-AzDevOpsServiceConnectionChecks
9393

94+
<#
95+
.SYNOPSIS
96+
Get the Acls for an service connection from Azure DevOps project
97+
98+
.DESCRIPTION
99+
Get the Acls for an service connection from Azure DevOps project using Azure DevOps Rest API
100+
101+
.PARAMETER ProjectId
102+
Project Id for Azure DevOps
103+
104+
.PARAMETER ServiceConnectionId
105+
Service connection id for Azure DevOps
106+
107+
.EXAMPLE
108+
Get-AzDevOpsServiceConnectionAcls -ProjectId $ProjectId -ServiceConnectionId $ServiceConnectionId
109+
#>
110+
function Get-AzDevOpsServiceConnectionAcls {
111+
[CmdletBinding()]
112+
param (
113+
[Parameter(Mandatory)]
114+
[string]
115+
$ProjectId,
116+
[Parameter(Mandatory)]
117+
[string]
118+
$ServiceConnectionId
119+
)
120+
if ($null -eq $script:connection) {
121+
throw "Not connected to Azure DevOps. Run Connect-AzDevOps first"
122+
}
123+
$Organization = $script:connection.Organization
124+
$header = $script:connection.GetHeader()
125+
$uri = "https://dev.azure.com/{0}/_apis/accesscontrollists/49b48001-ca20-4adc-8111-5b60c903a50c?api-version=7.2-preview.1&token=endpoints/{1}/{2}" -f $Organization, $ProjectId, $ServiceConnectionId
126+
try {
127+
$response = Invoke-RestMethod -Uri $uri -Method Get -Headers $header
128+
# If the response is not an object but a string, the authentication failed
129+
if ($response -is [string]) {
130+
throw "Authentication failed or project not found"
131+
}
132+
}
133+
catch {
134+
throw $_.Exception.Message
135+
}
136+
return @($response.value)
137+
}
138+
Export-ModuleMember -Function Get-AzDevOpsServiceConnectionAcls
139+
94140
<#
95141
.SYNOPSIS
96142
Export all Azure Resource Manager service connections from Azure DevOps project with checks as nested objects
@@ -142,6 +188,10 @@ function Export-AzDevOpsServiceConnections {
142188
# Get checks for service connection
143189
$serviceConnectionChecks = @(Get-AzDevOpsServiceConnectionChecks -Project $Project -ServiceConnectionId $serviceConnection.id)
144190
$serviceConnection | Add-Member -MemberType NoteProperty -Name Checks -Value $serviceConnectionChecks
191+
# Get acls for service connection
192+
$serviceConnectionAcls = @(Get-AzDevOpsServiceConnectionAcls -ProjectId $serviceConnection.serviceEndpointProjectReferences[0].projectReference.id -ServiceConnectionId $serviceConnection.id)
193+
$serviceConnection | Add-Member -MemberType NoteProperty -Name Acls -Value $serviceConnectionAcls
194+
145195
# Set id field to a JSON object with originalId, project and organization
146196
$serviceConnection.id = @{
147197
originalId = $serviceConnection.id

src/PSRule.Rules.AzureDevOps/Functions/DevOps.Tasks.VariableGroups.ps1

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,53 @@ Function Get-AzDevOpsVariableGroups {
4141
Export-ModuleMember -Function Get-AzDevOpsVariableGroups
4242
# End of function Get-AzDevOpsVariableGroups
4343

44+
<#
45+
.SYNOPSIS
46+
Get Acls for a variable group from Azure DevOps.
47+
48+
.DESCRIPTION
49+
Get Acls for a variable group from Azure DevOps using the REST API.
50+
51+
.PARAMETER ProjectId
52+
The id of the Azure DevOps project.
53+
54+
.PARAMETER VariableGroupId
55+
The id of the Azure DevOps variable group.
56+
57+
.EXAMPLE
58+
Get-AzDevOpsVariableGroupAcls -ProjectId 'myproject' -VariableGroupId 1
59+
#>
60+
function Get-AzDevOpsVariableGroupAcls {
61+
[CmdletBinding()]
62+
param(
63+
[Parameter(Mandatory)]
64+
[string]$ProjectId,
65+
[Parameter(Mandatory)]
66+
[string]$VariableGroupId
67+
)
68+
if ($null -eq $script:connection) {
69+
throw "Not connected to Azure DevOps. Run Connect-AzDevOps first"
70+
}
71+
$Organization = $script:connection.Organization
72+
Write-Verbose "Getting variable group acls for project $ProjectId and variable group $VariableGroupId"
73+
$uri = "https://dev.azure.com/{0}/_apis/accesscontrollists/b7e84409-6553-448a-bbb2-af228e07cbeb?api-version=7.2-preview.1&token=Library/{1}/VariableGroup/{2}" -f $Organization, $ProjectId, $VariableGroupId
74+
Write-Verbose "URI: $uri"
75+
$header = $script:connection.GetHeader()
76+
# try to get the variable group acls, throw a descriptive error if it fails for authentication or other reasons
77+
try {
78+
$response = Invoke-RestMethod -Uri $uri -Method Get -Headers $header
79+
# If the response is not an object but a string, the authentication failed
80+
if ($response -is [string]) {
81+
throw "Authentication failed or project not found"
82+
}
83+
}
84+
catch {
85+
throw $_.Exception.Message
86+
}
87+
return @($response.value)
88+
}
89+
Export-ModuleMember -Function Get-AzDevOpsVariableGroupAcls
90+
4491
<#
4592
.SYNOPSIS
4693
Export variable groups to JSON files.
@@ -84,11 +131,15 @@ Function Export-AzDevOpsVariableGroups {
84131
throw "Not connected to Azure DevOps. Run Connect-AzDevOps first"
85132
}
86133
$Organization = $script:connection.Organization
134+
$ProjectId = (Get-AzDevOpsProject -Project $Project).id
87135
$variableGroups = Get-AzDevOpsVariableGroups -Project $Project
88136
$variableGroups | ForEach-Object {
89137
$variableGroup = $_
90138
$variableGroup | Add-Member -MemberType NoteProperty -Name 'ObjectType' -Value 'Azure.DevOps.Tasks.VariableGroup'
91139
$variableGroup | Add-Member -MemberType NoteProperty -Name 'ObjectName' -Value "$Organization.$Project.$($variableGroup.name)"
140+
# Add the variable group acls
141+
$variableGroupAcls = @(Get-AzDevOpsVariableGroupAcls -ProjectId $ProjectId -VariableGroupId $variableGroup.id)
142+
$variableGroup | Add-Member -MemberType NoteProperty -Name 'Acls' -Value $variableGroupAcls
92143
# Set the id to a JSON object with the originalId, project and organization
93144
$variableGroup.id = @{
94145
originalId = $variableGroup.id

0 commit comments

Comments
 (0)