Skip to content

Commit e380a7c

Browse files
committed
feat: enhance sensitivity label handling with advanced settings
1 parent 9c1879c commit e380a7c

5 files changed

Lines changed: 127 additions & 0 deletions

File tree

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
function ConvertTo-CIPPExoHashtable {
2+
<#
3+
.SYNOPSIS
4+
Convert a value into the hashtable shape the EXO AdminApi accepts for PswsHashtable parameters.
5+
.DESCRIPTION
6+
Hashtable-typed cmdlet parameters (e.g. Set-Label -AdvancedSettings, New-LabelPolicy -Settings)
7+
sent over the AdminApi REST endpoint deserialize server-side as Newtonsoft JObjects, which the
8+
parameter binder cannot convert to PswsHashtable. Tagging the object with
9+
'@odata.type' = '#Exchange.GenericHashTable' makes the binder accept it - the same convention
10+
used elsewhere in CIPP for MultiValuedProperty add/remove hashtables.
11+
12+
Accepts a dictionary, a PSCustomObject (deserialized JSON object), or an array of key/value
13+
pairs in either the {Key, Value} object shape or the [key, value] pair shape used by label
14+
policy Settings in template JSON.
15+
.PARAMETER InputObject
16+
The value to convert.
17+
.FUNCTIONALITY
18+
Internal
19+
#>
20+
[CmdletBinding()]
21+
param(
22+
[Parameter(Mandatory)] $InputObject
23+
)
24+
25+
$Result = @{ '@odata.type' = '#Exchange.GenericHashTable' }
26+
27+
if ($InputObject -is [System.Collections.IDictionary]) {
28+
foreach ($Key in @($InputObject.Keys)) {
29+
if ($Key -ne '@odata.type') { $Result[$Key] = $InputObject[$Key] }
30+
}
31+
} elseif ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string]) {
32+
foreach ($Entry in $InputObject) {
33+
if ($null -eq $Entry) { continue }
34+
if ($Entry -isnot [string] -and $Entry.PSObject.Properties['Key']) {
35+
$Result[$Entry.Key] = $Entry.Value
36+
} elseif ($Entry -is [System.Collections.IList] -and $Entry.Count -ge 2) {
37+
$Result["$($Entry[0])"] = $Entry[1]
38+
}
39+
}
40+
} else {
41+
foreach ($Prop in $InputObject.PSObject.Properties) {
42+
if ($Prop.Name -ne '@odata.type') { $Result[$Prop.Name] = $Prop.Value }
43+
}
44+
}
45+
46+
return $Result
47+
}

Modules/CIPPCore/Public/ConvertTo-CIPPSensitivityLabelParams.ps1

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ function ConvertTo-CIPPSensitivityLabelParams {
1515
arrays (which are not valid input in their read form). A flat object (manual JSON authored against
1616
the deploy schema) has no 'LabelActions' and passes through unchanged.
1717
18+
Applied -AdvancedSettings values (e.g. a custom label color) are only readable through the same
19+
read-only 'Settings' array, so before dropping it the writable advanced settings are lifted into
20+
an 'AdvancedSettings' dictionary that New-/Set-Label accept. An explicit 'AdvancedSettings' value
21+
already on the template wins over captured values.
22+
1823
Deploy-time validation/allowlisting still happens in Set-CIPPSensitivityLabel via
1924
Get-CIPPSensitivityLabelField; this function only reshapes.
2025
.PARAMETER Label
@@ -42,6 +47,39 @@ function ConvertTo-CIPPSensitivityLabelParams {
4247
return [pscustomobject]$Flat
4348
}
4449

50+
# Writable advanced settings that Get-Label only reports inside the read-only Settings array
51+
# ([key, value] pairs). The rest of Settings is system metadata (displayname, contenttype,
52+
# tooltip, ...) that must not be echoed back to New-/Set-Label. Extend this list as more
53+
# -AdvancedSettings keys gain first-class support.
54+
$WritableAdvancedSettings = @('color')
55+
$CapturedAdvanced = @{}
56+
foreach ($Entry in @($Label.Settings)) {
57+
if ($null -eq $Entry) { continue }
58+
$Key = $null
59+
$Value = $null
60+
if ($Entry -isnot [string] -and $Entry.PSObject.Properties['Key']) {
61+
$Key = $Entry.Key
62+
$Value = $Entry.Value
63+
} elseif ("$Entry" -match '^\[\s*(.+?)\s*,\s*(.*?)\s*\]$') {
64+
# Get-Label serializes each entry as the string '[key, value]'
65+
$Key = $Matches[1]
66+
$Value = $Matches[2]
67+
}
68+
if ($Key -and $Key.ToLower() -in $WritableAdvancedSettings -and -not [string]::IsNullOrWhiteSpace("$Value")) {
69+
$CapturedAdvanced[$Key.ToLower()] = "$Value"
70+
}
71+
}
72+
if ($CapturedAdvanced.Count -gt 0) {
73+
# Explicit AdvancedSettings on the template win over values captured from Settings.
74+
$Explicit = $Flat['AdvancedSettings']
75+
if ($Explicit -is [System.Collections.IDictionary]) {
76+
foreach ($ExplicitKey in @($Explicit.Keys)) { $CapturedAdvanced[$ExplicitKey] = $Explicit[$ExplicitKey] }
77+
} elseif ($null -ne $Explicit) {
78+
foreach ($ExplicitProp in $Explicit.PSObject.Properties) { $CapturedAdvanced[$ExplicitProp.Name] = $ExplicitProp.Value }
79+
}
80+
$Flat['AdvancedSettings'] = $CapturedAdvanced
81+
}
82+
4583
foreach ($Raw in @($Label.LabelActions)) {
4684
if ($null -eq $Raw) { continue }
4785
$Action = if ($Raw -is [string]) { $Raw | ConvertFrom-Json } else { $Raw }

Modules/CIPPCore/Public/Set-CIPPSensitivityLabel.ps1

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ function Set-CIPPSensitivityLabel {
3636
$PolicySource = $Template.PolicyParams
3737
$LabelName = $LabelParams.Name
3838

39+
# PswsHashtable parameters need the Exchange.GenericHashTable odata type to bind over the AdminApi.
40+
if ($LabelParams.ContainsKey('AdvancedSettings')) {
41+
$LabelParams['AdvancedSettings'] = ConvertTo-CIPPExoHashtable -InputObject $LabelParams['AdvancedSettings']
42+
}
43+
3944
# Priority is valid on Set-Label but not New-Label, so it is applied via a dedicated Set-Label call below.
4045
$LabelPriority = $null
4146
if ($LabelParams.ContainsKey('Priority')) {
@@ -44,6 +49,16 @@ function Set-CIPPSensitivityLabel {
4449
}
4550

4651
try {
52+
# A custom label color travels as the 'color' advanced setting. Validate the hex format up front
53+
# so a bad value fails with a clear message instead of an opaque compliance-endpoint error.
54+
# An empty string is valid: it clears a previously set color.
55+
if ($LabelParams.ContainsKey('AdvancedSettings')) {
56+
$ColorValue = $LabelParams['AdvancedSettings']['color']
57+
if (-not [string]::IsNullOrEmpty("$ColorValue") -and "$ColorValue" -notmatch '^#[0-9A-Fa-f]{6}$') {
58+
throw "Invalid label color '$ColorValue' in the AdvancedSettings of '$LabelName'. Use a 6-digit hex color like #40E0D0."
59+
}
60+
}
61+
4762
$ExistingLabels = try { New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-Label' -Compliance | Select-Object Name, DisplayName } catch { @() }
4863
$ExistingLabelPolicies = try { New-ExoRequest -tenantid $TenantFilter -cmdlet 'Get-LabelPolicy' -Compliance | Select-Object Name } catch { @() }
4964

@@ -71,6 +86,13 @@ function Set-CIPPSensitivityLabel {
7186

7287
if ($PolicySource) {
7388
$PolicyHash = Format-CIPPCompliancePolicyParams -Source $PolicySource -AllowedFields $PolicyAllowedFields
89+
# Settings/AdvancedSettings are PswsHashtable on New-/Set-LabelPolicy; template JSON authors
90+
# Settings as [key, value] pairs, which the helper also normalizes.
91+
foreach ($HashtableParam in @('AdvancedSettings', 'Settings')) {
92+
if ($PolicyHash.ContainsKey($HashtableParam)) {
93+
$PolicyHash[$HashtableParam] = ConvertTo-CIPPExoHashtable -InputObject $PolicyHash[$HashtableParam]
94+
}
95+
}
7496
if (-not $PolicyHash.ContainsKey('Labels') -or -not $PolicyHash['Labels']) {
7597
$PolicyHash['Labels'] = @($LabelName)
7698
}

Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Security/Compliance-SensitivityLabel/Invoke-EditSensitivityLabel.ps1

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ Function Invoke-EditSensitivityLabel {
2323
}
2424
}
2525

26+
# PswsHashtable parameters need the Exchange.GenericHashTable odata type to bind over the AdminApi.
27+
foreach ($HashtableParam in @('AdvancedSettings', 'Settings')) {
28+
if ($Params.ContainsKey($HashtableParam)) {
29+
$Params[$HashtableParam] = ConvertTo-CIPPExoHashtable -InputObject $Params[$HashtableParam]
30+
}
31+
}
32+
2633
$null = New-ExoRequest -tenantid $TenantFilter -cmdlet 'Set-Label' -cmdParams $Params -Compliance -useSystemMailbox $true
2734
$Result = "Updated sensitivity label $Identity"
2835
Write-LogMessage -Headers $Request.Headers -API $APIName -tenant $TenantFilter -message $Result -Sev Info

Modules/CIPPHTTP/Public/Entrypoints/HTTP Functions/Security/Compliance-SensitivityLabel/Invoke-ListSensitivityLabel.ps1

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,19 @@ Function Invoke-ListSensitivityLabel {
2020
$labelGuid = $_.Guid
2121
@($Policies | Where-Object { $_.Labels -contains $labelGuid -or $_.Labels -contains $_.ImmutableId }) | Select-Object -ExpandProperty Name
2222
}
23+
},
24+
@{l = 'Color'; e = {
25+
# The 'color' advanced setting is only exposed inside the read-only Settings array,
26+
# either as a {Key, Value} object or as the serialized string '[color, #RRGGBB]'.
27+
foreach ($Entry in @($_.Settings)) {
28+
if ($null -eq $Entry) { continue }
29+
if ($Entry -isnot [string] -and $Entry.PSObject.Properties['Key']) {
30+
if ("$($Entry.Key)" -eq 'color') { "$($Entry.Value)"; break }
31+
} elseif ("$Entry" -match '^\[\s*color\s*,\s*(.*?)\s*\]$') {
32+
$Matches[1]; break
33+
}
34+
}
35+
}
2336
}
2437

2538
$StatusCode = [HttpStatusCode]::OK

0 commit comments

Comments
 (0)