Framework/Helpers/AdministratorHelper.ps1
class AdministratorHelper{ static [bool] $isCurrentUserPCA=$false; static [bool] $isCurrentUserPA=$false; static $AllPCAMembers = @() static $AllPAMembers = @() static $AllBAMembers = @() #Check whether uesr is PCA and subgroups member static [bool] isUserOrgAdminMember($organizationName, [PSObject] $allowedAdminGrp) { try { $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 $($organizationName); $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}",$organizationName) $groupsOrgObj = Invoke-RestMethod -Uri $url -Method Post -ContentType "application/json" -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)} -Body $body if ($allowedAdminGrp) { $groupsOrgObj = $groupsOrgObj.dataProviders.'ms.vss-admin-web.org-admin-groups-data-provider'.identities | where { $allowedAdminGrp.GroupNames -contains $_.displayName } } #else condition if 'AllowAdminControlScanForGroups' propertry not foud in orgpolicy. Then check using default group PA else { $groupsOrgObj = $groupsOrgObj.dataProviders.'ms.vss-admin-web.org-admin-groups-data-provider'.identities | where { "Project Collection Administrators" -eq $_.displayName } } foreach ($group in $groupsOrgObj) { #if user found in group return true if ([AdministratorHelper]::GetIsCurrentUserPCA($group.descriptor, $organizationName)) { return $true; } } return $false; } catch { return $false; } } static [bool] isUserProjectAdminMember($organizationName, $project, [PSObject] $allowedAdminGrp) { try { $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 $($organizationName); $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}",$organizationName) $body=$body.Replace("{1}",$project) $groupsObj = Invoke-RestMethod -Uri $url -Method Post -ContentType "application/json" -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)} -Body $body if ($allowedAdminGrp) { $groupsObj = $groupsObj.dataProviders."ms.vss-admin-web.org-admin-groups-data-provider".identities | where { $allowedAdminGrp.GroupNames -contains $_.displayName } } #else condition if 'AllowAdminControlScanForGroups' propertry not foud in orgpolicy. Then check using default group PA else { $groupsObj = $groupsObj.dataProviders."ms.vss-admin-web.org-admin-groups-data-provider".identities | where { "Project Administrators" -eq $_.displayName } } foreach ($group in $groupsObj) { #if user found in group return true if([AdministratorHelper]::GetIsCurrentUserPA($groupsObj.descriptor,$organizationName, $project)) { return $true; } } return $false; } catch { return $false; } } static [void] GetPCADescriptorAndMembers([string] $OrgName){ $url= "https://dev.azure.com/{0}/_apis/Contribution/HierarchyQuery?api-version=5.1-preview.1" -f $($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) $rmContext = [ContextHelper]::GetCurrentContext(); $user = ""; $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$rmContext.AccessToken))) try{ $responseObj = Invoke-RestMethod -Uri $url -Method Post -ContentType "application/json" -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)} -Body $body $accname = "Project Collection Administrators"; $prcollobj = $responseObj.dataProviders.'ms.vss-admin-web.org-admin-groups-data-provider'.identities | where {$_.displayName -eq $accname} if(($prcollobj | Measure-Object).Count -gt 0){ [AdministratorHelper]::FindPCAMembers($prcollobj.descriptor,$OrgName) } } catch { Write-Host $_ } } static [void] GetPADescriptorAndMembers([string] $OrgName,[string] $projName){ $url= "https://dev.azure.com/{0}/_apis/Contribution/HierarchyQuery?api-version=5.1-preview.1" -f $($OrgName); $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) $rmContext = [ContextHelper]::GetCurrentContext(); $user = ""; $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$rmContext.AccessToken))) try{ $responseObj = Invoke-RestMethod -Uri $url -Method Post -ContentType "application/json" -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)} -Body $body $accname = "Project Administrators"; $prcollobj = $responseObj.dataProviders.'ms.vss-admin-web.org-admin-groups-data-provider'.identities | where {$_.displayName -eq $accname} if(($prcollobj | Measure-Object).Count -gt 0){ [AdministratorHelper]::FindPAMembers($prcollobj.descriptor,$OrgName,$projName) } } catch { Write-Host $_ } } static [void] GetBADescriptorAndMembers([string] $OrgName,[string] $projName){ $url= "https://dev.azure.com/{0}/_apis/Contribution/HierarchyQuery?api-version=5.1-preview.1" -f $($OrgName); $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) $rmContext = [ContextHelper]::GetCurrentContext(); $user = ""; $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$rmContext.AccessToken))) try{ $responseObj = Invoke-RestMethod -Uri $url -Method Post -ContentType "application/json" -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)} -Body $body $accname = "Build Administrators"; $prcollobj = $responseObj.dataProviders.'ms.vss-admin-web.org-admin-groups-data-provider'.identities | where {$_.displayName -eq $accname} if(($prcollobj | Measure-Object).Count -gt 0){ [AdministratorHelper]::FindBAMembers($prcollobj.descriptor,$OrgName,$projName) } } catch { Write-Host $_ } } static [void] FindPCAMembers([string]$descriptor,[string] $OrgName){ try { if ($null -eq [AdministratorHelper]::AllPCAMembers -or [AdministratorHelper]::AllPCAMembers.Count -eq 0) { [ControlHelper]::FindGroupMembers($descriptor,$orgName,"") [AdministratorHelper]::AllPCAMembers = [ControlHelper]::groupMembersResolutionObj[$descriptor] } $currentUser = [ContextHelper]::GetCurrentSessionUser(); if((-not [string]::IsNullOrEmpty([AdministratorHelper]::AllPCAMembers)) -and [AdministratorHelper]::isCurrentUserPCA -eq $false -and $currentUser -in [AdministratorHelper]::AllPCAMembers.mailAddress){ [AdministratorHelper]::isCurrentUserPCA=$true; } } catch { Write-Host $_ } } static [void] FindPAMembers([string]$descriptor,[string] $OrgName,[string] $projName){ try { if ($null -eq [AdministratorHelper]::AllPAMembers -or [AdministratorHelper]::AllPAMembers.Count -eq 0) { [ControlHelper]::FindGroupMembers($descriptor,$orgName,$projName) [AdministratorHelper]::AllPAMembers = [ControlHelper]::groupMembersResolutionObj[$descriptor] } $currentUser = [ContextHelper]::GetCurrentSessionUser(); if((-not [string]::IsNullOrEmpty([AdministratorHelper]::AllPAMembers)) -and [AdministratorHelper]::isCurrentUserPA -eq $false -and $currentUser -in [AdministratorHelper]::AllPAMembers.mailAddress){ [AdministratorHelper]::isCurrentUserPA=$true; } } catch { Write-Host $_ } } static [void] FindBAMembers([string]$descriptor,[string] $OrgName,[string] $projName){ try { if ($null -eq [AdministratorHelper]::AllBAMembers -or [AdministratorHelper]::AllBAMembers.Count -eq 0) { [ControlHelper]::FindGroupMembers($descriptor,$orgName,$projName) [AdministratorHelper]::AllBAMembers = [ControlHelper]::groupMembersResolutionObj[$descriptor] } } catch { Write-Host $_ } } 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 [object] GetTotalPCAMembers([string] $OrgName){ #TODO: Need to reinitialize as PS ISE caches this list. It will be inappropriate if you switch org names from one scan to another in the same session. [AdministratorHelper]::AllPCAMembers = @(); [AdministratorHelper]::isCurrentUserPCA = $false; [AdministratorHelper]::GetPCADescriptorAndMembers($OrgName) #get unique pca based on display name and mail address [AdministratorHelper]::AllPCAMembers = @([AdministratorHelper]::AllPCAMembers | Sort-Object -Unique 'mailAddress') return [AdministratorHelper]::AllPCAMembers } static [object] GetTotalPAMembers([string] $OrgName,[string] $projName){ #Always reinitialize PA member list. Needed when trying to scan multiple projects [AdministratorHelper]::AllPAMembers = @(); [AdministratorHelper]::GetPADescriptorAndMembers($OrgName,$projName) [AdministratorHelper]::AllPAMembers = @([AdministratorHelper]::AllPAMembers | Sort-Object -Unique 'mailAddress') return [AdministratorHelper]::AllPAMembers } static [object] GetTotalBAMembers([string] $OrgName,[string] $projName){ #Always reinitialize PA member list. Needed when trying to scan multiple projects [AdministratorHelper]::AllBAMembers = @(); [AdministratorHelper]::GetBADescriptorAndMembers($OrgName,$projName) [AdministratorHelper]::AllBAMembers = @([AdministratorHelper]::AllBAMembers | Sort-Object -Unique 'mailAddress') return [AdministratorHelper]::AllBAMembers } static [bool] GetIsCurrentUserPCA([string] $descriptor,[string] $OrgName){ #TODO: Need to reinitialize as PS ISE caches this list. It will be inappropriate if you switch org names from one scan to another in the same session. [AdministratorHelper]::FindPCAMembers($descriptor,$OrgName) return [AdministratorHelper]::isCurrentUserPCA } static [bool] GetIsCurrentUserPA([string] $descriptor,[string] $OrgName,[string] $projName){ #Always reinitialize PA member list and its count. Needed when trying to scan multiple projects [AdministratorHelper]::isCurrentUserPA = $false; [AdministratorHelper]::FindPAMembers($descriptor,$OrgName,$projName) return [AdministratorHelper]::isCurrentUserPA } static [void] PopulatePCAResultsToControl($humanAccounts, $svcAccounts, $controlResult){ $TotalPCAMembers=$humanAccounts.Count + $svcAccounts.Count if($TotalPCAMembers -gt 0){ $controlResult.AddMessage("Current set of Project Collection Administrators: ") $controlResult.AdditionalInfo = "Count of Project Collection Administrators: " + $TotalPCAMembers; } if ($humanAccounts.Count -gt 0) { $display=($humanAccounts | FT displayName, mailAddress -AutoSize | Out-String -Width 512) $controlResult.AddMessage("`nHuman administrators: $($humanAccounts.Count) `n", $display) } if ($svcAccounts.Count -gt 0) { $display=($svcAccounts | FT displayName, mailAddress -AutoSize | Out-String -Width 512) $controlResult.AddMessage("`nService accounts: $($svcAccounts.Count) `n", $display) } return ; } } # SIG # Begin signature block # MIInpQYJKoZIhvcNAQcCoIInljCCJ5ICAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBN8F4ibwBFQfUW # aiQqjRacMzbGIWN27o2r9Z39SRt7rKCCDYUwggYDMIID66ADAgECAhMzAAADTU6R # phoosHiPAAAAAANNMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI4WhcNMjQwMzE0MTg0MzI4WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDUKPcKGVa6cboGQU03ONbUKyl4WpH6Q2Xo9cP3RhXTOa6C6THltd2RfnjlUQG+ # Mwoy93iGmGKEMF/jyO2XdiwMP427j90C/PMY/d5vY31sx+udtbif7GCJ7jJ1vLzd # j28zV4r0FGG6yEv+tUNelTIsFmmSb0FUiJtU4r5sfCThvg8dI/F9Hh6xMZoVti+k # bVla+hlG8bf4s00VTw4uAZhjGTFCYFRytKJ3/mteg2qnwvHDOgV7QSdV5dWdd0+x # zcuG0qgd3oCCAjH8ZmjmowkHUe4dUmbcZfXsgWlOfc6DG7JS+DeJak1DvabamYqH # g1AUeZ0+skpkwrKwXTFwBRltAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUId2Img2Sp05U6XI04jli2KohL+8w # VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh # dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzUwMDUxNzAfBgNVHSMEGDAW # gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v # d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw # MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov # L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx # XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB # ACMET8WuzLrDwexuTUZe9v2xrW8WGUPRQVmyJ1b/BzKYBZ5aU4Qvh5LzZe9jOExD # YUlKb/Y73lqIIfUcEO/6W3b+7t1P9m9M1xPrZv5cfnSCguooPDq4rQe/iCdNDwHT # 6XYW6yetxTJMOo4tUDbSS0YiZr7Mab2wkjgNFa0jRFheS9daTS1oJ/z5bNlGinxq # 2v8azSP/GcH/t8eTrHQfcax3WbPELoGHIbryrSUaOCphsnCNUqUN5FbEMlat5MuY # 94rGMJnq1IEd6S8ngK6C8E9SWpGEO3NDa0NlAViorpGfI0NYIbdynyOB846aWAjN # fgThIcdzdWFvAl/6ktWXLETn8u/lYQyWGmul3yz+w06puIPD9p4KPiWBkCesKDHv # XLrT3BbLZ8dKqSOV8DtzLFAfc9qAsNiG8EoathluJBsbyFbpebadKlErFidAX8KE # usk8htHqiSkNxydamL/tKfx3V/vDAoQE59ysv4r3pE+zdyfMairvkFNNw7cPn1kH # Gcww9dFSY2QwAxhMzmoM0G+M+YvBnBu5wjfxNrMRilRbxM6Cj9hKFh0YTwba6M7z # ntHHpX3d+nabjFm/TnMRROOgIXJzYbzKKaO2g1kWeyG2QtvIR147zlrbQD4X10Ab # rRg9CpwW7xYxywezj+iNAc+QmFzR94dzJkEPUSCJPsTFMIIHejCCBWKgAwIBAgIK # 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/Xmfwb1tbWrJUnMTDXpQzTGCGXYwghlyAgEBMIGVMH4x # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p # Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAANNTpGmGiiweI8AAAAA # A00wDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw # HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIBDM # F7LTvtadz3aqthdPEaVz4WTXr0+te73PiYzXz763MEIGCisGAQQBgjcCAQwxNDAy # oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20wDQYJKoZIhvcNAQEBBQAEggEAWayYhE7icLiI6DtBl0WR836R9ZAsnlX0Q2p2 # 505bnqDULQW/68idzpc2KFujXrGNdyLQKPl467TYoSDahoojD9rEzfUF7o0u7M9P # avxZLYaERVKf2flk8+uVYA0H6SGMdmT2JGn8XweOtqgTcPET2+YFIFji4+CDkJRR # 88wkkVNaC+a5vLPnTc6aSe3MrXO5yp8mq2AVkPoMNW3ZAgO9aXBHoYn0uTqM4qf3 # QL6rvBoWu1I3gnEJW0Zv3EPgSV0kmnuK04TyeQlja4uqE/exxWrv8wbqgRpbIR1k # zZPANP3UiBwV53QNV3Ddvv8Gl749+MQLmDmDY6BzLjfFoHCBq6GCFwAwghb8Bgor # BgEEAYI3AwMBMYIW7DCCFugGCSqGSIb3DQEHAqCCFtkwghbVAgEDMQ8wDQYJYIZI # AWUDBAIBBQAwggFRBgsqhkiG9w0BCRABBKCCAUAEggE8MIIBOAIBAQYKKwYBBAGE # WQoDATAxMA0GCWCGSAFlAwQCAQUABCCdsHOk02+R52baQhTkjd6YOADPT88q0e48 # ZIZX5GIQoQIGZLAcyLGRGBMyMDIzMDcxODEyMDUzMS42NDhaMASAAgH0oIHQpIHN # MIHKMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQL # ExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMSYwJAYDVQQLEx1UaGFsZXMg # VFNTIEVTTjoxMkJDLUUzQUUtNzRFQjElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt # U3RhbXAgU2VydmljZaCCEVcwggcMMIIE9KADAgECAhMzAAAByk/Cs+0DDRhsAAEA # AAHKMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo # aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y # cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw # MB4XDTIyMTEwNDE5MDE0MFoXDTI0MDIwMjE5MDE0MFowgcoxCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVy # aWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjEyQkMtRTNB # RS03NEVCMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIIC # IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwwGcq9j50rWEkcLSlGZLweUV # fxXRaUjiPsyaNVxPdMRs3CVe58siu/EkaVt7t7PNTPko/s8lNtusAeLEnzki44yx # k2c9ekm8E1SQ2YV9b8/LOxfKapZ8tVlPyxw6DmFzNFQjifVm8EiZ7lFRoY448vpc # bBD18qjYNF/2Z3SQchcsdV1N9Y6V2WGl55VmLqFRX5+dptdjreBXzi3WW9TsoCEW # cYCBK5wYgS9tT2SSSTzae3jmdw40g+LOIyrVPF2DozkStv6JBDPvwahXWpKGpO7r # HrKF+o7ECN/ViQFMZyp/vxePiUABDNqzEUI8s7klYmeHXvjeQOq/CM3C/Y8bj3fJ # ObnZH7eAXvRDnxT8R6W/uD1mGUJvv9M9BMu3nhKpKmSxzzO5LtcMEh2tMXxhMGGN # MUP3DOEK3X+2/LD1Z03usJTk5pHNoH/gDIvbp787Cw40tsApiAvtrHYwub0TqIv8 # Zy62l8n8s/Mv/P764CTqrxcXzalBHh+Xy4XPjmadnPkZJycp3Kczbkg9QbvJp0H/ # 0FswHS+efFofpDNJwLh1hs/aMi1K/ozEv7/WLIPsDgK16fU/axybqMKk0NOxgelU # jAYKl4wU0Y6Q4q9N/9PwAS0csifQhY1ooQfAI0iDCCSEATslD8bTO0tRtqdcIdav # OReqzoPdvAv3Dr1XXQ8CAwEAAaOCATYwggEyMB0GA1UdDgQWBBT6x/6lS4ESQ8KZ # hd0RgU7RYXM8fzAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNV # HR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2Ny # bC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYI # KwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAy # MDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0G # CSqGSIb3DQEBCwUAA4ICAQDY0HkqCS3KuKefFX8/rm/dtD9066dKEleNqriwZqsM # 4Ym8Ew4QiqOqO7mWoYYY4K5y8eXSOHKNXOfpO6RbaYj8jCOcJAB5tqLl5hiMgaMb # AVLrl1hlix9sloO45LON0JphKva3D6AVKA7P78mA9iRHZYUVrRiyfvQjWxmUnxhi # s8fom92+/RHcEZ1Dh5+p4gzeeL84Yl00Wyq9EcgBKKfgq0lCjWNSq1AUG1sELlgX # OSvKZ4/lXXH+MfhcHe91WLIaZkS/Hu9wdTT6I14BC97yhDsZWXAl0IJ801I6UtEF # pCsTeOyZBJ7CF0rf5lxJ8tE9ojNsyqXJKuwVn0ewCMkZqz/cEwv9FEx8QmsZ0ZNo # dTtsl+V9dZm+eUrMKZk6PKsKArtQ+jHkfVsHgKODloelpOmHqgX7UbO0NVnIlpP5 # 5gQTqV76vU7wRXpUfz7KhE3BZXNgwG05dRnCXDwrhhYz+Itbzs1K1R8I4YMDJjW9 # 0ASCg9Jf+xygRKZGKHjo2Bs2XyaKuN1P6FFCIVXN7KgHl/bZiakGq7k5TQ4OXK5x # khCHhjdgHuxj3hK5AaOy+GXxO/jbyqGRqeSxf+TTPuWhDWurIo33RMDGe5DbImjc # bcj6dVhQevqHClR1OHSfr+8m1hWRJGlC1atcOWKajArwOURqJSVlThwVgIyzGNmj # zjCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZIhvcNAQEL # BQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH # EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNV # BAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4X # DTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMCVVMxEzAR # BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p # Y3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3Rh # bXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDk4aZM # 57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25PhdgM/9cT8dm # 95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPFdvWGUNzB # RMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6GnszrYBb # fowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBpDco2LXCO # Mcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50ZuyjLVwIYw # XE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3EXzTdEonW # /aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0lBw0gg/w # EPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1qGFphAXPK # Z6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ+QuJYfM2 # BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PAPBXbGjfH # CBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkwEgYJKwYB # BAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxGNSnPEP8v # BO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARVMFMwUQYM # KwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0 # LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAKBggrBgEF # BQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD # VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBW # BgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny # bC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUH # AQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtp # L2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG9w0BAQsF # AAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0xM7U518Jx # Nj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmCVgADsAW+ # iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449xvNo32X2 # pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wMnosZiefw # C2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDSPeZKPmY7 # T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2dY3RILLFO # Ry3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxnGSgkujhL # mm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+CrvsQWY9af3L # wUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokLjzbaukz5 # m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL6Xu/OHBE # 0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggLOMIICNwIB # ATCB+KGB0KSBzTCByjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEmMCQGA1UE # CxMdVGhhbGVzIFRTUyBFU046MTJCQy1FM0FFLTc0RUIxJTAjBgNVBAMTHE1pY3Jv # c29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoBATAHBgUrDgMCGgMVAKOO55cMT4sy # PP6nClg2IWfajMqkoIGDMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh # c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD # b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw # MTAwDQYJKoZIhvcNAQEFBQACBQDoYIm6MCIYDzIwMjMwNzE4MTE0NzA2WhgPMjAy # MzA3MTkxMTQ3MDZaMHcwPQYKKwYBBAGEWQoEATEvMC0wCgIFAOhgiboCAQAwCgIB # AAICDQ0CAf8wBwIBAAICEXUwCgIFAOhh2zoCAQAwNgYKKwYBBAGEWQoEAjEoMCYw # DAYKKwYBBAGEWQoDAqAKMAgCAQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG9w0B # AQUFAAOBgQA5F6P/tAHEbTEy3vJtFG/0CLBsgdfNELmGVOk18DntyDgSt5JSpSbw # J9zeDg8PMBB+RU1r4hBbQ2eW86Z7DG5bDOkshjWCL2RLyrW040Juib67yeKyIoMQ # SIIWgwpWFiO/hcrmWapdrCtBnryd00hePSNfPXMD76w4CNl0Gzrd0jGCBA0wggQJ # AgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD # VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAk # BgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAByk/Cs+0D # DRhsAAEAAAHKMA0GCWCGSAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZI # hvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIEIGiwYLOyExukgK4E6P9W4No6cDCEM5RI # 98cOSCbgBfrEMIH6BgsqhkiG9w0BCRACLzGB6jCB5zCB5DCBvQQgEz0b85vrVU2s # lZAk4jt1SDEk6IzZAwVCoWwF3KzcGuAwgZgwgYCkfjB8MQswCQYDVQQGEwJVUzET # MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV # TWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T # dGFtcCBQQ0EgMjAxMAITMwAAAcpPwrPtAw0YbAABAAAByjAiBCA3RCWMeSYQJSKU # gwswNSvu8Y1cpEu/9GhOT6BzChSUtDANBgkqhkiG9w0BAQsFAASCAgCAoqt6nIkm # dBdw/mP+hRPJki+Lc3jHqMirJ/kK5KDzuqto4Pxz0lZYWF7Hpu+J2TOd1ixiTmoz # pULRP6Tzqbm/tm18jC9vGCTMlgj7sN/ESRoMIEJsC1mHHAC9crCrOiaZGt8MWdOs # e8bt54icRf/ahIrSSuul8/1V7A+tysOPTrTs4Kp2MTxd0YtWK53vezCiCs6xGClZ # sPUOFtWAEugXqRQB/YrcLals5myRRkKO4ectm9dJP21txGtolXRR/zYChAexcEFg # hUhetwJ+XguXa4IfrB4tDrb9KU+4wrcfhaly0C79j0inmKEQ3LuRI5XCvELaxXRl # uvBXPNEypCQ4iNg28EYCZwBW2ajpM6OCAfFEzxJVvXfPk6n80mjFafv3X6eAi7T6 # 8N31UalLQ7H2SBCplEAtYuFYQc9UJrNZbkDkOCTSCC/qlTWT9C8oQRb1UMKRdLif # 3JIaZjv9tH8GWvu5Pt7jNQO+L0gnT383D9IV1YkQFw2Ih3fxYhOWJ/inXruPpGxW # oDXnqz/fbr5aNV+sE3rpMNXW+Qg5jindtL3F7KpmBvVrNsUkA1FW8QrPEpf+fODX # bzTu1wEPgwmRIC+PWHN7IODh0QAl6glVeAFqFeLrxL7tCUGC0Al/aySpob/Z8/3H # qt/yY7lQwK0/O65HHyzxC1+NyzvRmq0VDw== # SIG # End signature block |