Skip to content

Commit 1b4a7a6

Browse files
committed
Entra Group Auth
1 parent fef71a1 commit 1b4a7a6

File tree

7 files changed

+464
-86
lines changed

7 files changed

+464
-86
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
function Get-CIPPAccessRole {
2+
<#
3+
.SYNOPSIS
4+
Get the access role for the current user
5+
6+
.DESCRIPTION
7+
Get the access role for the current user
8+
9+
.PARAMETER TenantID
10+
The tenant ID to check the access role for
11+
12+
.EXAMPLE
13+
Get-CippAccessRole -UserId $UserId
14+
15+
.FUNCTIONALITY
16+
Internal
17+
#>
18+
[CmdletBinding()]
19+
Param()
20+
21+
22+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
function Set-CIPPAccessRole {
2+
<#
3+
.SYNOPSIS
4+
Set the access role mappings
5+
6+
.DESCRIPTION
7+
Set the access role mappings for Entra groups
8+
9+
.PARAMETER Role
10+
The role to set (e.g. 'superadmin','admin','editor','readonly','customrole')
11+
12+
.PARAMETER Group
13+
The Entra group to set the role for
14+
15+
.FUNCTIONALITY
16+
Internal
17+
#>
18+
[CmdletBinding(SupportsShouldProcess = $true)]
19+
Param(
20+
[Parameter(Mandatory = $true)]
21+
[string]$Role,
22+
[Parameter(Mandatory = $true)]
23+
[string]$Group
24+
)
25+
26+
$BlacklistedRoles = @('authenticated', 'anonymous')
27+
28+
if ($BlacklistedRoles -contains $Role) {
29+
throw 'Role group cannot be set for authenticated or anonymous roles'
30+
}
31+
32+
if (!$Group.id -or !$Group.displayName) {
33+
throw 'Group is not valid'
34+
}
35+
36+
$Role = $Role.ToLower().Trim() -replace ' ', ''
37+
38+
$Table = Get-CippTable -TableName AccessRoleGroups
39+
$AccessGroup = Get-CIPPAzDataTableEntity @Table -Filter "RowKey = '$Role'"
40+
41+
$AccessGroup = [PSCustomObject]@{
42+
PartitionKey = [string]'AccessRole'
43+
RowKey = [string]$Role
44+
GroupId = [string]$Group.id
45+
GroupName = [string]$Group.displayName
46+
}
47+
48+
if ($PSCmdlet.ShouldProcess("Setting access role $Role for group $($Group.displayName)")) {
49+
Add-CIPPAzDataTableEntity -Table $Table -Entity $AccessGroup -Force
50+
}
51+
}

Modules/CIPPCore/Public/Authentication/Test-CIPPAccess.ps1

Lines changed: 176 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ function Test-CIPPAccess {
77

88
# Get function help
99
$FunctionName = 'Invoke-{0}' -f $Request.Params.CIPPEndpoint
10-
$Help = Get-Help $FunctionName
10+
11+
try {
12+
$Help = Get-Help $FunctionName -ErrorAction Stop
13+
} catch {}
1114

1215
# Check help for role
1316
$APIRole = $Help.Role
@@ -21,7 +24,27 @@ function Test-CIPPAccess {
2124
$CIPPRoot = (Get-Item $CIPPCoreModuleRoot).Parent.Parent
2225
$BaseRoles = Get-Content -Path $CIPPRoot\Config\cipp-roles.json | ConvertFrom-Json
2326

27+
if ($APIRole -eq 'Public') {
28+
return $true
29+
}
30+
31+
# Get default roles from config
32+
$CIPPCoreModuleRoot = Get-Module -Name CIPPCore | Select-Object -ExpandProperty ModuleBase
33+
$CIPPRoot = (Get-Item $CIPPCoreModuleRoot).Parent.Parent
34+
$BaseRoles = Get-Content -Path $CIPPRoot\Config\cipp-roles.json | ConvertFrom-Json
35+
$DefaultRoles = @('superadmin', 'admin', 'editor', 'readonly', 'anonymous', 'authenticated')
36+
37+
if ($APIRole -eq 'Public') {
38+
return $true
39+
}
40+
41+
# Get default roles from config
42+
$CIPPCoreModuleRoot = Get-Module -Name CIPPCore | Select-Object -ExpandProperty ModuleBase
43+
$CIPPRoot = (Get-Item $CIPPCoreModuleRoot).Parent.Parent
44+
$BaseRoles = Get-Content -Path $CIPPRoot\Config\cipp-roles.json | ConvertFrom-Json
45+
2446
if ($Request.Headers.'x-ms-client-principal-idp' -eq 'aad' -and $Request.Headers.'x-ms-client-principal-name' -match '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$') {
47+
$Type = 'APIClient'
2548
# Direct API Access
2649
$ForwardedFor = $Request.Headers.'x-forwarded-for' -split ',' | Select-Object -First 1
2750
$IPRegex = '^(?<IP>(?:\d{1,3}(?:\.\d{1,3}){3}|\[[0-9a-fA-F:]+\]|[0-9a-fA-F:]+))(?::\d+)?$'
@@ -44,7 +67,20 @@ function Test-CIPPAccess {
4467

4568
if ($IPMatched) {
4669
if ($Client.Role) {
47-
$CustomRoles = @($Client.Role)
70+
$CustomRoles = $Client.Role | ForEach-Object {
71+
if ($DefaultRoles -notcontains $_) {
72+
$_
73+
}
74+
}
75+
$BaseRole = $null
76+
foreach ($Role in $BaseRoles.PSObject.Properties) {
77+
foreach ($ClientRole in $Client.Role) {
78+
if ($Role.Name -eq $ClientRole) {
79+
$BaseRole = $Role
80+
break
81+
}
82+
}
83+
}
4884
} else {
4985
$CustomRoles = @('cipp-api')
5086
}
@@ -56,73 +92,163 @@ function Test-CIPPAccess {
5692
Write-Information "API Access: AppId=$($Request.Headers.'x-ms-client-principal-name'), IP=$IPAddress"
5793
}
5894
} else {
59-
$DefaultRoles = @('admin', 'editor', 'readonly', 'anonymous', 'authenticated')
95+
$Type = 'User'
6096
$User = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Request.Headers.'x-ms-client-principal')) | ConvertFrom-Json
6197

62-
if (!$TenantList.IsPresent -and $APIRole -match 'SuperAdmin' -and $User.userRoles -notcontains 'superadmin') {
63-
throw 'Access to this CIPP API endpoint is not allowed, the user does not have the required permission'
98+
# Check for roles granted via group membership
99+
if (($User.userRoles | Measure-Object).Count -eq 2 -and $User.userRoles -contains 'authenticated' -and $User.userRoles -contains 'anonymous') {
100+
$User = Test-CIPPAccessUserRole -User $User
101+
}
102+
103+
Write-Information ($User | ConvertTo-Json -Depth 5)
104+
# Return user permissions
105+
if ($Request.Params.CIPPEndpoint -eq 'me') {
106+
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
107+
StatusCode = [HttpStatusCode]::OK
108+
Body = (@{ 'clientPrincipal' = $User } | ConvertTo-Json -Depth 5)
109+
})
110+
return
64111
}
65112

66113
if ($User.userRoles -contains 'admin' -or $User.userRoles -contains 'superadmin') {
67114
if ($TenantList.IsPresent) {
68115
return @('AllTenants')
69116
}
70-
return $true
71117
}
72118

73119
$CustomRoles = $User.userRoles | ForEach-Object {
74120
if ($DefaultRoles -notcontains $_) {
75121
$_
76122
}
77123
}
124+
125+
$BaseRole = $null
126+
foreach ($Role in $BaseRoles.PSObject.Properties) {
127+
foreach ($UserRole in $User.userRoles) {
128+
if ($Role.Name -eq $UserRole) {
129+
$BaseRole = $Role
130+
break
131+
}
132+
}
133+
}
78134
}
79-
if (($CustomRoles | Measure-Object).Count -gt 0) {
80-
$Tenants = Get-Tenants -IncludeErrors
81-
$PermissionsFound = $false
82-
$PermissionSet = foreach ($CustomRole in $CustomRoles) {
83-
try {
84-
Get-CIPPRolePermissions -Role $CustomRole
85-
$PermissionsFound = $true
86-
} catch {
87-
Write-Information $_.Exception.Message
88-
continue
135+
136+
# Check base role permissions before continuing to custom roles
137+
if ($null -ne $BaseRole) {
138+
Write-Information "Base Role: $($BaseRole.Name)"
139+
$BaseRoleAllowed = $false
140+
foreach ($Include in $BaseRole.Value.include) {
141+
if ($APIRole -like $Include) {
142+
$BaseRoleAllowed = $true
143+
break
89144
}
90145
}
91-
if ($PermissionsFound) {
92-
if ($TenantList.IsPresent) {
93-
$LimitedTenantList = foreach ($Permission in $PermissionSet) {
94-
if ((($Permission.AllowedTenants | Measure-Object).Count -eq 0 -or $Permission.AllowedTenants -contains 'AllTenants') -and (($Permission.BlockedTenants | Measure-Object).Count -eq 0)) {
95-
@('AllTenants')
96-
} else {
97-
if ($Permission.AllowedTenants -contains 'AllTenants') {
98-
$Permission.AllowedTenants = $Tenants.customerId
146+
foreach ($Exclude in $BaseRole.Value.exclude) {
147+
if ($APIRole -like $Exclude) {
148+
$BaseRoleAllowed = $false
149+
break
150+
}
151+
}
152+
if (!$BaseRoleAllowed) {
153+
throw "Access to this CIPP API endpoint is not allowed, the '$($BaseRole.Name)' base role does not have the required permission: $APIRole"
154+
}
155+
}
156+
157+
# Check custom role permissions for limitations on api calls or tenants
158+
if ($null -eq $BaseRole.Name -and $Type -eq 'User' -and ($CustomRoles | Measure-Object).Count -eq 0) {
159+
Write-Information $BaseRole.Name
160+
throw 'Access to this CIPP API endpoint is not allowed, the user does not have the required permission'
161+
} elseif (($CustomRoles | Measure-Object).Count -gt 0) {
162+
if (@('admin', 'superadmin') -contains $BaseRole.Name) {
163+
return $true
164+
} else {
165+
$Tenants = Get-Tenants -IncludeErrors
166+
$PermissionsFound = $false
167+
$PermissionSet = foreach ($CustomRole in $CustomRoles) {
168+
try {
169+
Get-CIPPRolePermissions -Role $CustomRole
170+
$PermissionsFound = $true
171+
} catch {
172+
Write-Information $_.Exception.Message
173+
continue
174+
}
175+
}
176+
if ($PermissionsFound) {
177+
if ($TenantList.IsPresent) {
178+
$LimitedTenantList = foreach ($Permission in $PermissionSet) {
179+
if ((($Permission.AllowedTenants | Measure-Object).Count -eq 0 -or $Permission.AllowedTenants -contains 'AllTenants') -and (($Permission.BlockedTenants | Measure-Object).Count -eq 0)) {
180+
@('AllTenants')
181+
} else {
182+
if ($Permission.AllowedTenants -contains 'AllTenants') {
183+
$Permission.AllowedTenants = $Tenants.customerId
184+
}
185+
$Permission.AllowedTenants | Where-Object { $Permission.BlockedTenants -notcontains $_ }
99186
}
100-
$Permission.AllowedTenants | Where-Object { $Permission.BlockedTenants -notcontains $_ }
101187
}
188+
return $LimitedTenantList
102189
}
103-
return $LimitedTenantList
104-
}
105-
foreach ($Role in $PermissionSet) {
106-
# Loop through each custom role permission and check API / Tenant access
190+
107191
$TenantAllowed = $false
108192
$APIAllowed = $false
193+
foreach ($Role in $PermissionSet) {
194+
foreach ($Perm in $Role.Permissions) {
195+
if ($Perm -match $APIRole) {
196+
$APIAllowed = $true
197+
break
198+
}
199+
}
109200

110-
foreach ($Perm in $Role.Permissions) {
111-
if ($Perm -match $APIRole) {
112-
$APIAllowed = $true
113-
break
201+
if ($APIAllowed) {
202+
$TenantFilter = $Request.Query.tenantFilter ?? $Request.Body.tenantFilter ?? $Request.Body.tenantFilter.value ?? $Request.Query.tenantId ?? $Request.Body.tenantId ?? $Request.Body.tenantId.value ?? $env:TenantID
203+
# Check tenant level access
204+
if (($Role.BlockedTenants | Measure-Object).Count -eq 0 -and $Role.AllowedTenants -contains 'AllTenants') {
205+
$TenantAllowed = $true
206+
} elseif ($TenantFilter -eq 'AllTenants') {
207+
$TenantAllowed = $false
208+
} else {
209+
$Tenant = ($Tenants | Where-Object { $TenantFilter -eq $_.customerId -or $TenantFilter -eq $_.defaultDomainName }).customerId
210+
if ($Role.AllowedTenants -contains 'AllTenants') {
211+
$AllowedTenants = $Tenants.customerId
212+
} else {
213+
$AllowedTenants = $Role.AllowedTenants
214+
}
215+
if ($Tenant) {
216+
$TenantAllowed = $AllowedTenants -contains $Tenant -and $Role.BlockedTenants -notcontains $Tenant
217+
if (!$TenantAllowed) { continue }
218+
break
219+
} else {
220+
$TenantAllowed = $true
221+
break
222+
}
223+
}
114224
}
115225
}
116226

227+
if (!$APIAllowed) {
228+
throw "Access to this CIPP API endpoint is not allowed, you do not have the required permission: $APIRole"
229+
}
230+
if (!$TenantAllowed -and $Help.Functionality -notmatch 'AnyTenant') {
231+
throw 'Access to this tenant is not allowed'
232+
} else {
233+
return $true
234+
}
235+
} else {
236+
# No permissions found for any roles
237+
if ($TenantList.IsPresent) {
238+
return @('AllTenants')
239+
}
240+
return $true
117241
if ($APIAllowed) {
118242
$TenantFilter = $Request.Query.tenantFilter ?? $Request.Body.tenantFilter ?? $Request.Query.tenantId ?? $Request.Body.tenantId ?? $env:TenantID
119243
# Check tenant level access
120244
if (($Role.BlockedTenants | Measure-Object).Count -eq 0 -and $Role.AllowedTenants -contains 'AllTenants') {
121245
$TenantAllowed = $true
122246
} elseif ($TenantFilter -eq 'AllTenants') {
247+
123248
$TenantAllowed = $false
124249
} else {
125250
$Tenant = ($Tenants | Where-Object { $TenantFilter -eq $_.customerId -or $TenantFilter -eq $_.defaultDomainName }).customerId
251+
126252
if ($Role.AllowedTenants -contains 'AllTenants') {
127253
$AllowedTenants = $Tenants.customerId
128254
} else {
@@ -140,14 +266,19 @@ function Test-CIPPAccess {
140266
}
141267
}
142268

143-
if (!$APIAllowed) {
144-
throw "Access to this CIPP API endpoint is not allowed, you do not have the required permission: $APIRole"
145-
}
146269
if (!$TenantAllowed -and $Help.Functionality -notmatch 'AnyTenant') {
147-
Write-Information "Tenant not allowed: $TenantFilter"
148-
throw 'Access to this tenant is not allowed'
149-
} else {
150-
return $true
270+
271+
if (!$APIAllowed) {
272+
throw "Access to this CIPP API endpoint is not allowed, you do not have the required permission: $APIRole"
273+
}
274+
if (!$TenantAllowed -and $Help.Functionality -notmatch 'AnyTenant') {
275+
Write-Information "Tenant not allowed: $TenantFilter"
276+
277+
throw 'Access to this tenant is not allowed'
278+
} else {
279+
return $true
280+
}
281+
151282
}
152283
} else {
153284
# No permissions found for any roles
@@ -156,7 +287,10 @@ function Test-CIPPAccess {
156287
}
157288
return $true
158289
}
159-
} else {
160-
return $true
161290
}
291+
292+
if ($TenantList.IsPresent) {
293+
return @('AllTenants')
294+
}
295+
return $true
162296
}

0 commit comments

Comments
 (0)