Skip to content

Commit 72cd4fb

Browse files
Merge pull request #1 from Tools4everBV/feature-resource-creation-subpermissions
2 parents 7745531 + abf66b2 commit 72cd4fb

File tree

4 files changed

+584
-15
lines changed

4 files changed

+584
-15
lines changed

.DS_Store

6 KB
Binary file not shown.
Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,29 @@
1-
# Change Log
2-
3-
All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org).
4-
5-
## [1.0.0] - 27-05-2025
6-
7-
This is the first official release of _HelloID-Conn-Prov-Target-SDB-Identity_. This release is based on template version _v2.0.0_.
8-
9-
### Added
10-
11-
### Changed
12-
13-
### Deprecated
14-
15-
### Removed
1+
# Change Log
2+
3+
All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org).
4+
5+
## [1.0.1] - 19-12-2025
6+
7+
### Added
8+
9+
- Added resource creation (resource.ps1)
10+
- Added subpermissions (subPermissions.ps1)
11+
12+
### Changed
13+
14+
### Deprecated
15+
16+
### Removed
17+
18+
19+
## [1.0.0] - 27-05-2025
20+
21+
This is the first official release of _HelloID-Conn-Prov-Target-SDB-Identity_. This release is based on template version _v2.0.0_.
22+
23+
### Added
24+
25+
### Changed
26+
27+
### Deprecated
28+
29+
### Removed
Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
#####################################################
2+
# HelloID-Conn-Prov-Target-SDB-Identity-SubPermissions
3+
# Grants/revokes All groups
4+
# PowerShell V2
5+
#####################################################
6+
# Enable TLS1.2
7+
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12
8+
9+
# Script Mapping lookup values
10+
$contractCorrelationField = 'displayName'
11+
$contractCorrelationValue = { $_.custom.SDBGroupName }
12+
13+
# Determine all the sub-permissions that needs to be Granted/Updated/Revoked
14+
$currentPermissions = @{}
15+
foreach ($permission in $actionContext.CurrentPermissions) {
16+
$currentPermissions[$permission.Reference.Id] = $permission.DisplayName
17+
}
18+
19+
#region functions
20+
function Resolve-SDB-IdentityError {
21+
[CmdletBinding()]
22+
param (
23+
[Parameter(Mandatory)]
24+
[object]
25+
$ErrorObject
26+
)
27+
process {
28+
$httpErrorObj = [PSCustomObject]@{
29+
ScriptLineNumber = $ErrorObject.InvocationInfo.ScriptLineNumber
30+
Line = $ErrorObject.InvocationInfo.Line
31+
ErrorDetails = $ErrorObject.Exception.Message
32+
FriendlyMessage = $ErrorObject.Exception.Message
33+
}
34+
if (-not [string]::IsNullOrEmpty($ErrorObject.ErrorDetails.Message)) {
35+
$httpErrorObj.ErrorDetails = $ErrorObject.ErrorDetails.Message
36+
} elseif ($ErrorObject.Exception.GetType().FullName -eq 'System.Net.WebException') {
37+
if ($null -ne $ErrorObject.Exception.Response) {
38+
$streamReaderResponse = [System.IO.StreamReader]::new($ErrorObject.Exception.Response.GetResponseStream()).ReadToEnd()
39+
if (-not [string]::IsNullOrEmpty($streamReaderResponse)) {
40+
$httpErrorObj.ErrorDetails = $streamReaderResponse
41+
}
42+
}
43+
}
44+
try {
45+
$errorDetailsObject = ($httpErrorObj.ErrorDetails | ConvertFrom-Json)
46+
$httpErrorObj.FriendlyMessage = $errorDetailsObject.detail
47+
} catch {
48+
$httpErrorObj.FriendlyMessage = $httpErrorObj.ErrorDetails
49+
}
50+
Write-Output $httpErrorObj
51+
}
52+
}
53+
#endregion functions
54+
55+
try {
56+
57+
# Verify if [aRef] has a value
58+
if ([string]::IsNullOrEmpty($($actionContext.References.Account))) {
59+
throw 'The account reference could not be found'
60+
}
61+
62+
$headers = [System.Collections.Generic.Dictionary[string, string]]::new()
63+
$headers.Add('Authorization', "Bearer $($actionContext.Configuration.AccessToken)")
64+
$headers.Add('Accept', 'application/json')
65+
66+
Write-Information 'Verifying if a SDB-Identity account exists'
67+
$splatGetUser = @{
68+
Uri = "$($actionContext.Configuration.BaseUrl)/scim/Users/$($actionContext.References.Account)"
69+
Method = 'GET'
70+
Headers = $headers
71+
}
72+
try {
73+
$correlatedAccount = Invoke-RestMethod @splatGetUser
74+
} catch {
75+
if ($_.Exception.Response.StatusCode -eq 404) {
76+
Write-Information $_.Exception.Message
77+
} else {
78+
throw
79+
}
80+
}
81+
82+
if ($null -eq $correlatedAccount) {
83+
throw 'NotFound'
84+
}
85+
86+
Write-Information 'Retrieving permissions'
87+
$count = 100
88+
$startIndex = 1
89+
$retrievedPermissions = @()
90+
do {
91+
$splatGetPermissions = @{
92+
Uri = "$($actionContext.Configuration.BaseUrl)/scim/groups?startIndex=$startIndex&count=$count"
93+
Method = 'Get'
94+
Headers = $headers
95+
}
96+
$response = Invoke-RestMethod @splatGetPermissions
97+
98+
if ($response.Resources) {
99+
$retrievedPermissions += $response.Resources
100+
}
101+
102+
$totalResults = $response.totalResults
103+
$startIndex += $count
104+
} while ($retrievedPermissions.Count -lt $totalResults)
105+
106+
#region Define desired permissions
107+
$actionMessage = "calculating desired permission"
108+
109+
# Group on ExternalId to check if group exists (as correlation property has to be unique for a group)
110+
$groupsGrouped = $retrievedPermissions | Select id, displayName | Group-Object displayName -AsHashTable -AsString
111+
112+
$desiredPermissions = @{}
113+
if (-Not($actionContext.Operation -eq "revoke")) {
114+
# Example: Contract Based Logic:
115+
foreach ($contract in $personContext.Person.Contracts) {
116+
117+
$actionMessage = "calulating group for resource: $($resource | ConvertTo-Json)"
118+
119+
Write-Information "Contract: $($contract.ExternalId). In condition: $($contract.Context.InConditions)"
120+
if ($contract.Context.InConditions -OR ($actionContext.DryRun -eq $true)) {
121+
# Get group to use objectGuid to avoid name change issues
122+
$correlationField = $contractCorrelationField
123+
$correlationValue = $contractCorrelationValue
124+
125+
$group = $null
126+
$group = $groupsGrouped["$($correlationValue)"]
127+
128+
if (($group | Measure-Object).count -eq 0) {
129+
$outputContext.AuditLogs.Add([PSCustomObject]@{
130+
Action = "GrantPermission"
131+
Message = "No Group found where [$($correlationField)] = [$($correlationValue)]"
132+
IsError = $true
133+
})
134+
}
135+
elseif (($group | Measure-Object).count -gt 1) {
136+
$outputContext.AuditLogs.Add([PSCustomObject]@{
137+
Action = "GrantPermission"
138+
Message = "Multiple Groups found where [$($correlationField)] = [$($correlationValue)]. Please correct this so the groups are unique."
139+
IsError = $true
140+
})
141+
}
142+
else {
143+
# Add group to desired permissions with the id as key and the displayname as value (use id to avoid issues with name changes and for uniqueness)
144+
$desiredPermissions["$($group.id)"] = $group.DisplayName
145+
}
146+
}
147+
}
148+
}
149+
#endregion Define desired permissions
150+
151+
Write-Warning ("Desired Permissions: {0}" -f ($desiredPermissions.Values | ConvertTo-Json))
152+
Write-Warning ("Existing Permissions: {0}" -f ($actionContext.CurrentPermissions.DisplayName | ConvertTo-Json))
153+
154+
#region Compare current with desired permissions and revoke permissions
155+
$newCurrentPermissions = @{}
156+
foreach ($permission in $currentPermissions.GetEnumerator()) {
157+
if (-Not $desiredPermissions.ContainsKey($permission.Name) -AND $permission.Name -ne "No permissions defined") {
158+
#region Revoke permission
159+
# API docs: https://identitymanagement.services.iprova.nl/swagger-ui/#!/scim/PatchGroup
160+
$actionMessage = "revoking group [$($permission.Value)] with id [$($permission.Name)] to account with AccountReference: $($actionContext.References.Account | ConvertTo-Json)"
161+
162+
# Create permission body
163+
$revokePermissionBody = [PSCustomObject]@{
164+
schemas = @("urn:ietf:params:scim:api:messages:2.0:PatchOp")
165+
id = $permission.Name
166+
operations = @(
167+
@{
168+
op = "remove"
169+
path = "members"
170+
value = @(
171+
@{
172+
value = $actionContext.References.Account
173+
}
174+
)
175+
}
176+
)
177+
}
178+
179+
$revokePermissionSplatParams = @{
180+
Uri = "$($actionContext.Configuration.BaseUrl)/scim/groups/$($permission.Name)"
181+
Method = "PATCH"
182+
Body = ($revokePermissionBody | ConvertTo-Json -Depth 10)
183+
ContentType = 'application/json; charset=utf-8'
184+
Verbose = $false
185+
ErrorAction = "Stop"
186+
}
187+
188+
Write-Information "SplatParams: $($revokePermissionSplatParams | ConvertTo-Json)"
189+
190+
if (-Not($actionContext.DryRun -eq $true)) {
191+
# Add header after printing splat
192+
$revokePermissionSplatParams['Headers'] = $headers
193+
194+
$null = Invoke-RestMethod @revokePermissionSplatParams
195+
196+
$outputContext.AuditLogs.Add([PSCustomObject]@{
197+
Action = "RevokePermission"
198+
Message = "Revoked group [$($permission.Value)] with id [$($permission.Name)] to account with AccountReference: $($actionContext.References.Account | ConvertTo-Json)."
199+
IsError = $false
200+
})
201+
}
202+
else {
203+
Write-Warning "DryRun: Would revoke group [$($permission.Value)] with id [$($permission.Name)] to account with AccountReference: $($actionContext.References.Account | ConvertTo-Json)."
204+
}
205+
#endregion Revoke permission
206+
}
207+
else {
208+
$newCurrentPermissions[$permission.Name] = $permission.Value
209+
}
210+
}
211+
#endregion Compare current with desired permissions and revoke permissions
212+
213+
#region Compare desired with current permissions and grant permissions
214+
foreach ($permission in $desiredPermissions.GetEnumerator()) {
215+
$outputContext.SubPermissions.Add([PSCustomObject]@{
216+
DisplayName = $permission.Value
217+
Reference = [PSCustomObject]@{ Id = $permission.Name }
218+
})
219+
220+
if (-Not $currentPermissions.ContainsKey($permission.Name)) {
221+
#region Grant permission
222+
# API docs: https://identitymanagement.services.iprova.nl/swagger-ui/#!/scim/PatchGroup
223+
$actionMessage = "granting group [$($permission.Value)] with id [$($permission.Name)] to account with AccountReference: $($actionContext.References.Account | ConvertTo-Json)"
224+
225+
# Create permission body
226+
$grantPermissionBody = [PSCustomObject]@{
227+
schemas = @("urn:ietf:params:scim:api:messages:2.0:PatchOp")
228+
id = $permission.Name
229+
operations = @(
230+
@{
231+
op = "add"
232+
path = "members"
233+
value = @(
234+
@{
235+
value = $actionContext.References.Account
236+
}
237+
)
238+
}
239+
)
240+
}
241+
242+
$grantPermissionSplatParams = @{
243+
Uri = "$($actionContext.Configuration.BaseUrl)/scim/groups/$($permission.Name)"
244+
Method = "PATCH"
245+
Body = ($grantPermissionBody | ConvertTo-Json -Depth 10)
246+
ContentType = 'application/json; charset=utf-8'
247+
Verbose = $false
248+
ErrorAction = "Stop"
249+
}
250+
251+
Write-Information "SplatParams: $($grantPermissionSplatParams | ConvertTo-Json)"
252+
253+
if (-Not($actionContext.DryRun -eq $true)) {
254+
# Add header after printing splat
255+
$grantPermissionSplatParams['Headers'] = $headers
256+
257+
$null = Invoke-RestMethod @grantPermissionSplatParams
258+
259+
$outputContext.AuditLogs.Add([PSCustomObject]@{
260+
Action = "GrantPermission"
261+
Message = "Granted group [$($permission.Value)] with id [$($permission.Name)] to account with AccountReference: $($actionContext.References.Account | ConvertTo-Json)."
262+
IsError = $false
263+
})
264+
}
265+
else {
266+
Write-Warning "DryRun: Would grant group [$($permission.Value)] with id [$($permission.Name)] to account with AccountReference: $($actionContext.References.Account | ConvertTo-Json)."
267+
}
268+
#endregion Grant permission
269+
}
270+
}
271+
#endregion Compare desired with current permissions and grant permissions
272+
}
273+
catch {
274+
$ex = $PSItem
275+
if ($($ex.Exception.GetType().FullName -eq 'Microsoft.PowerShell.Commands.HttpResponseException') -or
276+
$($ex.Exception.GetType().FullName -eq 'System.Net.WebException')) {
277+
$errorObj = Resolve-SDB-IdentityError -Error $ex
278+
$auditMessage = "Error $($actionMessage). Error: $($errorObj.FriendlyMessage)"
279+
$warningMessage = "Error at Line [$($errorObj.ScriptLineNumber)]: $($errorObj.Line). Error: $($errorObj.ErrorDetails)"
280+
}
281+
else {
282+
$auditMessage = "Error $($actionMessage). Error: $($ex.Exception.Message)"
283+
$warningMessage = "Error at Line [$($ex.InvocationInfo.ScriptLineNumber)]: $($ex.InvocationInfo.Line). Error: $($ex.Exception.Message)"
284+
}
285+
286+
Write-Warning $warningMessage
287+
288+
$outputContext.AuditLogs.Add([PSCustomObject]@{
289+
# Action = "" # Optional
290+
Message = $auditMessage
291+
IsError = $true
292+
})
293+
}
294+
finally {
295+
# Handle case of empty defined dynamic permissions. Without this the entitlement will error.
296+
if ($actionContext.Operation -match "update|grant" -AND $outputContext.SubPermissions.count -eq 0) {
297+
$outputContext.SubPermissions.Add([PSCustomObject]@{
298+
DisplayName = "No permissions defined"
299+
Reference = [PSCustomObject]@{ Id = "No permissions defined" }
300+
})
301+
302+
Write-Warning "Skipped granting permissions for account with AccountReference: $($actionContext.References.Account | ConvertTo-Json). Reason: No permissions defined."
303+
}
304+
305+
# Check if auditLogs contains errors, if no errors are found, set success to true
306+
if (-NOT($outputContext.AuditLogs.IsError -contains $true)) {
307+
$outputContext.Success = $true
308+
}
309+
}

0 commit comments

Comments
 (0)