Private/Invoke-EntraOpsClassificationActionOverwrite.ps1
|
function Invoke-EntraOpsClassificationActionOverwrite { <# .SYNOPSIS Applies role action classification overwrites to a classification definition (tier structure) before it is written as tenant-specific classification file. .DESCRIPTION Shared helper used by Update-EntraOpsClassificationControlPlaneScope to bake role action overwrites from Classification_RoleActionOverwrites.json into the generated tenant-specific classification files. The classification logic in the Get-EntraOpsPrivilegedEAM* cmdlets consumes the adjusted files without any runtime overwrite handling. For every overwritten role action: - The action is removed from all TierLevelDefinition entries in ALL tiers whose RoleAssignmentScopeName overlaps with the overwrite scope (wildcard-aware in both directions) AND whose ActionType (defaults to "Action" when the property is absent) matches the overwrite's ActionType. This avoids duplicated classification entries for the same action, e.g. when an action exists in a scoped Control Plane entry and a "/*" Management Plane entry, the overwrite consolidates it into a single entry - and never removes/merges an action into an entry of the other plane (Action vs DataAction), since Azure RBAC keeps those as separate namespaces. - The action is added exactly once to the target tier: an existing entry with the same Service, ActionType, identical RoleAssignmentScopeName and no scope exclusions is reused, otherwise a new entry is created. Service and Category are taken from the overwrite definition or, if not defined, inherited from the entries the action was removed from ("Custom Classification" as fallback); ActionType is taken from the overwrite definition (defaults to "Action") and is only written onto a newly created entry when it is "DataAction", keeping "Action" entries identical to the pre-existing convention of omitting the property. - Entries that are left without any role actions are removed afterwards. For ApiPermissions classification files (entries carrying ResourceAppId/ResourceScope), the target entry uses ResourceAppId/ResourceScope explicitly defined on the overwrite (Import-EntraOpsClassificationOverwrites) when present. Otherwise they are inherited from the removed entries when the removed entries share a single distinct value, otherwise ResourceAppId is set to $null (matches any resource app) and ResourceScope to "All". When the overwrite defines an explicit ResourceAppId and/or ResourceScope, only entries whose ResourceAppId/ResourceScope overlap (empty/"All" counts as a wildcard) are considered for removal/reuse, so overwrites for one resource app/permission type do not affect others. .PARAMETER ClassificationDefinition Parsed classification definition (array of tier objects with TierLevelDefinition) as read from a Classification_*.Param.json file after placeholder substitution. .PARAMETER RoleActionOverwrites Normalized role action overwrite definitions from Import-EntraOpsClassificationOverwrites, including an ActionType property ("Action" or "DataAction", defaulting to "Action") describing which plane the overwritten actions belong to. .OUTPUTS [array] Updated classification definition. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [array]$ClassificationDefinition, [Parameter(Mandatory = $false)] [AllowEmptyCollection()] [array]$RoleActionOverwrites ) if ($null -eq $RoleActionOverwrites -or $RoleActionOverwrites.Count -eq 0) { return , $ClassificationDefinition } $AppliedOverwrites = [System.Collections.Generic.List[object]]::new() # Detect ApiPermissions schema (entries carry ResourceAppId/ResourceScope) $HasResourceProperties = $false foreach ($Tier in $ClassificationDefinition) { foreach ($Definition in @($Tier.TierLevelDefinition)) { if ($null -ne $Definition.PSObject.Properties['ResourceScope']) { $HasResourceProperties = $true break } } if ($HasResourceProperties) { break } } foreach ($Overwrite in $RoleActionOverwrites) { $OverwriteScopes = @($Overwrite.RoleAssignmentScopeName) # Only meaningful for ApiPermissions schema (ResourceApps). $null/empty means "any resource app"/"any # permission type" and preserves the previous inference-only behavior. $OverwriteResourceAppId = $Overwrite.ResourceAppId $OverwriteResourceScope = $Overwrite.ResourceScope # Plane of the overwritten actions. Defaults to "Action" both when the overwrite entry omits ActionType # and for schemas (ApiPermissions) which have no Action/DataAction concept at all. $OverwriteActionType = if ("$($Overwrite.ActionType)" -eq 'DataAction') { 'DataAction' } else { 'Action' } # Resolve the target tier BEFORE mutating anything. The removal loop below strips the action from # every matching tier, so resolving afterwards meant an unresolvable tier name (e.g. a typo, or a # non-standard name accepted because an explicit EAMTierLevelTagValue was supplied) left the action # removed everywhere and re-added nowhere - silently unclassified tenant-wide. $TargetTier = $ClassificationDefinition | Where-Object { $_.EAMTierLevelName -eq $Overwrite.EAMTierLevelName } | Select-Object -First 1 if ($null -eq $TargetTier) { Write-Warning "Skipping role action overwrite for '$(@($Overwrite.RoleDefinitionActions) -join ', ')': Tier level '$($Overwrite.EAMTierLevelName)' not found in classification definition. Classification left unchanged." continue } foreach ($Action in $Overwrite.RoleDefinitionActions) { # Remove the action from every entry in all tiers with overlapping scope (and, for ApiPermissions # with an explicit ResourceAppId/ResourceScope on the overwrite, overlapping resource app/permission # type) so that only a single classification entry for the action remains after the overwrite $RemovedFrom = [System.Collections.Generic.List[object]]::new() foreach ($Tier in $ClassificationDefinition) { foreach ($Definition in @($Tier.TierLevelDefinition)) { $DefinitionActionType = if ("$($Definition.ActionType)" -eq 'DataAction') { 'DataAction' } else { 'Action' } if ($DefinitionActionType -ne $OverwriteActionType) { continue } if (@($Definition.RoleDefinitionActions) -notcontains $Action) { # The action is not listed literally, but a wildcard pattern in this entry can still # match it at runtime (runtime matching is wildcard based, this removal pass is not). # Removing nothing here and adding the action to the target tier leaves an object # matching BOTH entries, so the overwrite silently has no effect. Warn rather than # fail quietly - the author needs to narrow the pattern or overwrite it directly. if ($Tier.EAMTierLevelName -ne $Overwrite.EAMTierLevelName) { $WildcardCover = @($Definition.RoleDefinitionActions | Where-Object { -not [string]::IsNullOrEmpty($_) -and $_ -ne $Action -and $Action -like $_ }) if ($WildcardCover.Count -gt 0) { Write-Warning "Role action overwrite for '$Action' may not take effect: tier '$($Tier.EAMTierLevelName)' (service '$($Definition.Service)') classifies it through wildcard pattern(s) '$($WildcardCover -join ', ')'. Narrow the pattern or overwrite the pattern directly." } } continue } # Direction of the scope overlap decides remove vs. exclude. Treating both directions as # "remove" made a narrowly scoped overwrite delete the action from a broadly scoped entry, # unclassifying it everywhere outside the overwrite scope. $OverwriteCoversDefinition = $false # overwrite scope equal/broader -> remove outright $DefinitionCoversOverwrite = $false # overwrite scope narrower -> exclude that scope foreach ($DefinitionScope in @($Definition.RoleAssignmentScopeName)) { foreach ($OverwriteScope in $OverwriteScopes) { if ($DefinitionScope -like $OverwriteScope) { $OverwriteCoversDefinition = $true } elseif ($OverwriteScope -like $DefinitionScope) { $DefinitionCoversOverwrite = $true } } } if (-not $OverwriteCoversDefinition -and -not $DefinitionCoversOverwrite) { continue } if ($HasResourceProperties -and -not [string]::IsNullOrEmpty($OverwriteResourceAppId) -and -not [string]::IsNullOrEmpty($Definition.ResourceAppId) -and "$($Definition.ResourceAppId)" -ne "$OverwriteResourceAppId") { continue } if ($HasResourceProperties -and -not [string]::IsNullOrEmpty($OverwriteResourceScope) -and -not [string]::IsNullOrEmpty($Definition.ResourceScope) -and "$($Definition.ResourceScope)" -ne "All" -and "$OverwriteResourceScope" -ne "All" -and "$($Definition.ResourceScope)" -ne "$OverwriteResourceScope") { continue } $RemovedFromEntry = [PSCustomObject]@{ 'TierLevelName' = $Tier.EAMTierLevelName 'Category' = $Definition.Category 'Service' = $Definition.Service 'ActionType' = $DefinitionActionType 'ResourceAppId' = $Definition.ResourceAppId 'ResourceScope' = $Definition.ResourceScope } if (-not $OverwriteCoversDefinition) { # Overwrite scope is narrower than this entry's scope. The entry stays valid for every # other scope it covers, so keep the action classified here and exclude only the # overwritten scope(s) instead of deleting the action from the entry entirely. if ($Tier.EAMTierLevelName -ne $Overwrite.EAMTierLevelName) { $ExistingExclusions = @($Definition.ExcludedRoleAssignmentScopeName | Where-Object { -not [string]::IsNullOrEmpty($_) }) $NewExclusions = @($OverwriteScopes | Where-Object { $ExistingExclusions -notcontains $_ }) if ($NewExclusions.Count -gt 0) { $MergedExclusions = @($ExistingExclusions) + @($NewExclusions) if ($null -eq $Definition.PSObject.Properties['ExcludedRoleAssignmentScopeName']) { $Definition | Add-Member -NotePropertyName 'ExcludedRoleAssignmentScopeName' -NotePropertyValue $MergedExclusions -Force } else { $Definition.ExcludedRoleAssignmentScopeName = $MergedExclusions } } } $RemovedFrom.Add($RemovedFromEntry) | Out-Null continue } # ApiPermissions only: when the overwrite narrows to a single permission type but the matched # entry covers both ("All"), removing the action outright unclassifies the complementary # permission type. Preserve it at the original tier by splitting the entry first. if ($HasResourceProperties -and "$($Definition.ResourceScope)" -eq "All" -and -not [string]::IsNullOrEmpty($OverwriteResourceScope) -and "$OverwriteResourceScope" -ne "All") { $ComplementaryScope = if ("$OverwriteResourceScope" -eq "Application") { "Delegated" } else { "Application" } $ComplementaryDefinition = $null foreach ($SiblingDefinition in @($Tier.TierLevelDefinition)) { if ($SiblingDefinition.Service -ne $Definition.Service) { continue } if ("$($SiblingDefinition.ResourceScope)" -ne $ComplementaryScope) { continue } if ("$($SiblingDefinition.ResourceAppId)" -ne "$($Definition.ResourceAppId)") { continue } $SiblingActionType = if ("$($SiblingDefinition.ActionType)" -eq 'DataAction') { 'DataAction' } else { 'Action' } if ($SiblingActionType -ne $DefinitionActionType) { continue } $ComplementaryDefinition = $SiblingDefinition break } if ($null -eq $ComplementaryDefinition) { $ComplementaryDefinition = [PSCustomObject][ordered]@{ 'Category' = $Definition.Category 'Service' = $Definition.Service 'ResourceAppId' = $Definition.ResourceAppId 'ResourceScope' = $ComplementaryScope 'RoleAssignmentScopeName' = @($Definition.RoleAssignmentScopeName) 'RoleDefinitionActions' = @() } $Tier.TierLevelDefinition = @($Tier.TierLevelDefinition) + @($ComplementaryDefinition) } if (@($ComplementaryDefinition.RoleDefinitionActions) -notcontains $Action) { $ComplementaryDefinition.RoleDefinitionActions = @($ComplementaryDefinition.RoleDefinitionActions) + @($Action) Write-Verbose "Split '$Action' out of ResourceScope 'All' in tier '$($Tier.EAMTierLevelName)': kept '$ComplementaryScope' at the original tier before applying the '$OverwriteResourceScope' overwrite." } } $Definition.RoleDefinitionActions = @($Definition.RoleDefinitionActions | Where-Object { $_ -ne $Action }) $RemovedFrom.Add($RemovedFromEntry) | Out-Null } } # Service/Category: explicit overwrite definition first, then inherited from the entries # the action was removed from (target tier preferred), "Custom Classification" as fallback $RemovedFromTargetTier = @($RemovedFrom | Where-Object { $_.TierLevelName -eq $Overwrite.EAMTierLevelName }) $TargetService = if (-not [string]::IsNullOrEmpty($Overwrite.Service)) { $Overwrite.Service } elseif ($RemovedFromTargetTier.Count -gt 0) { $RemovedFromTargetTier[0].Service } elseif ($RemovedFrom.Count -gt 0) { $RemovedFrom[0].Service } else { "Custom Classification" } $TargetCategory = if ($RemovedFromTargetTier.Count -gt 0) { $RemovedFromTargetTier[0].Category } elseif ($RemovedFrom.Count -gt 0) { $RemovedFrom[0].Category } else { "ClassificationOverwrite" } # ResourceAppId/ResourceScope (ApiPermissions schema only): explicit value on the overwrite first, # otherwise inherited from the entries the action was removed from when they share a single distinct # value, otherwise $null (any resource app) / "All" (any permission type). $TargetResourceAppId = $null $TargetResourceScope = $null if ($HasResourceProperties) { $TargetResourceAppId = if (-not [string]::IsNullOrEmpty($OverwriteResourceAppId)) { $OverwriteResourceAppId } else { $DistinctResourceAppIds = @($RemovedFrom.ResourceAppId | Where-Object { -not [string]::IsNullOrEmpty($_) } | Select-Object -Unique) if ($DistinctResourceAppIds.Count -eq 1) { $DistinctResourceAppIds[0] } else { $null } } $TargetResourceScope = if (-not [string]::IsNullOrEmpty($OverwriteResourceScope)) { $OverwriteResourceScope } else { $DistinctResourceScopes = @($RemovedFrom.ResourceScope | Where-Object { -not [string]::IsNullOrEmpty($_) } | Select-Object -Unique) if ($DistinctResourceScopes.Count -eq 1) { $DistinctResourceScopes[0] } else { "All" } } } # Reuse an existing entry with same service, same ActionType (plane), identical scopes, no exclusions # and (for ApiPermissions) the same resolved resource app/permission type, otherwise create a new one $TargetDefinition = $null foreach ($Definition in @($TargetTier.TierLevelDefinition)) { if ($Definition.Service -ne $TargetService) { continue } $DefinitionActionType = if ("$($Definition.ActionType)" -eq 'DataAction') { 'DataAction' } else { 'Action' } if ($DefinitionActionType -ne $OverwriteActionType) { continue } if (@($Definition.ExcludedRoleAssignmentScopeName | Where-Object { -not [string]::IsNullOrEmpty($_) }).Count -gt 0) { continue } $DefinitionScopes = @($Definition.RoleAssignmentScopeName) if ($DefinitionScopes.Count -ne $OverwriteScopes.Count) { continue } if (Compare-Object -ReferenceObject $DefinitionScopes -DifferenceObject $OverwriteScopes) { continue } if ($HasResourceProperties) { if ("$($Definition.ResourceAppId)" -ne "$TargetResourceAppId") { continue } if ("$($Definition.ResourceScope)" -ne "$TargetResourceScope") { continue } } $TargetDefinition = $Definition break } if ($null -eq $TargetDefinition) { $NewDefinition = [ordered]@{ 'Category' = $TargetCategory 'Service' = $TargetService } # Only written when "DataAction" - keeps newly created Action-plane entries identical in shape to # the pre-existing convention of omitting the property (which implies "Action"). if ($OverwriteActionType -eq 'DataAction') { $NewDefinition['ActionType'] = 'DataAction' } if ($HasResourceProperties) { $NewDefinition['ResourceAppId'] = $TargetResourceAppId $NewDefinition['ResourceScope'] = $TargetResourceScope } $NewDefinition['RoleAssignmentScopeName'] = $OverwriteScopes $NewDefinition['RoleDefinitionActions'] = @() $TargetDefinition = [PSCustomObject]$NewDefinition $TargetTier.TierLevelDefinition = @($TargetTier.TierLevelDefinition) + @($TargetDefinition) } if (@($TargetDefinition.RoleDefinitionActions) -notcontains $Action) { $TargetDefinition.RoleDefinitionActions = @($TargetDefinition.RoleDefinitionActions) + @($Action) $AppliedOverwrites.Add([PSCustomObject]@{ Tier = $Overwrite.EAMTierLevelName SourceFile = $Overwrite.SourceFile }) | Out-Null } } } # Remove entries which are left without any role actions after the overwrite pass foreach ($Tier in $ClassificationDefinition) { $Tier.TierLevelDefinition = @($Tier.TierLevelDefinition | Where-Object { @($_.RoleDefinitionActions | Where-Object { -not [string]::IsNullOrEmpty($_) }).Count -gt 0 }) } if ($AppliedOverwrites.Count -gt 0) { Write-Host " Role action overwrites applied:" -ForegroundColor Yellow $AppliedOverwrites | Group-Object Tier, SourceFile | Sort-Object Name | ForEach-Object { $Example = $_.Group[0] Write-Host " $($Example.Tier): $($_.Count) role action overwrite(s) from $($Example.SourceFile)" -ForegroundColor Yellow } } return , $ClassificationDefinition } # SIG # Begin signature block # MIIniAYJKoZIhvcNAQcCoIIneTCCJ3UCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAV7pMF8AH5Ycvq # jhsWcM6O0d7FjVU/5hoMintBe5sSCaCCIDwwggXJMIIEsaADAgECAhAbtY8lKt8j # AEkoya49fu0nMA0GCSqGSIb3DQEBDAUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK # ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy # dGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5l # dHdvcmsgQ0EwHhcNMjEwNTMxMDY0MzA2WhcNMjkwOTE3MDY0MzA2WjCBgDELMAkG # A1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAl # BgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMb # Q2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMIICIjANBgkqhkiG9w0BAQEFAAOC # Ag8AMIICCgKCAgEAvfl4+ObVgAxknYYblmRnPyI6HnUBfe/7XGeMycxca6mR5rlC # 5SBLm9qbe7mZXdmbgEvXhEArJ9PoujC7Pgkap0mV7ytAJMKXx6fumyXvqAoAl4Va # qp3cKcniNQfrcE1K1sGzVrihQTib0fsxf4/gX+GxPw+OFklg1waNGPmqJhCrKtPQ # 0WeNG0a+RzDVLnLRxWPa52N5RH5LYySJhi40PylMUosqp8DikSiJucBb+R3Z5yet # /5oCl8HGUJKbAiy9qbk0WQq/hEr/3/6zn+vZnuCYI+yma3cWKtvMrTscpIfcRnNe # GWJoRVfkkIJCu0LW8GHgwaM9ZqNd9BjuiMmNF0UpmTJ1AjHuKSbIawLmtWJFfzcV # WiNoidQ+3k4nsPBADLxNF8tNorMe0AZa3faTz1d1mfX6hhpneLO/lv403L3nUlbl # s+V1e9dBkQXcXWnjlQ1DufyDljmVe2yAWk8TcsbXfSl6RLpSpCrVQUYJIP4ioLZb # MI28iQzV13D4h1L92u+sUS4Hs07+0AnacO+Y+lbmbdu1V0vc5SwlFcieLnhO+Nqc # noYsylfzGuXIkosagpZ6w7xQEmnYDlpGizrrJvojybawgb5CAKT41v4wLsfSRvbl # jnX98sy50IdbzAYQYLuDNbdeZ95H7JlI8aShFf6tjGKOOVVPORa5sWOd/7cCAwEA # AaOCAT4wggE6MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLahVDkCw6A/joq8 # +tT4HKbROg79MB8GA1UdIwQYMBaAFAh2zcsH/yT2xc3tu5C84oQ3RnX3MA4GA1Ud # DwEB/wQEAwIBBjAvBgNVHR8EKDAmMCSgIqAghh5odHRwOi8vY3JsLmNlcnR1bS5w # bC9jdG5jYS5jcmwwawYIKwYBBQUHAQEEXzBdMCgGCCsGAQUFBzABhhxodHRwOi8v # c3ViY2Eub2NzcC1jZXJ0dW0uY29tMDEGCCsGAQUFBzAChiVodHRwOi8vcmVwb3Np # dG9yeS5jZXJ0dW0ucGwvY3RuY2EuY2VyMDkGA1UdIAQyMDAwLgYEVR0gADAmMCQG # CCsGAQUFBwIBFhhodHRwOi8vd3d3LmNlcnR1bS5wbC9DUFMwDQYJKoZIhvcNAQEM # BQADggEBAFHCoVgWIhCL/IYx1MIy01z4S6Ivaj5N+KsIHu3V6PrnCA3st8YeDrJ1 # BXqxC/rXdGoABh+kzqrya33YEcARCNQOTWHFOqj6seHjmOriY/1B9ZN9DbxdkjuR # mmW60F9MvkyNaAMQFtXx0ASKhTP5N+dbLiZpQjy6zbzUeulNndrnQ/tjUoCFBMQl # lVXwfqefAcVbKPjgzoZwpic7Ofs4LphTZSJ1Ldf23SIikZbr3WjtP6MZl9M7JYjs # NhI9qX7OAo0FmpKnJ25FspxihjcNpDOO16hO0EoXQ0zF8ads0h5YbBRRfopUofbv # n3l6XYGaFpAP4bvxSgD5+d2+7arszgowggZrMIIEU6ADAgECAhBX5gfP8LabvnxC # XoItEBa/MA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhB # c3NlY28gRGF0YSBTeXN0ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBDb2RlIFNp # Z25pbmcgMjAyMSBDQTAeFw0yNjEwMDYwODI4MjNaFw0yNzEwMDYwODI4MjJaMIGQ # MQswCQYDVQQGEwJERTEdMBsGA1UECAwUUmhpbmVsYW5kLVBhbGF0aW5hdGUxEjAQ # BgNVBAcMCUxhaG5zdGVpbjEeMBwGA1UECgwVT3BlbiBTb3VyY2UgRGV2ZWxvcGVy # MS4wLAYDVQQDDCVPcGVuIFNvdXJjZSBEZXZlbG9wZXIgVGhvbWFzIE5hdW5oZWlt # MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAphp2ljSmVsRB6gvkEq7A # DUMENpKZiDFoAfF1ktD1nCwaVyI+bJ0ROjHwAn4iJgS+vlBDPOUIw2GLHkiqUhOg # HSwzg6gj45GNmt6zxsKP0zPlhMB/fb0zmz7/F7e3yfKCXLfhbHuk0Es9gJ+8Wmet # 9StVwwSE/K0MpW06GKA9nPWyE4N/ADwigDIj9rsTKJR4cve9w8G1b3zohpUc/qMr # onab+HVw5r9b3sOBnnaXC6zvrX4fovRz1ztwfWjRjILLoFG6mW/TuK5F2ICD3Rhl # 9R2rJWWWhzTDMsQlzvX+/SaM74q3OFpIVNdhvtmWU9pKUBRAyYMijcDqKEFGSzrc # 8JFmGMpP5TuroWF+N21d3px2eARvW1mV0DCfFAH+Og20SH/gVW6/ltQQEkH/z/Zt # 49ARyJrgESLOQtlMCIYSoKISdkk4ml4VUS1E/PcbzXEYj3a7m6DOxcd3WuY03UT9 # cnk3p0LMN6LeLYAZgVY+HRloZlH+kW2SkOHNuWgtXvZhAgMBAAGjggF4MIIBdDAM # BgNVHRMBAf8EAjAAMD0GA1UdHwQ2MDQwMqAwoC6GLGh0dHA6Ly9jY3NjYTIwMjEu # Y3JsLmNlcnR1bS5wbC9jY3NjYTIwMjEuY3JsMHMGCCsGAQUFBwEBBGcwZTAsBggr # BgEFBQcwAYYgaHR0cDovL2Njc2NhMjAyMS5vY3NwLWNlcnR1bS5jb20wNQYIKwYB # BQUHMAKGKWh0dHA6Ly9yZXBvc2l0b3J5LmNlcnR1bS5wbC9jY3NjYTIwMjEuY2Vy # MB8GA1UdIwQYMBaAFN10XUwA23ufoHTKsW73PMAywHDNMB0GA1UdDgQWBBTBvGyH # MXj1dMbVnxuof9oP4PY7cDBLBgNVHSAERDBCMAgGBmeBDAEEATA2BgsqhGgBhvZ3 # AgUBBDAnMCUGCCsGAQUFBwIBFhlodHRwczovL3d3dy5jZXJ0dW0ucGwvQ1BTMBMG # A1UdJQQMMAoGCCsGAQUFBwMDMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsF # AAOCAgEAkC165c7/r6y3VeEEpNXas9bQs6ZpKPdJffHkf9xoPi4kWvAIWGc4ocG1 # DJLb18RXiDYShDmT5sb/6I08FeipKi2DNJkuOq6yOTJ3S7+hNFIZCyd6i7WKO8ea # P5I+FAbyJP4+5coZoMg45bUa9JoocaJCm+5n7WiCfN7/EQpClW5Trm4+ZMbp7EMN # F1uLJgGCMnN0SsoUarp7PHlqupMxMC4Uhhe+lAP2ytcSIU81L97/qjrADbLWqICk # 9il5iGM+aZndguq/cmfH9rJj4GRZHWnG6WwnzYD9+KgYlRI6em+/9dq+IRiXe2Bs # thUuIaFW7U2gWw8tBTEYm1UBLHdJx+L1gjJCLs5KT/wy5wU32wZxNDqPUQejUC1x # GvU3cKqmLgRq+yNf3gFVfzqIoggyM3U9wQvnk7nBtCkpjXS2hdx4yxzcmrlWsDHr # z6+6/jeyQySzbYUlFmKnc4n9Mw2soHOajEua5poFARVRS5KgUBuiiQ21YFNYzWBo # qGuECaaAI/C5rGxapMJ3dAOBnHw6NLqBet+ZXBJEccX5y89MLlOQu+jDmJP8HieR # qDLlwfhVZnw4RY8Rus43NZBwhNlVuOUXqocHKmJhKNlekda3qsJj93eQBQOxKJcc # w5O08q9jukpH2+yzFAj+7ll33DuNUgWptogc4Awtbx3glzcEvJQwggaCMIIEaqAD # AgECAhAo8HfBHDa9/l90MkdwJy4DMA0GCSqGSIb3DQEBDAUAMFYxCzAJBgNVBAYT # AlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0ZW1zIFMuQS4xJDAiBgNVBAMT # G0NlcnR1bSBUaW1lc3RhbXBpbmcgMjAyMSBDQTAeFw0yNjAzMTEwNzM0NTRaFw0z # NjAyMjcwNzM0NTRaMFAxCzAJBgNVBAYTAlBMMSEwHwYDVQQKDBhBc3NlY28gRGF0 # YSBTeXN0ZW1zIFMuQS4xHjAcBgNVBAMMFUNlcnR1bSBUaW1lc3RhbXAgMjAyNjCC # AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi9zfBw+xI12hi2F9uNQBNO # STkTIi13fuh3DiWybI5392bBfJw6tO7zDs7UqP/rejVf4xFTU3XNOPsuOFkP5SPF # snWwn3VJL+NhNhsuujvYsQtaQnvNo1wk46reTgws1L8OFEdKaxgyqT4zOQx81mLw # NZSqXNU9Rrb9oAJWJF4Mydz+C1ebu0D0+vgM3tqAQ8TU203fqgKCPqIdHloaFqH9 # eyR+8tnpmy2hnnN59ZOtmsuHeRwf5iTOhlhf0d/qqquLOSIGPgGVL1Hj5XBUox2O # E7tQRr+l/xxNbL6OuwBO4Aa2EbuxYg37cpXv5i2/lTL4NASB/duf9W4fjA3Ro+GP # +bC54n2OYiM3Cq8z92ejx8F5HWO2e4/75eC95B9tMw/eq7109GDyW2+2ut5Bn0o8 # MXJTERkyystWbqcnUkomA97qfiDCwvpWuOMdfTOksXe9S+xDxOJaCbiyTivgPulx # cT+tfQTRXBr5UwtPvc+INnafOEilzVHA11tv+pHcylryxBc08cUihf/ttqkZS0bm # x3VxQay8uohaTp0//ECA9vYGW51SKdJMJhXvKyIdBLy8WYNchTt8yRITv9Adxg9J # qGujh+TnP1bW3aQjFtlEl7SzgNeI8OVA/CJAWWUuRT3Au9ToOpHy/flrrknhN4he # Ew7/6ILMv4HSsj0YZ4otAgMBAAGjggFQMIIBTDB1BggrBgEFBQcBAQRpMGcwOwYI # KwYBBQUHMAKGL2h0dHA6Ly9zdWJjYS5yZXBvc2l0b3J5LmNlcnR1bS5wbC9jdHNj # YTIwMjEuY2VyMCgGCCsGAQUFBzABhhxodHRwOi8vc3ViY2Eub2NzcC1jZXJ0dW0u # Y29tMB8GA1UdIwQYMBaAFL5UAi+/QGxzQ86sCSVOnkNEGu7gMAwGA1UdEwEB/wQC # MAAwOQYDVR0fBDIwMDAuoCygKoYoaHR0cDovL3N1YmNhLmNybC5jZXJ0dW0ucGwv # Y3RzY2EyMDIxLmNybDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAOBgNVHQ8BAf8E # BAMCB4AwIgYDVR0gBBswGTAIBgZngQwBBAIwDQYLKoRoAYb2dwIFAQswHQYDVR0O # BBYEFCM5aiirmhKnpKfyH/lFmr+N/oIDMA0GCSqGSIb3DQEBDAUAA4ICAQBm/pOb # rAveTqYFPCSthxNVwBHTW4TfuBQY2KGCdMlH6ALjVPYf2XARNGMTXScR0JQ2/c2L # xLpoHHhRwxFf3d933DhFS165rYj6iPSQKm41rc9AMpTCjMdmai1aK9o870pOzTzL # RhWFVIxm+5Qf+t4V5z14tPoUsAWdfRKD1Zzvzfe5AHlE9rxOZcGROiULxs3Edf7Z # J8RZI0jzD9juj2oEX2XS7BVKuNCK1W0h1mjapv028yqo8UtZh5/1Ib48Yyp88BDY # Shh5PSGUUuS5tYXVfcduIPBe3PdNTYRYdT5VNxkwLgjMzIcr/6SH2fejFJDjnCxj # vXLuarErJg/IdI+hXdGIBDVOGv1zrkF4ktoQvFZIp5DIAYh0GwyUhc87qqn5UaYT # j2+iyiBDSNh7aGCrkuAnqg7Qh7frhX+QhMGeJ9SpLpnG76qN85hLyjvYR6gRYiLT # j8G4fxjqV400/buPw0rBo9yJZyq/8cGdCOLPOYf9qpefTcrZKeO9pg+UJHx8UABb # aTqizRxm8sUSEycccCCFhIit3AI/3wGuSOADeiA676NcQ7+dsP04YvzvdN7pTZNy # g672WpBDJdg0eItHl4yomvNeIOWh1qs4QqOW6TI8Ocsi4uSDTLorxLUDnBTwKQzz # pxd1YUctEgRh32R0J9FkFkeEruJU94RljFr1uDCCBrkwggShoAMCAQICEQCZo4AK # JlU7ZavcboSms+o5MA0GCSqGSIb3DQEBDAUAMIGAMQswCQYDVQQGEwJQTDEiMCAG # A1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVt # IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3Rl # ZCBOZXR3b3JrIENBIDIwHhcNMjEwNTE5MDUzMjE4WhcNMzYwNTE4MDUzMjE4WjBW # MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu # MSQwIgYDVQQDExtDZXJ0dW0gQ29kZSBTaWduaW5nIDIwMjEgQ0EwggIiMA0GCSqG # SIb3DQEBAQUAA4ICDwAwggIKAoICAQCdI88EMCM7wUYs5zNzPmNdenW6vlxNur3r # Lfi+5OZ+U3iZIB+AspO+CC/bj+taJUbMbFP1gQBJUzDUCPx7BNLgid1TyztVLn52 # NKgxxu8gpyTr6EjWyGzKU/gnIu+bHAse1LCitX3CaOE13rbuHbtrxF2tPU8f253Q # gX6eO8yTbGps1Mg+yda3DcTsOYOhSYNCJiL+5wnjZ9weoGRtvFgMHtJg6i671OPX # IciiHO4Lwo2p9xh/tnj+JmCQEn5QU0NxzrOiRna4kjFaA9ZcwSaG7WAxeC/xoZSx # F1oK1UPZtKVt+yrsGKqWONoK6f5EmBOAVEK2y4ATDSkb34UD7JA32f+Rm0wsr5aj # zftDhA5mBipVZDjHpwzv8bTKzCDUSUuUmPo1govD0RwFcTtMXcfJtm1i+P2UNXad # PyYVKRxKQATHN3imsfBiNRdN5kiVVeqP55piqgxOkyt+HkwIA4gbmSc3hD8ke66t # 9MjlcNg73rZZlrLHsAIV/nJ0mmgSjBI/TthoGJDydekOQ2tQD2Dup/+sKQptalDl # ui59SerVSJg8gAeV7N/ia4mrGoiez+SqV3olVfxyLFt3o/OQOnBmjhKUANoKLYlK # mUpKEFI0PfoT8Q1W/y6s9LTI6ekbi0igEbFUIBE8KDUGfIwnisEkBw5KcBZ3XwnH # mfznwlKo8QIDAQABo4IBVTCCAVEwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU # 3XRdTADbe5+gdMqxbvc8wDLAcM0wHwYDVR0jBBgwFoAUtqFUOQLDoD+Oirz61Pgc # ptE6Dv0wDgYDVR0PAQH/BAQDAgEGMBMGA1UdJQQMMAoGCCsGAQUFBwMDMDAGA1Ud # HwQpMCcwJaAjoCGGH2h0dHA6Ly9jcmwuY2VydHVtLnBsL2N0bmNhMi5jcmwwbAYI # KwYBBQUHAQEEYDBeMCgGCCsGAQUFBzABhhxodHRwOi8vc3ViY2Eub2NzcC1jZXJ0 # dW0uY29tMDIGCCsGAQUFBzAChiZodHRwOi8vcmVwb3NpdG9yeS5jZXJ0dW0ucGwv # Y3RuY2EyLmNlcjA5BgNVHSAEMjAwMC4GBFUdIAAwJjAkBggrBgEFBQcCARYYaHR0 # cDovL3d3dy5jZXJ0dW0ucGwvQ1BTMA0GCSqGSIb3DQEBDAUAA4ICAQB1iFgP5Y9Q # KJpTnxDsQ/z0O23JmoZifZdEOEmQvo/79PQg9nLF/GJe6ZiUBEyDBHMtFRK0mXj3 # Qv3gL0sYXe+PPMfwmreJHvgFGWQ7XwnfMh2YIpBrkvJnjwh8gIlNlUl4KENTK5DL # qsYPEtRQCw7R6p4s2EtWyDDr/M58iY2UBEqfUU/ujR9NuPyKk0bEcEi62JGxauFY # zZ/yld13fHaZskIoq2XazjaD0pQkcQiIueL0HKiohS6XgZuUtCKA7S6CHttZEsOb # QJ1j2s0urIDdqF7xaXFVaTHKtAuMfwi0jXtF3JJphrJfc+FFILgCbX/uYBPBlbBI # P4Ht4xxk2GmfzMn7oxPITpigQFJFWuzTMUUgdRHTxaTSKRJ/6Uh7ki/pFjf9sUAS # WgxT69QF9Ki4JF5nBIujxZ2sOU9e1HSCJwOfK07t5nnzbs1LbHuAIGJsRJiQ6HX/ # DW1XFOlXY1rc9HufFhWU+7Uk+hFkJsfzqBz3pRO+5aI6u5abI4Qws4YaeJH7H7M8 # X/YNoaArZbV4Ql+jarKsE0+8XvC4DJB+IVcvC9Ydqahi09mjQse4fxfef0L7E3hh # o2O3bLDM6v60rIRUCi2fJT2/IRU5ohgyTch4GuYWefSBsp5NPJh4QRTP9DC3gc5Q # EKtbrTY0Ka87Web7/zScvLmvQBm8JDFpDjCCBrkwggShoAMCAQICEQDn/2nHOzXO # S5Em2HR8aKWHMA0GCSqGSIb3DQEBDAUAMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UE # ChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENl # cnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBO # ZXR3b3JrIENBIDIwHhcNMjEwNTE5MDUzMjA3WhcNMzYwNTE4MDUzMjA3WjBWMQsw # CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMSQw # IgYDVQQDExtDZXJ0dW0gVGltZXN0YW1waW5nIDIwMjEgQ0EwggIiMA0GCSqGSIb3 # DQEBAQUAA4ICDwAwggIKAoICAQDpEh8ENe25XXrFppVBvoplf0530W0lddNmjtv4 # YSh/f7eDQKFaIqc7tHj7ox+u8vIsJZlroakUeMS3i3T8aJRC+eQs4FF0GqvkM6+W # ZO8kmzZfxmZaBYmMLs8FktgFYCzywmXeQ1fEExflee2OpbHVk665eXRHjH7MYZIz # Nnjl2m8Hy8ulB9mR8wL/W0v0pjKNT6G0sfrx1kk+3OGosFUb7yWNnVkWKU4qSxLv # 16kJ6oVJ4BSbZ4xMak6JLeB8szrK9vwGDpvGDnKCUMYL3NuviwH1x4gZG0JAXU3x # 2pOAz91JWKJSAmRy/l0s0l5bEYKolg+DMqVhlOANd8Yh5mkQWaMEvBRE/kAGzIqg # WhwzN2OsKIVtO8mf5sPWSrvyplSABAYa13rMYnzwfg08nljZHghquCJYCa/xHK9a # cev9UD7Y+usr15d7mrszzxhF1JOr1Mpup2chNSBlyOObhlSO16rwrffVrg/SzaKf # SndS5swRhr8bnDqNJY9TNyEYvBYpgF95K7p0g4LguR4A++Z1nFIHWVY5v0fNVZmg # zxD9uVo/gta3onGOQj3JCxgYx0KrCXu4yc9QiVwTFLWbNdHFSjBCt5/8Q9pLuRhV # ocdCunhcHudMS1CGQ/Rn0+7P+fzMgWdRKfEOh/hjLrnQ8BdJiYrZNxvIOhM2aa3z # EDHNwwIDAQABo4IBVTCCAVEwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUvlQC # L79AbHNDzqwJJU6eQ0Qa7uAwHwYDVR0jBBgwFoAUtqFUOQLDoD+Oirz61PgcptE6 # Dv0wDgYDVR0PAQH/BAQDAgEGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMDAGA1UdHwQp # MCcwJaAjoCGGH2h0dHA6Ly9jcmwuY2VydHVtLnBsL2N0bmNhMi5jcmwwbAYIKwYB # BQUHAQEEYDBeMCgGCCsGAQUFBzABhhxodHRwOi8vc3ViY2Eub2NzcC1jZXJ0dW0u # Y29tMDIGCCsGAQUFBzAChiZodHRwOi8vcmVwb3NpdG9yeS5jZXJ0dW0ucGwvY3Ru # Y2EyLmNlcjA5BgNVHSAEMjAwMC4GBFUdIAAwJjAkBggrBgEFBQcCARYYaHR0cDov # L3d3dy5jZXJ0dW0ucGwvQ1BTMA0GCSqGSIb3DQEBDAUAA4ICAQC4k1l3yUwV/ZQH # CKCneqAs8EGTnwEUJLdDpokN/dMhKjK0rR5qX8nIIHzxpQR3TAw2IRw1Uxsr2Pli # G3bCFqSdQTUbfaTq6V3vBzEebDru9QFjqlKnxCF2h1jhLNFFplbPJiW+JSnJTh1f # KEqEdKdxgl9rVTvlxfEJ7exOn25MGbd/wGPwuSmMxRJVO0wnqgS7kmoJjNF9zqee # hFSDDP8ZVkWg4EZ2tIS0M3uZmByRr+1Lkwjjt8AtW83mVnZTyTsOb+FNfwJY7DS4 # FmWhkRbgcHRetreoTirPOr/ozyDKhT8MTSTf6Lttg6s6T/u08mDWw6HK04ZRDfQ9 # sb77QV8mKgO44WGP31vXnVKoWVJpFBjPvjL8/Zck/5wXX2iqjOaLStFOR/IQki+E # hn4zlcgVm22ZVCBPF+l8nAwUUShCtKuSU7GmZLKCmmxQMkSiWILTm8EtVD6AxnJh # oq8EnhjEEyUoflkeRF2WhFiVQOmWTwZRr44IxWGkNJC6tTorW5rl2Zl+2e9JLPYf # 3pStAPMDoPKIjVXd6NW2+fZrNUBeDo2eOa5Fn7Brs/HLQff5Xgris5MeUbdVgDrF # 8uxO6cLPvZPo63j62SsNg55pTWk9fUIF9iPoRbb4QurjoY/woI1RAOKtYtTic6aA # Jq3u83RIPpGXBSJKwx4KJAOZnCDCtTGCBqIwggaeAgEBMGowVjELMAkGA1UEBhMC # UEwxITAfBgNVBAoTGEFzc2VjbyBEYXRhIFN5c3RlbXMgUy5BLjEkMCIGA1UEAxMb # Q2VydHVtIENvZGUgU2lnbmluZyAyMDIxIENBAhBX5gfP8LabvnxCXoItEBa/MA0G # CWCGSAFlAwQCAQUAoIGEMBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZI # hvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcC # ARUwLwYJKoZIhvcNAQkEMSIEIK/FCSmkNEf7rt+CVqTexfh/FIDFoPqMmQfLHBD9 # QgVZMA0GCSqGSIb3DQEBAQUABIIBgGzwixVbkouAsTDUAPlpBuFwwoZp+I8xuBKr # o+yGQkj9oBE5IafuY0lkz0vQMdWA8GtdQDxDf0jF1r9TAqQ3gjeikF2eaSmVUxae # WE0UE3SAKa7zAyE386UwST8TUNEbR1/SLlTUYvJ+uwEpjQpWrL6YPEsb30wftf7M # ow8m2FwQY+daa11nIyqRi+cg/9DdEzYzR/pv/3ONM+V/eTWFJ4fT67hU0iYXloLl # 7UIYYUstdEVy9q3WSdII4vf8UpwSlg1CR2U00lnfYeV3VKMVk4MSUNW6E6py7xWl # 3tVXllv2bPo9oU5LsM7YtXZTk8mNqPaO6YQeoVA6ivlUYVuMxtBTvROqrFp7i7h9 # YzDix5egP1NqfAyo7z3JRe3SpXquzeEDbqpnTg09/RxADfGqCRIfseQRdEcVtXS9 # CbAHZyj5uEeyjdOKp5zUnn5VIinBS5Tv9ukMge3wtBGOu1nZIiEp7ea6xqHDe0Q0 # BFiR+FJLz0gcr8xUm1wB+DeFv7/YBqGCBAIwggP+BgkqhkiG9w0BCQYxggPvMIID # 6wIBATBqMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBTeXN0 # ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBUaW1lc3RhbXBpbmcgMjAyMSBDQQIQ # KPB3wRw2vf5fdDJHcCcuAzANBglghkgBZQMEAgIFAKCCAVYwGgYJKoZIhvcNAQkD # MQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNjEwMTMwOTM5NTdaMDcG # CyqGSIb3DQEJEAIvMSgwJjAkMCIEIIW+kOEK0kONfMkotq9IsJqyCBd87PiwEmxY # 05EFJcQ8MD8GCSqGSIb3DQEJBDEyBDA9E6C3ri1AzPzm3e8n3/ace2Ejup2PKTQQ # hiIvNhDPFygiRiYInB797alfe+MAoDswgZ8GCyqGSIb3DQEJEAIMMYGPMIGMMIGJ # MIGGBBRXFGhBDKha80JO+RZKUTYQ9NONmDBuMFqkWDBWMQswCQYDVQQGEwJQTDEh # MB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMSQwIgYDVQQDExtDZXJ0 # dW0gVGltZXN0YW1waW5nIDIwMjEgQ0ECECjwd8EcNr3+X3QyR3AnLgMwDQYJKoZI # hvcNAQEBBQAEggIAef4nzvx+FmBEmdMib5324+zpPFpvSdavK8ge52cwClBmbAWP # EaczK7SJGmBymY5lB280RWTDefljwiFz+HJhY1rlK7l4tQUypEUzugu9OAiQ/vSI # gQQErbima1MDAjrbCbDczOarjqyQGOpMDEMs7VPyJmev9gbIs/wPFm7i7RxYum+s # 8Rf7skcsqkZNVzCX+4EL2BTROLmITt2MEhYJ00Yl6oTf2oxHLG0GtihCbh0/G2vH # c35AvNrM0Bk/Rs8fs4JDMrqDETE0eeCaQ7KfHuOlyPIxkyqo7H38rUJvA/+nNjze # DQS3OucZuhSgemth4wMve5VtYmW33OePq2MhaQq+glEEyNLggX1o0IvkIQlLJI8O # spi+w/UAIeWAw6bh+tODwK9iwNVLp6SqziA7TgQ1k6WUnR6kEkSaEJ+YeT93Kloq # 5pTqkPowPqVUhCaROmC3QNcPDzNLteafZSMdeefBVjATdvVhjkuKzTFJ0yh5lp+r # 2ZSLCR2fhpnQQv8vWd/FotZJQOMsBIx6tlcPicNgapwLzOOPNhChGSNco6SFcJtR # Rww8p4rsZZ/+lwaFg0xf4re/nF4Ww6CwUZrl97ddlrRuIklf448DDRRrxeGyH4DI # /pit4U/nWAbC739bCV0fNL1MP6N5asBzVm7uxDa197T6MruZIdEZTndDuSM= # SIG # End signature block |