Framework/Helpers/ControlHelper.ps1
|
# This class should contains method that would be required to filter/targer controls class ControlHelper: EventBase{ static [psobject] $ControlFixBackup = @() static $GroupMembersResolutionObj = @{} #Caching group resolution static $ResolvedBroaderGroups = @{} # Caching expanded broader groups static $GroupsWithNumerousMembers = @() # Groups which contains very large groups static $VeryLargeGroups = @() # Very large groups (fetched from controlsettings) static $IsGroupDetailsFetchedFromPolicy = $false static $AllowedMemberCountPerBroadGroup = @() # Allowed member count (fetched from controlsettings) static $GroupsToExpand = @() # Groups for which meber counts need to check (fetched from controlsettings) static $GroupsWithDescriptor = @{} # All broder groups with descriptors static [string] $parentGroup = $null #used to store current broader group #Checks if the severities passed by user are valid and filter out invalid ones hidden static [string []] CheckValidSeverities([string []] $ParamSeverities) { $ValidSeverities = @(); $ValidSeverityValues = @(); $InvalidSeverities = @(); $ControlSettings = [ConfigurationManager]::LoadServerConfigFile("ControlSettings.json"); if([Helpers]::CheckMember($ControlSettings, 'ControlSeverity')) { $severityMapping = $ControlSettings.ControlSeverity #Discard the severity values passed in parameter that do not have mapping in Org settings. foreach($sev in $severityMapping.psobject.properties) { $ValidSeverities += $sev.value } $ValidSeverityValues += $ParamSeverities | Where-Object { $_ -in $ValidSeverities} $InvalidSeverities += $ParamSeverities | Where-Object { $_ -notin $ValidSeverities } } else { $ValidEnumSeverities = [Enum]::GetNames('ControlSeverity') $ValidSeverityValues += $ParamSeverities | Where-Object { $_ -in $ValidEnumSeverities} $InvalidSeverities += $ParamSeverities | Where-Object { $_ -notin $ValidEnumSeverities } } if($InvalidSeverities) { [EventBase]:: PublishGenericCustomMessage("WARNING: No control severity corresponds to `"$($InvalidSeverities -join ', ')`" for your org.",[MessageType]::Warning) } return $ValidSeverityValues } static [object] GetIdentitiesFromAADGroup([string] $OrgName, [String] $EntityId, [String] $groupName) { $members = @() $AllUsers = @() $rmContext = [ContextHelper]::GetCurrentContext(); $user = ""; $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user, $rmContext.AccessToken))) try { $apiUrl = 'https://dev.azure.com/{0}/_apis/IdentityPicker/Identities/{1}/connections?identityTypes=user&identityTypes=group&operationScopes=ims&operationScopes=source&connectionTypes=successors&depth=1&properties=DisplayName&properties=SubjectDescriptor&properties=SignInAddress' -f $($OrgName), $($EntityId) $responseObj = @(Invoke-RestMethod -Method Get -Uri $apiURL -Headers @{Authorization = ("Basic {0}" -f $base64AuthInfo) } -UseBasicParsing) # successors property will not be available if there are no users added to group. if ([Helpers]::CheckMember($responseObj[0], "successors")) { $members = @($responseObj.successors | Select-Object originId, displayName, @{Name = "subjectKind"; Expression = { $_.entityType } }, @{Name = "mailAddress"; Expression = { $_.signInAddress } }, @{Name = "descriptor"; Expression = { $_.subjectDescriptor } }, @{Name = "groupName"; Expression = { $groupName } }) } $members | ForEach-Object { if ($_.subjectKind -eq 'User') { $AllUsers += $_ } } return $AllUsers } catch { Write-Host $_ return $AllUsers } } static [void] ResolveNestedGroupMembers([string]$descriptor,[string] $orgName,[string] $projName){ [ControlHelper]::groupMembersResolutionObj[$descriptor] = @() $url="https://dev.azure.com/{0}/_apis/Contribution/HierarchyQuery?api-version=5.1-preview" -f $($orgName); if ([string]::IsNullOrEmpty($projName)){ $postbody=@' {"contributionIds":["ms.vss-admin-web.org-admin-members-data-provider"],"dataProviderContext":{"properties":{"subjectDescriptor":"{0}","sourcePage":{"url":"https://dev.azure.com/{2}/_settings/groups?subjectDescriptor={1}","routeId":"ms.vss-admin-web.collection-admin-hub-route","routeValues":{"adminPivot":"groups","controller":"ContributedPage","action":"Execute"}}}}} '@ $postbody=$postbody.Replace("{0}",$descriptor) $postbody=$postbody.Replace("{1}",$orgName) } else { $postbody=@' {"contributionIds":["ms.vss-admin-web.org-admin-members-data-provider"],"dataProviderContext":{"properties":{"subjectDescriptor":"{0}","sourcePage":{"url":"https://dev.azure.com/{1}/{2}/_settings/permissions?subjectDescriptor={0}","routeId":"ms.vss-admin-web.collection-admin-hub-route","routeValues":{"adminPivot":"groups","controller":"ContributedPage","action":"Execute"}}}}} '@ $postbody=$postbody.Replace("{0}",$descriptor) $postbody=$postbody.Replace("{1}",$orgName) $postbody=$postbody.Replace("{2}",$projName) } $rmContext = [ContextHelper]::GetCurrentContext(); $user = ""; $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$rmContext.AccessToken))) try { $response = Invoke-RestMethod -Uri $url -Method Post -ContentType "application/json" -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)} -Body $postbody if([Helpers]::CheckMember($response.dataProviders.'ms.vss-admin-web.org-admin-members-data-provider', "identities")) { $data=$response.dataProviders.'ms.vss-admin-web.org-admin-members-data-provider'.identities $data | ForEach-Object{ if($_.subjectKind -eq "group") { if ([string]::IsNullOrWhiteSpace($_.descriptor) -and (-not [string]::IsNullOrWhiteSpace($_.entityId))) { $identities = @([ControlHelper]::GetIdentitiesFromAADGroup($orgName, $_.entityId, $_.displayName)) if ($identities.Count -gt 0) { [ControlHelper]::groupMembersResolutionObj[$descriptor] += $identities } } else { [ControlHelper]::ResolveNestedGroupMembers($_.descriptor,$orgName,$projName) [ControlHelper]::groupMembersResolutionObj[$descriptor] += [ControlHelper]::groupMembersResolutionObj[$_.descriptor] } } else { [ControlHelper]::groupMembersResolutionObj[$descriptor] += $_ } } } } catch { Write-Host $_ } } static [void] FindGroupMembers([string]$descriptor,[string] $orgName,[string] $projName){ if (-not [ControlHelper]::GroupMembersResolutionObj.ContainsKey("OrgName")) { [ControlHelper]::GroupMembersResolutionObj["OrgName"] = $orgName } [ControlHelper]::ResolveNestedGroupMembers($descriptor, $orgName, $projName) } static [PSObject] ResolveNestedBroaderGroupMembers([PSObject]$groupObj,[string] $orgName,[string] $projName) { $groupPrincipalName = $groupObj.principalName $members = @() if ([ControlHelper]::ResolvedBroaderGroups.ContainsKey($groupPrincipalName)) { $members += [ControlHelper]::ResolvedBroaderGroups[$_.principalName]; return $members } else { [ControlHelper]::ResolvedBroaderGroups[$groupPrincipalName] = @() $rmContext = [ContextHelper]::GetCurrentContext(); $user = ""; $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$rmContext.AccessToken))) $memberslist = @() if ([string]::IsNullOrWhiteSpace($groupObj.descriptor) -and (-not [string]::IsNullOrWhiteSpace($groupObj.entityId))) { $apiUrl = 'https://dev.azure.com/{0}/_apis/IdentityPicker/Identities/{1}/connections?identityTypes=user&identityTypes=group&operationScopes=ims&operationScopes=source&connectionTypes=successors&depth=1&properties=DisplayName&properties=SubjectDescriptor&properties=SignInAddress' -f $($OrgName), $($groupObj.entityId) $responseObj = @(Invoke-RestMethod -Method Get -Uri $apiURL -Headers @{Authorization = ("Basic {0}" -f $base64AuthInfo) } -UseBasicParsing) # successors property will not be available if there are no users added to group. if ([Helpers]::CheckMember($responseObj[0], "successors")) { $memberslist = @($responseObj.successors | Select-Object entityId, originId, descriptor, @{Name = "principalName"; Expression = { $_.displayName } }, @{Name = "mailAddress"; Expression = { $_.signInAddress } }, @{Name = "subjectKind"; Expression = { $_.entityType } }) } $members += [ControlHelper]::NestedGroupResolverHelper($memberslist) } else { $descriptor = $groupObj.descriptor $url="https://dev.azure.com/{0}/_apis/Contribution/HierarchyQuery?api-version=5.1-preview" -f $($orgName); $postbody=@' {"contributionIds":["ms.vss-admin-web.org-admin-members-data-provider"],"dataProviderContext":{"properties":{"subjectDescriptor":"{0}","sourcePage":{"url":"https://dev.azure.com/{1}/{2}/_settings/permissions?subjectDescriptor={0}","routeId":"ms.vss-admin-web.collection-admin-hub-route","routeValues":{"adminPivot":"groups","controller":"ContributedPage","action":"Execute"}}}}} '@ $postbody=$postbody.Replace("{0}",$descriptor) $postbody=$postbody.Replace("{1}",$orgName) $postbody=$postbody.Replace("{2}",$projName) $response = Invoke-RestMethod -Uri $url -Method Post -ContentType "application/json" -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)} -Body $postbody if([Helpers]::CheckMember($response.dataProviders.'ms.vss-admin-web.org-admin-members-data-provider', "identities")) { $memberslist = @($response.dataProviders.'ms.vss-admin-web.org-admin-members-data-provider'.identities) } $members += [ControlHelper]::NestedGroupResolverHelper($memberslist) } [ControlHelper]::ResolvedBroaderGroups[$groupPrincipalName] = $members return $members } } static [PSObject] NestedGroupResolverHelper($memberslist) { $members = @() if ($memberslist.Count -gt 0) { $memberslist | ForEach-Object { if($_.subjectKind -eq "Group") { if ([ControlHelper]::VeryLargeGroups -contains $_.principalName) { [ControlHelper]::GroupsWithNumerousMembers += [ControlHelper]::parentGroup } else { $members += [ControlHelper]::ResolveNestedBroaderGroupMembers($_, $orgName, $projName) Write-Host "Group: [$($_.principalName)]; MemberCount: $([ControlHelper]::ResolvedBroaderGroups[$_.principalName].Count)" -ForegroundColor Cyan } } else { $members += $_ } } } return $members } static [PSObject] GetBroaderGroupDescriptorObj([string] $OrgName,[string] $groupName) { $groupObj = @() $projName = $groupName.Split("\")[0] -replace '[\[\]]' $rmContext = [ContextHelper]::GetCurrentContext(); $user = ""; $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$rmContext.AccessToken))) $url= "https://dev.azure.com/{0}/_apis/Contribution/HierarchyQuery?api-version=5.1-preview.1" -f $($OrgName); if ($projName -eq $OrgName) { $body=@' {"contributionIds":["ms.vss-admin-web.org-admin-groups-data-provider"],"dataProviderContext":{"properties":{"sourcePage":{"url":"https://dev.azure.com/{0}/_settings/groups","routeId":"ms.vss-admin-web.collection-admin-hub-route","routeValues":{"adminPivot":"groups","controller":"ContributedPage","action":"Execute"}}}}} '@ $body=$body.Replace("{0}",$OrgName) } else { $body=@' {"contributionIds":["ms.vss-admin-web.org-admin-groups-data-provider"],"dataProviderContext":{"properties":{"sourcePage":{"url":"https://dev.azure.com/{0}/{1}/_settings/permissions","routeId":"ms.vss-admin-web.project-admin-hub-route","routeValues":{"project":"{1}","adminPivot":"permissions","controller":"ContributedPage","action":"Execute"}}}}} '@ $body=$body.Replace("{0}",$OrgName) $body=$body.Replace("{1}",$projName) } try{ $responseObj = Invoke-RestMethod -Uri $url -Method Post -ContentType "application/json" -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)} -Body $body $groupObj = $responseObj.dataProviders.'ms.vss-admin-web.org-admin-groups-data-provider'.identities | Where-Object {$_.principalName -eq $groupName} } catch { Write-Host $_ } return $groupObj } hidden static FetchControlSettingDetails() { $ControlSettingsObj = [ConfigurationManager]::LoadServerConfigFile("ControlSettings.json"); [ControlHelper]::VeryLargeGroups = @($ControlSettingsObj.VeryLargeGroups) [ControlHelper]::AllowedMemberCountPerBroadGroup = @($ControlSettingsObj.AllowedMemberCountPerBroadGroup) [ControlHelper]::GroupsToExpand = @($ControlSettingsObj.BroaderGroupsToExpand) [ControlHelper]::IsGroupDetailsFetchedFromPolicy = $true } static [PSObject] FilterBroadGroupMembers([PSObject] $groupList, [bool] $fetchDescriptor) { $broaderGroupsWithExcessiveMembers = @() if ([ControlHelper]::IsGroupDetailsFetchedFromPolicy -eq $false) { [ControlHelper]::FetchControlSettingDetails() } $groupList | Foreach-Object { # In some cases, group object doesn't contains descriptor key which requires to expand the groups. if ($fetchDescriptor) { $groupName = $_.Name.split('\')[-1] } else { $groupName = $_.principalName.split('\')[-1] } if ([ControlHelper]::GroupsToExpand -contains $groupName) { if ($fetchDescriptor) { if (-not ([ControlHelper]::GroupsWithDescriptor.keys -contains $_.Name)) { [ControlHelper]::GroupsWithDescriptor[$_.Name] += [ControlHelper]::GetBroaderGroupDescriptorObj($this.OrganizationContext.OrganizationName, $_.Name) } $groupObj = [ControlHelper]::GroupsWithDescriptor[$_.Name] } else { $groupObj = $_ } [ControlHelper]::parentGroup = $groupObj.principalName #Checking if group is prenet in the dictionary, if not then expand it, else fetch from dictionary if (-not [ControlHelper]::ResolvedBroaderGroups.ContainsKey($groupObj.principalName)) { $groupMembers = @([ControlHelper]::ResolveNestedBroaderGroupMembers($groupObj, $this.OrganizationContext.OrganizationName, $this.ResourceContext.ResourceGroupName)) } else { $groupMembers = @([ControlHelper]::ResolvedBroaderGroups[$groupObj.principalName]) } $groupMembersCount = @($groupMembers | Select-Object -property mailAddress -Unique).Count # Checking the allowed member count for broader group # Fail only if member count is greater then allowed limit. if ([Helpers]::CheckMember([ControlHelper]::AllowedMemberCountPerBroadGroup, $groupName)) { $allowedMemberCount = [ControlHelper]::AllowedMemberCountPerBroadGroup.$groupName if (($groupMembersCount -gt $allowedMemberCount) -or ([ControlHelper]::GroupsWithNumerousMembers -contains $groupObj.principalName)) { $broaderGroupsWithExcessiveMembers += $groupObj.principalName } } else { $broaderGroupsWithExcessiveMembers += $groupObj.principalName } } else { if ($fetchDescriptor) { $broaderGroupsWithExcessiveMembers += $_.Name } else { $broaderGroupsWithExcessiveMembers += $_.principalName } } } return $broaderGroupsWithExcessiveMembers } } # SIG # Begin signature block # MIIjmAYJKoZIhvcNAQcCoIIjiTCCI4UCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCyv/SKTgBBRFGZ # bXiKKIa73Sr4C+LTC4Z0581yHYkj4KCCDYUwggYDMIID66ADAgECAhMzAAAB4HFz # JMpcmPgZAAAAAAHgMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjAxMjE1MjEzMTQ2WhcNMjExMjAyMjEzMTQ2WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDRXpc9eiGRI/2BlmU7OMiQPTKpNlluodjT2rltPO/Gk47bH4gBShPMD4BX/4sg # NvvBun6ZOG2dxUW30myWoUJJ0iRbTAv2JFzjSpVQvPE+D5vtmdu6WlOR2ahF4leF # 5Vvk4lPg2ZFrqg5LNwT9gjwuYgmih+G2KwT8NMWusBhO649F4Ku6B6QgA+vZld5S # G2XWIdvS0pmpmn/HFrV4eYTsl9HYgjn/bPsAlfWolLlEXYTaCljK7q7bQHDBrzlR # ukyyryFpPOR9Wx1cxFJ6KBqg2jlJpzxjN3udNJPOqarnQIVgB8DUm3I5g2v5xTHK # Ovz9ucN21467cYcIxjPC4UkDAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUVBWIZHrG4UIX3uX4142l+8GsPXAw # VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh # dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ2MzAxMDAfBgNVHSMEGDAW # gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v # d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw # MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov # L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx # XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB # AE5msNzmYzYbNgpnhya6YsrM+CIC8CXDu10nwzZtkgQciPOOqAYmFcWJCwD5VZzs # qFwad8XIOrfCylWf4hzn09mD87yuazpuCstLSqfDLNd3740+254vEZqdGxOglAGU # ih2IiF8S0GDwucpLGzt/OLXPFr/d4MWxPuX0L+HB5lA3Y/CJE673dHGQW2DELdqt # ohtkhp+oWFn1hNDDZ3LP++HEZvA7sI/o/981Sh4kaGayOp6oEiQuGeCXyfrIC9KX # eew0UlYX/NHVDqr4ykKkqpHtzbUbuo7qovUHPbYKcRGWrrEtBS5SPLFPumqsRtzb # LgU9HqfRAN36bMsd2qynGyWBVFOM7NMs2lTCGM85Z/Fdzv/8tnYT36Cmbue+IM+6 # kS86j6Ztmx0VIFWbOvNsASPT6yrmYiecJiP6H0TrYXQK5B3jE8s53l+t61ab0Eul # 7DAxNWX3lAiUlzKs3qZYQEK1LFvgbdTXtBRnHgBdABALK3RPrieIYqPln9sAmg3/ # zJZi4C/c2cWGF6WwK/w1Nzw08pj7jaaZZVBpCeDe+y7oM26QIXxracot7zJ21/TL # 70biK36YybSUDkjhQPP/uxT0yebLNBKk7g8V98Wna2MsHWwk0sgqpkjIp02TrkVz # 26tcF2rml2THRSDrwpBa4x9c8rM8Qomiyeh2tEJnsx2LMIIHejCCBWKgAwIBAgIK # YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV # BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv # c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm # aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw # OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD # VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG # 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la # UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc # 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D # dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+ # lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk # kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6 # A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd # X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL # 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd # sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3 # T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS # 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI # bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL # BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD # uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv # c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf # MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf # MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF # BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h # cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA # YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn # 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7 # v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b # pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/ # KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy # CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp # mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi # hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb # BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS # oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL # gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX # cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCFWkwghVlAgEBMIGVMH4x # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p # Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAHgcXMkylyY+BkAAAAA # AeAwDQYJYIZIAWUDBAIBBQCggbAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw # HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIJzx # 0gzNDmbdH9F+xaHrRaNcJRA4R2PAXnfj086e+reiMEQGCisGAQQBgjcCAQwxNjA0 # oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEcgBpodHRwczovL3d3dy5taWNyb3NvZnQu # Y29tIDANBgkqhkiG9w0BAQEFAASCAQAunrtqQS2e60Czr5HOxJpNION/UleNCoHw # FpMqVRWqA/hHZ86G48780b5w14kujmKqWBApnLn8j5uuOICKNtYITscSHnH2XLTu # CkTCeW9JBiz/JLYuBm2m8QigsvB7ncaFTBy8hvItGxTFJODaUPYzaY+5eBGn8zsM # xxLAALLFTS1Rg9QJWNyXHjVI3Z2Rdy7CBdSXyNpnFlWq/rBMMV1QYtU/IM2efISx # kkhJuFFI0/UnZDDqRdETkXM7+1phiX5UkQu/D877poqejPxpfSoRf6cjJ34n+ZjB # AUvqABwKYmYqMTftusn4wv3v09pL1lpb3XCMvMdo/x2tMig7AmVIoYIS8TCCEu0G # CisGAQQBgjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglg # hkgBZQMEAgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEE # AYRZCgMBMDEwDQYJYIZIAWUDBAIBBQAEIOil7YthLB0Vk6fNrXhMKmSsGENfyQVd # UkqmKRtFYUSpAgZhRNNSdRoYEzIwMjEwOTI0MTM0NjA3LjIxNlowBIACAfSggdSk # gdEwgc4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH # EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNV # BAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1U # aGFsZXMgVFNTIEVTTjo2MEJDLUUzODMtMjYzNTElMCMGA1UEAxMcTWljcm9zb2Z0 # IFRpbWUtU3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABWiy5bkQ0 # y28oAAAAAAFaMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI # EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv # ZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBD # QSAyMDEwMB4XDTIxMDExNDE5MDIxNloXDTIyMDQxMTE5MDIxNlowgc4xCzAJBgNV # BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w # HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29m # dCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVT # Tjo2MEJDLUUzODMtMjYzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg # U2VydmljZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALC9XBxXDa0n # CK70Hf+Gih6NxRl1mAhzFJdok8bs3xpJ87TM28rEeHkZAaE+Kb9Gi9UvTpQ3zrEW # yWSpIQkyxv/Wf0cIhA1mJOqyu20TN3l96ZvgzYrzO/rQlPvbKW79oAO4+YFsekQC # trzM9hQoS5BYGwPh9Qz66BuSxH9QweywNBQsjkVoikpBxkS+EXSIzpba2afvnRMX # 7LLe2eryc+PlPXmTSOfH1WNykc25u9zo6ZX0gAd4jUpBzdMLnHCtE62bL2PO00cm # AJsitqgaov+3lFrfd0sPACwTGO9iymlJlb2savwjqSnj5RzG4RxG6rU2i7etbnQT # ozR73OHMGOUCAwEAAaOCARswggEXMB0GA1UdDgQWBBRkcyU/9RyPkn7QBoXZOTQ8 # wN4xZzAfBgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBN # MEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0 # cy9NaWNUaW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoG # CCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01p # Y1RpbVN0YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQM # MAoGCCsGAQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQAAAG3sfFgbUiw4gWMV8VOx # lbIG/CIMSiciDtIZnPL84OMN4lJeV5LeJr+HYBcox5ruWZm49K29iBmJv6ViXMtP # 81pYZ1EFM7306Y+zLIh/tS574PeWsHvPD0QOxQ4HOM2GNPvFAdUvo8z5pgV/5E+l # Pu61uUCIBTDESiHO+N7ragqb3METPqRKPLNAJcKPDcalKznmGPlnzY6P1zop/7a9 # 0VcBHRKKQ/hTvn/8C8Y6b+Mvk5kYJh67KNbVVcuuBWyFSMZTHGenHnuHVg9svH7+ # lm/V/wIbZUKKJJO0HQmyodySeD/JLC7NNsDYRpFN+29dLRtx0eWyZosJmT8qKbBI # MIIGcTCCBFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9z # b2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEz # NjU1WhcNMjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAx # MDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1 # +n9plGt0VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROO # fGEwWbEwRA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEby # WEeGMoQedGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqv # S2SJUGKxXf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noq # CjHw2k4GkbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVP # Ik0CAwEAAaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpc # ijGQ80N7fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNV # HQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo # 0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29m # dC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5j # cmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jv # c29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCB # oAYDVR0gAQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0 # dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0w # QAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQA # YQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+B # cQM9naOhIW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RX # CCtRgkQS+7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31a # PxzymXlKkVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E # 4XCfMkon/VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517 # IW3DnKOiPPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl # 5WTs9/S/fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ # 3110mCIIYdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm7 # 7MbL2IK0cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1Qn # IffIrE7aKLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1 # rtxEPJdQcdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrv # CScc1bN+NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJ # BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k # MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jv # c29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNT # IEVTTjo2MEJDLUUzODMtMjYzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3Rh # bXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUAzIAFmL3GHHWcAJYi3haGwlplGi6g # gYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G # A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYw # JAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0B # AQUFAAIFAOT340YwIhgPMjAyMTA5MjQwOTQwMjJaGA8yMDIxMDkyNTA5NDAyMlow # dzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA5PfjRgIBADAKAgEAAgIc8gIB/zAHAgEA # AgIQ8zAKAgUA5Pk0xgIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMC # oAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBABBDZepC # hGVQVaqwIYmy5RxSdKmVsdgCl14UVfBtWCnkoX3G/BPWSaf3asgDUvnIi/g+aRNS # 4gez9uvae3OzYRhlmsOH2HKWE9BUmnOR2AxK5+6ME3jZE4OJvia5/U5E4A8wAcd3 # UI/lnQHqGXkTqzGo+LqgO1jhuxhN4qHI7wRiMYIDDTCCAwkCAQEwgZMwfDELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9z # b2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAFaLLluRDTLbygAAAAAAVowDQYJ # YIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkq # hkiG9w0BCQQxIgQggRhLeqc7SR6Yx5jzNn3OOe8IqJBNYFpZXPsMSAn98WwwgfoG # CyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCCT/KgmdMSy5F0ww4Iar9cmf5Is3pM0 # hUuIInL5bbF/sDCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo # aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y # cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw # AhMzAAABWiy5bkQ0y28oAAAAAAFaMCIEIDKsluzEBrHChh4mMXIjMrMAw/9NWZIk # rS8OPSAOlIC8MA0GCSqGSIb3DQEBCwUABIIBAFKmtmvb1V4jUGQJvGM7K7zaIMU8 # o0WBEK+L1rZLHzuV+oCgE9VLkTfuXcU2wQi9Em6ZEteFRejNKdNh1HE1VTYniU0L # RGIr6CjXaTxkyOfPoIkKrVf/buAYK4ql0VzhLSBG2/aeqKb43ayv1NCSJB5gDlWN # onTkEZ0LG/PcrjffkTD3Ne0IaVteTDNeXZDYzNLBeIrlTWJhBtAiCWP+D+BruMkG # Wj56C3I76Ce38kDfvelc2WDECHiV0UOWCite1WY8J2lP8WvEG7AsloOVfVAUVfKZ # XzcIJAr6V5HjeRnM7/xYzJcl/f8v2Wsl1/HEVpEDh43lm2DEgXKQMu6gcdg= # SIG # End signature block |