Private/Invoke-GuacPatch.ps1
|
#Requires -Version 5.1 function Get-GuacPatchOperation { <# .SYNOPSIS Builds a single RFC 6902 JSON Patch operation. .DESCRIPTION Constructs the ordered hashtable form of one JSON Patch operation, matching the wire format Guacamole deserializes into APIPatch<T>: { "op": <operation>, "path": <path>, "value": <value> }. The "value" key is always included (it may be $null). Guacamole's DirectoryResource.patchObjects omits "value" entirely for "remove" operations, and its RelatedObjectSetResource / PermissionSetResource PATCH endpoints require a string "value", so callers must supply the correct value shape per endpoint: - collection PATCH (connections, users, ...): value is the object for add/replace and $null for remove - permission set PATCH: value is the permission type string (for example "UPDATE" or "ADMINISTER") - related-object-set PATCH (memberUsers, userGroups, ...): value is the identifier string Reference (Apache Guacamole 1.6.0): - guacamole/src/main/java/org/apache/guacamole/rest/jsonpatch/APIPatch.java (op, path, value) - guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryResource.java (patchObjects: add/replace/remove) - guacamole/src/main/java/org/apache/guacamole/rest/permission/PermissionSetResource.java (patchPermissions: permission type as value) - guacamole/src/main/java/org/apache/guacamole/rest/identifier/RelatedObjectSetResource.java (patchObjects: identifier as value, path "/") This function is private to the N2C.GuacAdmin module. #> [CmdletBinding()] [OutputType([hashtable])] param ( [Parameter(Mandatory = $true)] [ValidateSet('add', 'remove', 'replace')] [string] $Op, [Parameter(Mandatory = $true, Position = 0)] [string] $Path, [Parameter(Mandatory = $false)] [AllowNull()] [object] $Value = $null ) if ([string]::IsNullOrWhiteSpace($Path) -or -not $Path.StartsWith('/')) { throw (New-Object System.ArgumentException( 'A JSON Patch path must be non-empty and start with "/" (RFC 6902).', 'Path')) } return [ordered]@{ op = $Op path = $Path value = $Value } } function ConvertTo-GuacJsonPatch { <# .SYNOPSIS Normalizes JSON Patch input into an RFC 6902 operations array. .DESCRIPTION Accepts the -Patch parameter values the public cmdlets expose and returns an array of ordered hashtables with the canonical wire form { op, path, value }: - a single ordered/regular hashtable with an "op" key - an array of hashtables (one operation each) Guards against the Windows PowerShell 5.1 pipeline quirk where a single-element array arrives as its lone element: the input is always re-wrapped as an array before validation. Throws on empty input, on a hashtable without an "op" key, and on an operation type other than add/replace/remove (the operations DirectoryResource.patchObjects supports; RFC 6902 also defines test/copy/move, which Guacamole's collection PATCH rejects). Reference (Apache Guacamole 1.6.0): - guacamole/src/main/java/org/apache/guacamole/rest/jsonpatch/APIPatch.java - guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryResource.java (patchObjects: "Only add, replace, and remove are supported.") This function is private to the N2C.GuacAdmin module. #> [CmdletBinding()] [OutputType([System.Collections.ArrayList])] param ( [Parameter(Mandatory = $true, Position = 0)] [AllowNull()] [object] $Patch ) $operations = @($Patch) if ($operations.Count -eq 0) { throw (New-Object System.ArgumentException( 'A patch must contain at least one operation.', 'Patch')) } $result = [System.Collections.ArrayList]::new() foreach ($item in $operations) { if ($null -eq $item) { throw (New-Object System.ArgumentException( 'A patch operation cannot be null.', 'Patch')) } if ($item -is [System.Collections.IDictionary]) { $dict = $item } elseif ($item.PSObject.Properties.Match('op').Count -gt 0) { $dict = [ordered]@{} foreach ($prop in $item.PSObject.Properties) { $dict[$prop.Name] = $prop.Value } } else { throw (New-Object System.ArgumentException( ('Each patch operation must be a hashtable with an "op" key; got: {0}' -f $item.GetType().Name), 'Patch')) } if ($dict.Count -eq 0 -or -not $dict.Contains('op')) { throw (New-Object System.ArgumentException( 'Each patch operation must include an "op" key (add, replace, or remove).', 'Patch')) } $op = [string]$dict['op'] switch ($op.ToLowerInvariant()) { 'add' { } 'replace' { } 'remove' { } default { throw (New-Object System.ArgumentException( ("Unsupported patch operation '{0}': Guacamole collection PATCH supports add, replace, and remove." -f $op), 'Patch')) } } if (-not $dict.Contains('path')) { throw (New-Object System.ArgumentException( ('Each patch operation must include a "path" key; operation "{0}" has none.' -f $op), 'Patch')) } $path = [string]$dict['path'] if ([string]::IsNullOrWhiteSpace($path) -or -not $path.StartsWith('/')) { throw (New-Object System.ArgumentException( ('A JSON Patch path must be non-empty and start with "/" (RFC 6902): "{0}"' -f $path), 'Patch')) } $normalized = [ordered]@{ op = $op.ToLowerInvariant() path = $path value = $null } if ($dict.Contains('value')) { $normalized['value'] = $dict['value'] } [void]$result.Add($normalized) } return $result } function Invoke-GuacPatch { <# .SYNOPSIS Applies a JSON Patch (RFC 6902) to a Guacamole collection endpoint. .DESCRIPTION Wraps Invoke-GuacRest for the collection mutation endpoints: serializes an array of RFC 6902 operations (as normalized by ConvertTo-GuacJsonPatch) to JSON and sends it as PATCH. DirectoryResource.patchObjects responds with an APIPatchResponse (a JSON object: { "patches": [ { "op", "identifier", "path" }, ... ] }) and returns HTTP 400 (APIPatchFailureException) when any operation fails, in which case Invoke-GuacRest converts it into a terminating GuacRestException carrying the parsed body. The PermissionSetResource and RelatedObjectSetResource PATCH endpoints return an empty body (HTTP 204/200), for which this function returns $true. The returned object is the decoded response body (an object on both Windows PowerShell 5.1 and 7.x), or $true for an empty response. Reference (Apache Guacamole 1.6.0): - guacamole/src/main/java/org/apache/guacamole/rest/directory/DirectoryResource.java (patchObjects -> APIPatchResponse) - guacamole/src/main/java/org/apache/guacamole/rest/jsonpatch/APIPatchResponse.java (patches list) - guacamole/src/main/java/org/apache/guacamole/rest/permission/PermissionSetResource.java (patchPermissions: void) - guacamole/src/main/java/org/apache/guacamole/rest/identifier/RelatedObjectSetResource.java (patchObjects: void) This function is private to the N2C.GuacAdmin module. #> [CmdletBinding()] [OutputType([object])] param ( [Parameter(Mandatory = $true)] [hashtable] $Context, [Parameter(Mandatory = $true)] [string] $Path, [Parameter(Mandatory = $true)] [AllowNull()] [object] $Patch ) $operations = ConvertTo-GuacJsonPatch -Patch $Patch $body = ConvertTo-GuacJson -InputObject $operations return (Invoke-GuacRest -Server $Context['Server'] -Token $Context['Token'] ` -Method PATCH -Path $Path -Body $body -ContentType 'application/json') } |