Framework/Managers/ControlStateExtension.ps1

using namespace System.Management.Automation
Set-StrictMode -Version Latest

class ControlStateExtension
{
    #Static attestation index file object.
    #This gets cashed for every scan and reset for every fresh scan command in servicessecurity status
    [PSObject] $ControlStateIndexer = $null;
    #Property indicates if Attestation index file is present in blob
    [bool] $IsControlStateIndexerPresent = $true;
    hidden [int] $HasControlStateReadPermissions = 1;
    hidden [int] $HasControlStateWritePermissions = -1;
    hidden [string]    $IndexerBlobName ="Resource.index.json"
    
    hidden [int] $retryCount = 3;
    hidden [string] $UniqueRunId;

    hidden [OrganizationContext] $OrganizationContext;
    hidden [InvocationInfo] $InvocationContext;
    hidden [PSObject] $ControlSettings; 
    hidden [PSObject] $resourceType;
    hidden [PSObject] $resourceName;
    hidden [PSObject] $resourceGroupName;
    hidden [PSObject] $AttestationBody;
    [bool] $IsPersistedControlStates = $false;
    [bool] $FailedDownloadForControlStateIndexer = $false
    #hidden [bool] $PrintExtStgPolicyProjErr = $true;
    hidden [bool] $PrintParamPolicyProjErr = $true; 
    hidden [bool] $PrintAttestationRepoErr = $true; 
    hidden static [bool] $IsOrgAttestationProjectFound  = $false; # Flag to represent if Host proj(attestation repo) is avilable for org controls. FALSE => Project or Repo not yet found.
    hidden [AzSKSettings] $AzSKSettings;


    ControlStateExtension([OrganizationContext] $organizationContext, [InvocationInfo] $invocationContext)
    {
        $this.OrganizationContext = $organizationContext;
        $this.InvocationContext = $invocationContext;    
        
        $this.ControlSettings = [ConfigurationManager]::LoadServerConfigFile("ControlSettings.json");    
        $this.AttestationBody = [ConfigurationManager]::LoadServerConfigFile("ADOAttestation.json");

        if (!$this.AzSKSettings) 
        {    
            $this.AzSKSettings = [ConfigurationManager]::GetAzSKSettings();                
        }
    }

    static [string] ComputeHashX([string] $dataToHash)
    {
        return [Helpers]::ComputeHashShort($dataToHash, [Constants]::AttestationHashLen)
    }


    hidden [void] Initialize([bool] $CreateResourcesIfNotExists)
    {
        if([string]::IsNullOrWhiteSpace($this.UniqueRunId))
        {
            $this.UniqueRunId = $(Get-Date -format "yyyyMMdd_HHmmss");
        }

        # this function to check and set access permission
        $this.SetControlStatePermission();

        #Reset attestation index file and set attestation index file present flag to get fresh index file from storage
        $this.ControlStateIndexer = $null;
        $this.IsControlStateIndexerPresent = $true
    }

    # fetch allowed group for attestation from setting file and check user is member of this group and set acccess permission
    hidden [void] SetControlStatePermission()
    {
        try
          {    
            $this.HasControlStateWritePermissions = 1
          }
          catch
          {
              $this.HasControlStateWritePermissions = 0
          }
    }


    hidden [bool] ComputeControlStateIndexer()
    {
        try {
            $AzSKTemp = Join-Path $([Constants]::AzSKAppFolderPath) "Temp" | Join-Path -ChildPath $this.UniqueRunId | Join-Path -ChildPath "ServerControlState";
            if(-not (Test-Path -Path $AzSKTemp))
            {
                New-Item -ItemType Directory -Path $AzSKTemp -Force | Out-Null
            }
            $indexerObject = Get-ChildItem -Path (Join-Path $AzSKTemp $($this.IndexerBlobName)) -Force -ErrorAction Stop | Get-Content | ConvertFrom-Json
        }
        catch {
            #Write-Host $_
        }

        #Cache code: Fetch index file only if index file is null and it is present on storage blob
        if(-not $this.ControlStateIndexer -and $this.IsControlStateIndexerPresent)
        {        
            #Attestation index blob is not preset then return
            [ControlStateIndexer[]] $indexerObjects = @();
            $this.ControlStateIndexer  = $indexerObjects

            $AzSKTemp = Join-Path $([Constants]::AzSKAppFolderPath) "Temp" | Join-Path -ChildPath $this.UniqueRunId | Join-Path -ChildPath "ServerControlState";
            if(-not (Test-Path -Path $AzSKTemp))
            {
                New-Item -ItemType Directory -Path $AzSKTemp -Force | Out-Null
            }

            $indexerObject = @();
            $loopValue = $this.retryCount;
            while($loopValue -gt 0)
            {
                $loopValue = $loopValue - 1;
                try
                {
                  #FailedDownloadForControlStateIndexer is used if file present in repo then variable is false, if file not present then it goes to exception so variable value is true.
                  #If file resent in repo with no content, there will be no exception in api call and respose body will be null
                  $this.FailedDownloadForControlStateIndexer = $false
                  $webRequestResult = $this.GetRepoFileContent( $this.IndexerBlobName );
                  if($webRequestResult){
                           $indexerObject = $webRequestResult 
                  }
                  else {
                      if ($this.FailedDownloadForControlStateIndexer -eq $false) {
                          $this.IsControlStateIndexerPresent = $true
                      }
                      else {
                        $this.IsControlStateIndexerPresent = $false  
                      }
                  }
                  $loopValue = 0;
                }
                catch{
                    #Attestation index blob is not preset then return
                    $this.IsControlStateIndexerPresent = $false
                    return $true;
                }
            }
            $this.ControlStateIndexer += $indexerObject;
        }
        
        return $true;
    }

    # set indexer for rescan post attestation
    hidden [PSObject] RescanComputeControlStateIndexer([string] $projectName, [string] $resourceType)
    {
            #$this.resourceType is used inside the GetProject method to get the project name for organization from extension storage, also return project for other resources
        $this.resourceType = $resourceType;
        if ($resourceType -eq "Organization" -or $resourceType -eq "Project") {
            $this.resourceName = $projectName
        }
        else {
            $this.resourceGroupName = $projectName
        }
        
        [PSObject] $ControlStateIndexerForRescan = $this.GetRepoFileContent($this.IndexerBlobName );
                #setting below global variables null as needed for next resource.
        $this.resourceType = $null;
        $this.resourceName = "";
        $this.resourceGroupName = "";
        
        return $ControlStateIndexerForRescan;
    }
        #isRescan parameter is added to check if method is called from rescan.
    hidden [PSObject] GetControlState([string] $id, [string] $resourceType, [string] $resourceName, [string] $resourceGroupName, [bool] $isRescan = $false)
    {
        try
        {
            $this.resourceType = $resourceType;
            $this.resourceName = $resourceName
            $this.resourceGroupName = $resourceGroupName
            [ControlState[]] $controlStates = @();
            
            if(!$this.GetProject())
            {
                return $null;
            }
            # We reset ControlStateIndexer to null whenever we move to a new project (project context switch)
            if($this.resourceType -eq "Project" ){
                $this.ControlStateIndexer =  $null;
                $this.IsControlStateIndexerPresent = $true;
            }
            #getting resource.index for rescan
            [PSObject] $ControlStateIndexerForRescan = $null;
            [bool] $retVal = $true;
            if ($isRescan) {
                #this is to set project name from GetProject method
                $projectName = $resourceName;
                if ($resourceType -ne "Organization" -and $resourceType -ne "Project") {
                    $projectName = $resourceGroupName
                }
                $ControlStateIndexerForRescan = $this.RescanComputeControlStateIndexer($projectName, $resourceType);
                #Above method setting below blobal variable null so settting them again.
                $this.resourceType = $resourceType;
                $this.resourceName = $resourceName
                $this.resourceGroupName = $resourceGroupName
            }
            else {
                $retVal = $this.ComputeControlStateIndexer();
            }

            if(($null -ne $this.ControlStateIndexer -and  $retVal) -or $isRescan)
            {
                $indexes = @();
                if ($isRescan) {
                    $indexes = $ControlStateIndexerForRescan;
                }
                else {
                    $indexes += $this.ControlStateIndexer
                }

                if ($indexes)
                {
                    $hashId = [ControlStateExtension]::ComputeHashX($id)
                    $selectedIndex = $indexes | Where-Object { $_.HashId -eq $hashId}
                
                    if(($selectedIndex | Measure-Object).Count -gt 0)
                    {
                        $hashId = $selectedIndex.HashId | Select-Object -Unique
                        $controlStateBlobName = $hashId + ".json"

                        $ControlStatesJson = $null;
                        #Fetch attestation file content from repository
                        $ControlStatesJson = $this.GetRepoFileContent($controlStateBlobName)
                        if($ControlStatesJson )
                        {
                            $retVal = $true;
                        }
                        else {
                            $retVal = $false;
                        }

                        #$ControlStatesJson = Get-ChildItem -Path (Join-Path $AzSKTemp $controlStateBlobName) -Force | Get-Content | ConvertFrom-Json
                        if($null -ne $ControlStatesJson)
                        {                    
                            $ControlStatesJson | ForEach-Object {
                                try
                                {
                                    $controlState = [ControlState] $_
                                    $controlStates += $controlState;                                
                                }
                                catch 
                                {
                                    [EventBase]::PublishGenericException($_);
                                }
                            }
                        }
                    }
                }
            }
            if($this.resourceType -eq "Organization" ){
                $this.ControlStateIndexer =  $null;
                $this.IsControlStateIndexerPresent = $true;
            }
            return $controlStates;
        }
        catch{

            if($this.resourceType -eq "Organization"){
                $this.ControlStateIndexer = $null;
                $this.IsControlStateIndexerPresent = $true;
            }
            [EventBase]::PublishGenericException($_);
            return $null;
        }
    }

    hidden [void] SetControlState([string] $id, [ControlState[]] $controlStates, [bool] $Override, [string] $resourceType, [string] $resourceName, [string] $resourceGroupName)
    {    
        $this.resourceType = $resourceType;    
        $this.resourceName = $resourceName;
        $this.resourceGroupName = $resourceGroupName
        
        if(!$this.GetProject())
        {
            return
        }
        
        $AzSKTemp = Join-Path $([Constants]::AzSKAppFolderPath) "Temp" | Join-Path -ChildPath $this.UniqueRunId | Join-Path -ChildPath "ServerControlState";                
        if(-not (Test-Path $(Join-Path $AzSKTemp "ControlState")))
        {
            New-Item -ItemType Directory -Path $(Join-Path $AzSKTemp "ControlState") -ErrorAction Stop | Out-Null
        }
        else
        {
            Remove-Item -Path $(Join-Path $AzSKTemp "ControlState" | Join-Path -ChildPath '*' ) -Force -Recurse 
        }
        
        $hash = [ControlStateExtension]::ComputeHashX($id) 
        $indexerPath = Join-Path $AzSKTemp "ControlState" | Join-Path -ChildPath $this.IndexerBlobName;
        if(-not (Test-Path -Path (Join-Path $AzSKTemp "ControlState")))
        {
            New-Item -ItemType Directory -Path (Join-Path $AzSKTemp "ControlState") -Force
        }
        $fileName = Join-Path $AzSKTemp "ControlState" | Join-Path -ChildPath ($hash+".json");
        
        #Filter out the "Passed" controls
        $finalControlStates = $controlStates | Where-Object { $_.ActualVerificationResult -ne [VerificationResult]::Passed};
        if(($finalControlStates | Measure-Object).Count -gt 0)
        {
            $this.IsPersistedControlStates = $false;
            if($Override)
            {
                $this.IsPersistedControlStates = $true;
                # in the case of override, just persist what is evaluated in the current context. No merging with older data
                $this.UpdateControlIndexer($id, $finalControlStates, $false);
                $finalControlStates = $finalControlStates | Where-Object { $_.State};
            }
            else
            {
                #merge with the exiting if found
                $persistedControlStates = $this.GetPersistedControlStates("$hash.json");
                $finalControlStates = $this.MergeControlStates($persistedControlStates, $finalControlStates);

                # COmmenting this code out. We will be handling encoding-decoding to b64 at SetStateData and WriteDetailedLogs.ps1
                
                #$finalControl = @();
                ##convert state data object to encoded string
                #foreach ($controls in $finalControlStates) {
                # # checking If state.DataObject is not empty and dataobject is not encode string, if control is already attested it will have encoded string
                # if ($controls.state.DataObject -and !($controls.state.DataObject -is [string]) ) {
                # try {
                # #when dataobject is empty it comes like {} and null check does not work it alwasys count 1
                # if ($controls.state.DataObject.count -gt 0) {
                # $stateData = $controls.state.DataObject | ConvertTo-Json -Depth 10
                # $encodedStateData =[Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($stateData))
                # $controls.state.DataObject = $encodedStateData;
                # }
                # }
                # catch {
                # #eat the exception
                # }
                # }
                # $finalControl += $controls;
                #}
                #$finalControlStates = $finalControl;
                $this.UpdateControlIndexer($id, $finalControlStates, $false);
                
            }
        }
        else
        {
            #purge would remove the entry from the control indexer and also purge the stale state json.
            $this.PurgeControlState($id);
        }
        if(($finalControlStates|Measure-Object).Count -gt 0)
        {
            [JsonHelper]::ConvertToJsonCustom($finalControlStates) | Out-File $fileName -Force        
        }

        if($null -ne $this.ControlStateIndexer)
        {                
            [JsonHelper]::ConvertToJsonCustom($this.ControlStateIndexer) | Out-File $indexerPath -Force
            $controlStateArray = Get-ChildItem -Path (Join-Path $AzSKTemp "ControlState")
            $controlStateArray | ForEach-Object {
                $state = $_;
                try
                {
                    $this.UploadFileContent($state.FullName);
                }
                catch
                {
                    $_
                    #eat this exception and retry
                }
            }
        }
    }

    [void] UploadFileContent( $FullName )
    {
        $fileContent = Get-Content -Path $FullName -raw  
        $fileName = $FullName.split('\')[-1];

        $projectName = $this.GetProject();
        $attestationRepo = [Constants]::AttestationRepo;
        #Get attesttion repo name from controlsetting file if AttestationRepo varibale value is not empty.
        if ([Helpers]::CheckMember($this.ControlSettings,"AttestationRepo")) {
            $attestationRepo =  $this.ControlSettings.AttestationRepo;
        }
        #Get attesttion repo name from local azsksettings.json file if AttestationRepo varibale value is not empty.
        if ($this.AzSKSettings.AttestationRepo) {
            $attestationRepo = $this.AzSKSettings.AttestationRepo;
        }

        $rmContext = [ContextHelper]::GetCurrentContext();
        $user = "";
        $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$rmContext.AccessToken)))
       
        $uri = "https://dev.azure.com/{0}/{1}/_apis/git/repositories/{2}/refs?api-version=6.0" -f $this.OrganizationContext.OrganizationName, $projectName, $attestationRepo 
        try {
        $webRequest = Invoke-RestMethod -Uri $uri -Method Get -ContentType "application/json" -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)}
        $branchName = [Constants]::AttestationDefaultBranch;
        #Get attesttion branch name from controlsetting file if AttestationBranch varibale value is not empty.
        if ([Helpers]::CheckMember($this.ControlSettings,"AttestationBranch")) {
            $branchName =  $this.ControlSettings.AttestationBranch;
        }
        #Get attesttion branch name from local azsksettings.json file if AttestationBranch varibale value is not empty.
        if ($this.AzSKSettings.AttestationBranch) {
            $branchName = $this.AzSKSettings.AttestationBranch;
        }
        
        $branchId = ($webRequest.value | where {$_.name -eq "refs/heads/"+$branchName}).ObjectId

        $uri = [Constants]::AttRepoStorageUri -f $this.OrganizationContext.OrganizationName, $projectName, $attestationRepo  
        $body = $this.CreateBody($fileContent, $fileName, $branchId, $branchName);
        $webRequestResult = Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)} -Body $body

        if ($fileName -eq $this.IndexerBlobName) {
           $this.IsControlStateIndexerPresent = $true;
         }   
       }
        catch {
            Write-Host "Error: Attestation denied.`nThis may be because: `n (a) $($attestationRepo) repository is not present in the project `n (b) you do not have write permission on the repository. `n" -ForegroundColor Red
            Write-Host "See more at https://aka.ms/adoscanner/attestation `n" -ForegroundColor Yellow 
        }
    }

    
    [string] CreateBody([string] $fileContent, [string] $fileName, [string] $branchId, [string] $branchName){
        
        $body = $this.AttestationBody.Post | ConvertTo-Json -Depth 10
        $body = $body.Replace("{0}",$branchId) 

        $body = $body.Replace("{2}", $this.CreatePath($fileName))  
        if ( $this.IsControlStateIndexerPresent -and $fileName -eq $this.IndexerBlobName ) {
            $body = $body.Replace("{1}","edit") 
        }
        elseif ($this.IsPersistedControlStates -and $fileName -ne $this.IndexerBlobName ) {
            $body = $body.Replace("{1}","edit") 
        }
        else {
            $body = $body.Replace("{1}","add") 
        }

        $content = ($fileContent | ConvertTo-Json -Depth 10) -replace '^.|.$', ''
        $body = $body.Replace("{3}", $content)
        $body = $body.Replace("{4}", $branchName)

        return $body;         
    }

    [string] CreatePath($fileName){
        $path = $fileName
        if (!($this.resourceType -eq "Organization" -or $fileName -eq $this.IndexerBlobName) -and ($this.resourceType -ne "Project")) {
            $path = $this.resourceGroupName + "/" + $this.resourceType + "/" + $fileName;
        }
        elseif(!($this.resourceType -eq "Organization" -or $fileName -eq $this.IndexerBlobName))
        {
            $path = $this.resourceName + "/" + $fileName;
        }
        
        return $path;
    }

    [string] GetProject(){
        $projectName = "";
        #If EnableMultiProjectAttestation is enabled and ProjectToStoreAttestation has project, only then ProjectToStoreAttestation will be used as central attestation location.
        if ([Helpers]::CheckMember($this.ControlSettings, "EnableMultiProjectAttestation") -and [Helpers]::CheckMember($this.ControlSettings, "ProjectToStoreAttestation")) {
            return $this.ControlSettings.ProjectToStoreAttestation;
        }
        if ($this.resourceType -eq "Organization" -or $this.resourceType -eq $null) 
        {
            if($this.InvocationContext)
            {
            #Get project name from ext storage to fetch org attestation
            $projectName = $this.GetProjectNameFromExtStorage();
            $printCentralOrgPolicyMessage = $false;
            #If not found then check if 'PolicyProject' parameter is provided in command
            if ([string]::IsNullOrEmpty($projectName))
            {
                $projectName = [AzSKSettings]::InvocationContext.BoundParameters["PolicyProject"];
                if(-not [string]::IsNullOrEmpty($projectName))
                {
                    # Handle the case of org policy hosted in another Org
                    $policyProjectOrgInfo = $projectName.split("/"); 
                    if ($policyProjectOrgInfo.length -eq 2) {
                        $printCentralOrgPolicyMessage = $true;
                        $projectName = $null;
                    }
                }
                if ([string]::IsNullOrEmpty($projectName))
                {
                    #TODO: azsk setting fetching and add comment for EnableOrgControlAttestation
                    if (!$this.AzSKSettings) 
                    {    
                        $this.AzSKSettings = [ConfigurationManager]::GetAzSKSettings();                
                    }
                    $projectName = $this.AzSKSettings.PolicyProject    
                    if(-not [string]::IsNullOrEmpty($projectName))
                    {
                        # Handle the case of org policy hosted in another Org
                        $policyProjectOrgInfo = $projectName.split("/"); 
                        if ($policyProjectOrgInfo.length -eq 2) {
                            $projectName = $null;
                            $printCentralOrgPolicyMessage = $true;
                        }
                    }
                    $enableOrgControlAttestation = $this.AzSKSettings.EnableOrgControlAttestation
                    if([string]::IsNullOrEmpty($projectName) -and $printCentralOrgPolicyMessage -eq $true -and $enableOrgControlAttestation)
                    {
                        Write-Host "Attestation is not enabled for centralized org policy." -ForegroundColor Red
                    }

                    if([string]::IsNullOrEmpty($projectName))
                    {
                        if ($this.PrintParamPolicyProjErr -eq $true -and $enableOrgControlAttestation -eq $true)
                        {
                            Write-Host -ForegroundColor Yellow "Could not fetch attestation-project-name. `nYou can: `n`r(a) Run Set-AzSKADOMonitoringSetting -PolicyProject '<PolicyProjectName>' or `n`r(b) Use '-PolicyProject' parameter to specify the host project containing attestation details of organization controls."
                            $this.PrintParamPolicyProjErr = $false;
                        }   
                    }
                }

                #If $projectName was set in the above if clause - we need to next validate whether this project has an attestattion repo as shown below.
                if(-not [string]::IsNullOrEmpty($projectName)) 
                {
                    if ([ControlStateExtension]::IsOrgAttestationProjectFound -eq $false)
                    {
                        #Validate if Attestation repo is available in policy project
                        $attestationRepo = [Constants]::AttestationRepo;
                        try 
                        {
                            $rmContext = [ContextHelper]::GetCurrentContext();
                            $user = "";
                            $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$rmContext.AccessToken)))
                        
                            #Get attesttion repo name from controlsetting file if AttestationRepo varibale value is not empty.
                            if ([Helpers]::CheckMember($this.ControlSettings,"AttestationRepo")) {
                                $attestationRepo =  $this.ControlSettings.AttestationRepo;
                            }
                            #Get attesttion repo name from local azsksettings.json file if AttestationRepo varibale value is not empty.
                            if ($this.AzSKSettings.AttestationRepo) {
                                $attestationRepo = $this.AzSKSettings.AttestationRepo;
                            }

                            $uri = "https://dev.azure.com/{0}/{1}/_apis/git/repositories/{2}/refs?api-version=6.0" -f $this.OrganizationContext.OrganizationName, $projectName, $attestationRepo
                            $webRequest = Invoke-RestMethod -Uri $uri -Method Get -ContentType "application/json" -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)}
                            [ControlStateExtension]::IsOrgAttestationProjectFound = $true # Policy project and repo found
                        }
                        catch {
                            $projectName = "";
                            #2010 ToDO: [ControlStateExtension]::IsOrgAttestationProjectFound = $false # Policy project and repo found
                            if ($this.PrintAttestationRepoErr -eq $true)
                            {
                                Write-Host -ForegroundColor Yellow "Could not find attestation repo [$($attestationRepo)] in the policy project."
                                $this.PrintAttestationRepoErr = $false;
                            }

                            # eat exception. This means attestation repo was not found
                            # attestation repo is required to scan org controls and send hasrequiredaccess as true
                        }
                    }
                }
            }}
        }
        elseif($this.resourceType -eq "Project" )
        {
            $projectName = $this.resourceName
        }
        else {
            $projectName = $this.resourceGroupName
        }
        
        return $projectName;
    }

    [string] GetProjectNameFromExtStorage()
    {
        try {
            $rmContext = [ContextHelper]::GetCurrentContext();
            $user = "";
            $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$rmContext.AccessToken)))
            
            $uri = [Constants]::StorageUri -f $this.OrganizationContext.OrganizationName, $this.OrganizationContext.OrganizationName, [Constants]::OrgAttPrjExtFile 
            $webRequestResult = Invoke-RestMethod -Uri $uri -Method Get -ContentType "application/json" -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)}
            #If repo is not found, we will fall into the catch block from IRM call above
            [ControlStateExtension]::IsOrgAttestationProjectFound = $true # Policy project found
            return $webRequestResult.Project
        }
        catch {
            #2010 ToDo: [ControlStateExtension]::IsOrgAttestationProjectFound = $false # Policy project not found
            return $null;
        }
    }

    [bool] SetProjectInExtForOrg() {
        $projectName = $this.InvocationContext.BoundParameters["AttestationHostProjectName"]
        $rmContext = [ContextHelper]::GetCurrentContext();
        $user = "";
        $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user, $rmContext.AccessToken)))
        $fileName = [Constants]::OrgAttPrjExtFile 

        $apiURL = "https://dev.azure.com/{0}/_apis/projects/{1}?api-version=6.0" -f $($this.OrganizationContext.OrganizationName), $projectName;
        try { 
            $responseObj = [WebRequestHelper]::InvokeGetWebRequest($apiURL) ;
            #$projects = $responseObj | Where-Object { $projectName -contains $_.name }
            #if ($null -eq $projects) {
            # Write-Host "$($projectName) Project not found: Incorrect project name or you do not have neccessary permission to access the project." -ForegroundColor Red
            # return $false
            #}
                   
        }
        catch {
            Write-Host "$($projectName) Project not found: Incorrect project name or you do not have necessary permission to access the project." -ForegroundColor Red
            return $false
        }
               
        $uri = [Constants]::StorageUri -f $this.OrganizationContext.OrganizationName, $this.OrganizationContext.OrganizationName, $fileName
        try {
            $webRequestResult = Invoke-RestMethod -Uri $uri -Method Get -ContentType "application/json" -Headers @{Authorization = ("Basic {0}" -f $base64AuthInfo) }
            Write-Host "Project $($webRequestResult.Project) is already configured to store attestation details for organization-specific controls." -ForegroundColor Yellow
        }
        catch {
            $body = @{"id" = "$fileName"; "Project" = $projectName; } | ConvertTo-Json
            $uri = [Constants]::StorageUri -f $this.OrganizationContext.OrganizationName, $this.OrganizationContext.OrganizationName, $fileName  
            try {
                $webRequestResult = Invoke-RestMethod -Uri $uri -Method Put -ContentType "application/json" -Headers @{Authorization = ("Basic {0}" -f $base64AuthInfo) } -Body $body    
                return $true;
            }
            catch {    
            Write-Host "Error: Could not configure host project for attestation of org-specific controls because 'ADOSecurityScanner' extension is not installed in your organization." -ForegroundColor Red
            }
                
        }
        return $false;
    }

    [PSObject] GetRepoFileContent($fileName)
    {
        $projectName = $this.GetProject();
        $branchName =  [Constants]::AttestationDefaultBranch
        #Get attesttion branch name from controlsetting file if AttestationBranch varibale value is not empty.
        if ([Helpers]::CheckMember($this.ControlSettings,"AttestationBranch")) {
            $branchName =  $this.ControlSettings.AttestationBranch;
        }
        #Get attesttion branch name from local azsksettings.json file if AttestationBranch varibale value is not empty.
        if ($this.AzSKSettings.AttestationBranch) {
            $branchName = $this.AzSKSettings.AttestationBranch;
        } 

        $fileName = $this.CreatePath($fileName);

        $rmContext = [ContextHelper]::GetCurrentContext();
        $user = "";
        $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$rmContext.AccessToken)))
        
        try
        {
            $attestationRepo = [Constants]::AttestationRepo;
            #Get attesttion repo name from controlsetting file if AttestationRepo varibale value is not empty.
            if ([Helpers]::CheckMember($this.ControlSettings,"AttestationRepo")) {
                $attestationRepo =  $this.ControlSettings.AttestationRepo;
            }
            #Get attesttion repo name from local azsksettings.json file if AttestationRepo varibale value is not empty.
            if ($this.AzSKSettings.AttestationRepo) {
                $attestationRepo = $this.AzSKSettings.AttestationRepo;
            }
           $uri = [Constants]::GetAttRepoStorageUri -f $this.OrganizationContext.OrganizationName, $projectName, $attestationRepo, $fileName, $branchName 
           $webRequestResult = Invoke-RestMethod -Uri $uri -Method Get -ContentType "application/json" -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)}
           if ($webRequestResult) {
            # COmmenting this code out. We will be handling encoding-decoding to b64 at SetStateData and WriteDetailedLogs.ps1

            #if($fileName -ne $this.IndexerBlobName)
            #{
            # #convert back state data from encoded string
            # $attestationData = @();
            # foreach ($controls in $webRequestResult)
            # {
            # if($controls.State.DataObject -is [string])
            # {
            # $controls.State.DataObject = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($controls.State.DataObject)) | ConvertFrom-Json
            # }
            # $attestationData += $controls;
            # }
            # $webRequestResult = $attestationData;
            #}
            return $webRequestResult
           }
           return $null;
        }
        catch{
            if ($fileName -eq  $this.IndexerBlobName) {
                $this.FailedDownloadForControlStateIndexer = $true
            }
            return $null;
        }
    }

    [void] RemoveAttestationData($fileName)
    {
        $projectName = $this.GetProject();
        $fileName = $this.CreatePath($fileName);
        $attestationRepo = [Constants]::AttestationRepo;
        #Get attesttion repo name from controlsetting file if AttestationRepo varibale value is not empty.
        if ([Helpers]::CheckMember($this.ControlSettings,"AttestationRepo")) {
            $attestationRepo =  $this.ControlSettings.AttestationRepo;
        }
        #Get attesttion repo name from local azsksettings.json file if AttestationRepo varibale value is not empty.
        if ($this.AzSKSettings.AttestationRepo) {
            $attestationRepo = $this.AzSKSettings.AttestationRepo;
        }

        $rmContext = [ContextHelper]::GetCurrentContext();
        $user = "";
        $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$rmContext.AccessToken)))
        
        $uri = "https://dev.azure.com/{0}/{1}/_apis/git/repositories/{2}/refs?api-version=6.0" -f $this.OrganizationContext.OrganizationName, $projectName, $attestationRepo
        $webRequest = Invoke-RestMethod -Uri $uri -Method Get -ContentType "application/json" -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)}
        $branchId = ($webRequest.value | where {$_.name -eq 'refs/heads/master'}).ObjectId
        
        $body = $this.AttestationBody.Delete | ConvertTo-Json -Depth 10;
        $body = $body.Replace('{0}',$branchId)
        $body = $body.Replace('{1}',$fileName)
        
        $branchName = [Constants]::AttestationDefaultBranch;
        #Get attesttion branch name from controlsetting file if AttestationBranch varibale value is not empty.
        if ([Helpers]::CheckMember($this.ControlSettings,"AttestationBranch")) {
            $branchName =  $this.ControlSettings.AttestationBranch;
        }
        #Get attesttion branch name from local azsksettings.json file if AttestationBranch varibale value is not empty.
        if ($this.AzSKSettings.AttestationBranch) {
            $branchName = $this.AzSKSettings.AttestationBranch;
        }
        $body = $body.Replace('{2}',$branchName)

        try
        {
           $uri = [Constants]::AttRepoStorageUri -f $this.OrganizationContext.OrganizationName, $projectName, $attestationRepo 
           $webRequestResult = Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)} -Body $body
        }
        catch{
            Write-Host "Could not remove attastation for: " + $fileName;
            Write-Host $_
        }
    }

    hidden [void] PurgeControlState([string] $id)
    {        
        $AzSKTemp = Join-Path $([Constants]::AzSKAppFolderPath) "Temp" | Join-Path -ChildPath $this.UniqueRunId | Join-Path -ChildPath "ServerControlState";                
        if(-not (Test-Path $(Join-Path $AzSKTemp "ControlState")))
        {
            New-Item -ItemType Directory -Path (Join-Path $AzSKTemp "ControlState") -ErrorAction Stop | Out-Null
        }
        else
        {
            Remove-Item -Path $(Join-Path $AzSKTemp "ControlState" | Join-Path -ChildPath '*') -Force -Recurse
        }

        $hash = [ControlStateExtension]::ComputeHashX($id);
        $indexerPath = Join-Path $AzSKTemp "ControlState" | Join-Path -ChildPath $this.IndexerBlobName ;
        $fileName = Join-Path $AzSKTemp "ControlState" | Join-Path -ChildPath ("$hash.json");
        
        $this.UpdateControlIndexer($id, $null, $true);
        if($null -ne $this.ControlStateIndexer)
        {                
            [JsonHelper]::ConvertToJsonCustom($this.ControlStateIndexer) | Out-File $indexerPath -Force
            $controlStateArray = Get-ChildItem -Path (Join-Path $AzSKTemp "ControlState");                
            $controlStateArray | ForEach-Object {
                $state = $_
                $loopValue = $this.retryCount;
                while($loopValue -gt 0)
                {
                    $loopValue = $loopValue - 1;
                    try
                    {
                        $this.UploadFileContent($state.FullName);
                        $loopValue = 0;
                    }
                    catch
                    {
                        #eat this exception and retry
                    }
                }
            }
        }
        try
        {
            $hashFile = "$hash.json";
            $this.RemoveAttestationData($hashFile)
        }
        catch
        {
            #eat this exception and retry
        }    
    }

    hidden [ControlState[]] GetPersistedControlStates([string] $controlStateBlobName)
    {
        $AzSKTemp = Join-Path $([Constants]::AzSKAppFolderPath) "Temp" | Join-Path -ChildPath $this.UniqueRunId | Join-Path -ChildPath "ServerControlState";
        if(-not (Test-Path (Join-Path $AzSKTemp "ExistingControlStates")))
        {
            New-Item -ItemType Directory -Path (Join-Path $AzSKTemp "ExistingControlStates") -ErrorAction Stop | Out-Null
        }
    
        [ControlState[]] $ControlStatesJson = @()

        $loopValue = $this.retryCount;
        while($loopValue -gt 0)
        {
            $loopValue = $loopValue - 1;
            try
            {
                #$ControlStatesJson = @()
                $ControlStatesJson = $this.GetRepoFileContent($controlStateBlobName) 
                if ($ControlStatesJson) {
                    $this.IsPersistedControlStates = $true
                }
                $loopValue = 0;
            }
            catch
            {
                $this.IsPersistedControlStates = $false;
                #$ControlStatesJson = @()
                #eat this exception and retry
            }
        }

        return $ControlStatesJson
    }

    hidden [ControlState[]] MergeControlStates([ControlState[]] $persistedControlStates,[ControlState[]] $controlStates)
    {
        [ControlState[]] $computedControlStates = $controlStates;
        if(($computedControlStates | Measure-Object).Count -le 0)
        {
            $computedControlStates = @();
        }
        if(($persistedControlStates | Measure-Object).Count -gt 0)
        {
            $persistedControlStates | ForEach-Object {
                $controlState = $_;
                if(($computedControlStates | Where-Object { ($_.InternalId -eq $controlState.InternalId) -and ($_.ChildResourceName -eq $controlState.ChildResourceName) } | Measure-Object).Count -le 0)
                {
                    $computedControlStates += $controlState;
                }
            }
        }
        #remove the control states with null state which would be in the case of clear attestation.
        $computedControlStates = $computedControlStates | Where-Object { $_.State}

        return $computedControlStates;
    }

    hidden [void] UpdateControlIndexer([string] $id, [ControlState[]] $controlStates, [bool] $ToBeDeleted)
    {
        $this.ControlStateIndexer = $null;
        $retVal = $this.ComputeControlStateIndexer();

        if($retVal)
        {                
            $tempHash = [ControlStateExtension]::ComputeHashX($id);
            #take the current indexer value
            $filteredIndexerObject = $null;
            $filteredIndexerObject2 = $null;
            if ($this.ControlStateIndexer -and ($this.ControlStateIndexer | Measure-Object).Count -gt 0) {
                $filteredIndexerObject = $this.ControlStateIndexer | Where-Object { $_.HashId -eq $tempHash}
                #remove the current index from the list
                $filteredIndexerObject2 = $this.ControlStateIndexer | Where-Object { $_.HashId -ne $tempHash}
            }

            $this.ControlStateIndexer = @();
            if($filteredIndexerObject2)
            {
              $this.ControlStateIndexer += $filteredIndexerObject2
            }
            if(-not $ToBeDeleted)
            {    
                $currentIndexObject = $null;
                #check if there is an existing index and the controlstates are present for that index resource
                if(($filteredIndexerObject | Measure-Object).Count -gt 0 -and ($controlStates | Measure-Object).Count -gt 0)
                {
                    $currentIndexObject = $filteredIndexerObject;
                    if(($filteredIndexerObject | Measure-Object).Count -gt 1)
                    {
                        $currentIndexObject = $filteredIndexerObject | Select-Object -Last 1
                    }                    
                    $currentIndexObject.AttestedBy = [ContextHelper]::GetCurrentSessionUser();
                    $currentIndexObject.AttestedDate = [DateTime]::UtcNow;
                    $currentIndexObject.Version = "1.0";
                }
                elseif(($controlStates | Measure-Object).Count -gt 0)
                {
                    $currentIndexObject = [ControlStateIndexer]::new();
                    $currentIndexObject.ResourceId = $id
                    $currentIndexObject.HashId = $tempHash;
                    $currentIndexObject.AttestedBy = [ContextHelper]::GetCurrentSessionUser();
                    $currentIndexObject.AttestedDate = [DateTime]::UtcNow;
                    $currentIndexObject.Version = "1.0";
                }
                if($null -ne $currentIndexObject)
                {
                    $this.ControlStateIndexer += $currentIndexObject;            
                }
            }
        }
    }
    
    [bool] HasControlStateReadAccessPermissions()
    {
        if($this.HasControlStateReadPermissions -le 0)
        {
            return $false;
        }
        else
        {
            return $true;
        }
    }

    [void] SetControlStateReadAccessPermissions([int] $value)
    {
        $this.HasControlStateReadPermissions  = $value
    }

    [void] SetControlStateWriteAccessPermissions([int] $value)
    {
        $this.HasControlStateWritePermissions  = $value
    }

    [bool] HasControlStateWriteAccessPermissions()
    {        
        if($this.HasControlStateWritePermissions -le 0)
        {
            return $false;
        }
        else
        {
            return $true;
        }
    }

    [bool] GetControlStatePermission([string] $featureName, [string] $resourceName)
    {
        try
          {    
            $this.HasControlStateWritePermissions = 0
     
            $allowedGrpForOrgAtt = $this.ControlSettings.GroupsWithAttestPermission | where { $_.ResourceType -eq "Organization" } | select-object -property GroupNames 
            
            $url= "https://dev.azure.com/{0}/_apis/Contribution/HierarchyQuery?api-version=5.1-preview" -f $($this.OrganizationContext.OrganizationName);
            $postbody="{'contributionIds':['ms.vss-admin-web.org-admin-groups-data-provider'],'dataProviderContext':{'properties':{'sourcePage':{'url':'https://dev.azure.com/$($this.OrganizationContext.OrganizationName)/_settings/groups','routeId':'ms.vss-admin-web.collection-admin-hub-route','routeValues':{'adminPivot':'groups','controller':'ContributedPage','action':'Execute'}}}}}" | ConvertFrom-Json
            $groupsOrgObj = [WebRequestHelper]::InvokePostWebRequest($url,$postbody);
            $groupsOrgObj = $groupsOrgObj.dataProviders.'ms.vss-admin-web.org-admin-groups-data-provider'.identities | where { $allowedGrpForOrgAtt.GroupNames -contains $_.displayName }

            if($this.CheckGroupMemberPCA($groupsOrgObj.descriptor)){
                return $true;
            }

            if($featureName -ne "Organization")
            {
               $allowedGrpForAtt = $this.ControlSettings.GroupsWithAttestPermission | where { $_.ResourceType -eq $featureName } | select-object -property GroupNames             
               $url = 'https://dev.azure.com/{0}/_apis/Contribution/HierarchyQuery?api-version=5.0-preview.1' -f $($this.OrganizationContext.OrganizationName);
               $inputbody = '{"contributionIds":["ms.vss-admin-web.org-admin-groups-data-provider"],"dataProviderContext":{"properties":{"sourcePage":{"url":"","routeId":"ms.vss-admin-web.project-admin-hub-route","routeValues":{"project":"","adminPivot":"permissions","controller":"ContributedPage","action":"Execute"}}}}}' | ConvertFrom-Json
               $inputbody.dataProviderContext.properties.sourcePage.url = "https://dev.azure.com/$($this.OrganizationContext.OrganizationName)/$($resourceName)/_settings/permissions";
               $inputbody.dataProviderContext.properties.sourcePage.routeValues.Project =$resourceName;
       
               $groupsObj = [WebRequestHelper]::InvokePostWebRequest($url,$inputbody); 
               $groupsObj = $groupsObj.dataProviders."ms.vss-admin-web.org-admin-groups-data-provider".identities | where { $allowedGrpForAtt.GroupNames -contains $_.displayName }

               foreach ($group in $groupsObj)
               { 
                if($this.CheckGroupMemberPA($group.descriptor,$resourceName)){
                    return $true;
                }    
               }
            }
            if($this.HasControlStateWritePermissions -gt 0)
            {
              return $true
            }
            else
            {
                return $false
            }
          }
          catch
          {
              $this.HasControlStateWritePermissions = 0
              return $false;
          }
    }

    [bool] CheckGroupMemberPA($descriptor,[string] $resourceName)
    {
        <#
        $inputbody = '{"contributionIds":["ms.vss-admin-web.org-admin-members-data-provider"],"dataProviderContext":{"properties":{"subjectDescriptor":"","sourcePage":{"url":"","routeId":"ms.vss-admin-web.collection-admin-hub-route","routeValues":{"adminPivot":"groups","controller":"ContributedPage","action":"Execute"}}}}}' | ConvertFrom-Json
        
        $inputbody.dataProviderContext.properties.subjectDescriptor = $descriptor;
        $inputbody.dataProviderContext.properties.sourcePage.url = "https://dev.azure.com/$($this.OrganizationContext.OrganizationName)/_settings/groups?subjectDescriptor=$($descriptor)";
        
        $apiURL = "https://dev.azure.com/{0}/_apis/Contribution/HierarchyQuery?api-version=5.0-preview" -f $($this.OrganizationContext.OrganizationName);
 
        $groupMembersObj = [WebRequestHelper]::InvokePostWebRequest($apiURL,$inputbody);
        $users = $groupMembersObj.dataProviders."ms.vss-admin-web.org-admin-members-data-provider".identities | where {$_.subjectKind -eq "user"}
 
        if($null -ne $users){
            $currentUser = [ContextHelper]::GetCurrentSessionUser();
            $grpmember = ($users | where { $_.mailAddress -eq $currentUser } );
            if ($null -ne $grpmember ) {
                 $this.HasControlStateWritePermissions = 1
                 return $true;
            }
        }
        if($this.HasControlStateWritePermissions -gt 0)
        {
          return $true
        }
        else
        {
            return $false
        }#>


        $isUserPA=[AdministratorHelper]::GetIsCurrentUserPA($descriptor,$this.OrganizationContext.OrganizationName,$resourceName);
        if($isUserPA -eq $true){
            $this.HasControlStateWritePermissions = 1
            return $true;
        }
        if($this.HasControlStateWritePermissions -gt 0)
        {
          return $true
        }
        else
        {
            return $false
        }

    }

    [bool] CheckGroupMemberPCA($descriptor){
        $isUserPCA=[AdministratorHelper]::GetIsCurrentUserPCA($descriptor,$this.OrganizationContext.OrganizationName);
        if($isUserPCA -eq $true){
            $this.HasControlStateWritePermissions = 1
            return $true;
        }
        if($this.HasControlStateWritePermissions -gt 0)
        {
          return $true
        }
        else
        {
            return $false
        }
    }


}

# SIG # Begin signature block
# MIInoAYJKoZIhvcNAQcCoIInkTCCJ40CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDzD6oK+67MzfTD
# mecs7qpAgpNgi/amWx6FvlgSa7rs3aCCDYEwggX/MIID56ADAgECAhMzAAACUosz
# qviV8znbAAAAAAJSMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMjU5WhcNMjIwOTAxMTgzMjU5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDQ5M+Ps/X7BNuv5B/0I6uoDwj0NJOo1KrVQqO7ggRXccklyTrWL4xMShjIou2I
# sbYnF67wXzVAq5Om4oe+LfzSDOzjcb6ms00gBo0OQaqwQ1BijyJ7NvDf80I1fW9O
# L76Kt0Wpc2zrGhzcHdb7upPrvxvSNNUvxK3sgw7YTt31410vpEp8yfBEl/hd8ZzA
# v47DCgJ5j1zm295s1RVZHNp6MoiQFVOECm4AwK2l28i+YER1JO4IplTH44uvzX9o
# RnJHaMvWzZEpozPy4jNO2DDqbcNs4zh7AWMhE1PWFVA+CHI/En5nASvCvLmuR/t8
# q4bc8XR8QIZJQSp+2U6m2ldNAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUNZJaEUGL2Guwt7ZOAu4efEYXedEw
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDY3NTk3MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAFkk3
# uSxkTEBh1NtAl7BivIEsAWdgX1qZ+EdZMYbQKasY6IhSLXRMxF1B3OKdR9K/kccp
# kvNcGl8D7YyYS4mhCUMBR+VLrg3f8PUj38A9V5aiY2/Jok7WZFOAmjPRNNGnyeg7
# l0lTiThFqE+2aOs6+heegqAdelGgNJKRHLWRuhGKuLIw5lkgx9Ky+QvZrn/Ddi8u
# TIgWKp+MGG8xY6PBvvjgt9jQShlnPrZ3UY8Bvwy6rynhXBaV0V0TTL0gEx7eh/K1
# o8Miaru6s/7FyqOLeUS4vTHh9TgBL5DtxCYurXbSBVtL1Fj44+Od/6cmC9mmvrti
# yG709Y3Rd3YdJj2f3GJq7Y7KdWq0QYhatKhBeg4fxjhg0yut2g6aM1mxjNPrE48z
# 6HWCNGu9gMK5ZudldRw4a45Z06Aoktof0CqOyTErvq0YjoE4Xpa0+87T/PVUXNqf
# 7Y+qSU7+9LtLQuMYR4w3cSPjuNusvLf9gBnch5RqM7kaDtYWDgLyB42EfsxeMqwK
# WwA+TVi0HrWRqfSx2olbE56hJcEkMjOSKz3sRuupFCX3UroyYf52L+2iVTrda8XW
# esPG62Mnn3T8AuLfzeJFuAbfOSERx7IFZO92UPoXE1uEjL5skl1yTZB3MubgOA4F
# 8KoRNhviFAEST+nG8c8uIsbZeb08SeYQMqjVEmkwggd6MIIFYqADAgECAgphDpDS
# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla
# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT
# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG
# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S
# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz
# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7
# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u
# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33
# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl
# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP
# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB
# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF
# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM
# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ
# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud
# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO
# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0
# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p
# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw
# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA
# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY
# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj
# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd
# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ
# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf
# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ
# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j
# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B
# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96
# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7
# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I
# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIZdTCCGXECAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAlKLM6r4lfM52wAAAAACUjAN
# BglghkgBZQMEAgEFAKCBsDAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQga08KTDXE
# 1VI70IXM4pEkse5GW6WyjMqQtZsvubezCDIwRAYKKwYBBAGCNwIBDDE2MDSgFIAS
# AE0AaQBjAHIAbwBzAG8AZgB0oRyAGmh0dHBzOi8vd3d3Lm1pY3Jvc29mdC5jb20g
# MA0GCSqGSIb3DQEBAQUABIIBAHkOK/mSHZuVjeoMB+rXCiuOa2C9MYul30Jjqcph
# EGwxkrYFPKP2/eG6KT467e0QHb+6jTLNajr7RqyuR/0NT4ir5bdWSj+EWrpl37eZ
# BjFzVNhg2H1WneS0A0OaRxzW0HwA0/tGrIY1kyrPXAE8FoCHKARor1z5mI9X7pFd
# YVtLP4XA7JHeFWHT8+RXTo1pgt4cWdxUk84MAO35GEACRFxtagqMCfOp+oCKAbFK
# GkC5CiD3ahmKSh7JEU9RLyuILVid+Hj3xW7yAOtwImYeMYRbyzteiXq1OHxIj72c
# kJpoa3wQOSap/ml1ZSfO2lebFN1+aw4ojg+eO+BOl0NNnuyhghb9MIIW+QYKKwYB
# BAGCNwMDATGCFukwghblBgkqhkiG9w0BBwKgghbWMIIW0gIBAzEPMA0GCWCGSAFl
# AwQCAQUAMIIBUQYLKoZIhvcNAQkQAQSgggFABIIBPDCCATgCAQEGCisGAQQBhFkK
# AwEwMTANBglghkgBZQMEAgEFAAQgWygQAW6TFHpZfZg+Klhql6qw4mQlElZUh07M
# ws9xLQ8CBmH67Bz7OxgTMjAyMjAyMTUwNzE2MzUuNDg0WjAEgAIB9KCB0KSBzTCB
# yjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMc
# TWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRT
# UyBFU046N0JGMS1FM0VBLUI4MDgxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0
# YW1wIFNlcnZpY2WgghFUMIIHDDCCBPSgAwIBAgITMwAAAZ8rRTUVCC5LXQABAAAB
# nzANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAe
# Fw0yMTEyMDIxOTA1MjJaFw0yMzAyMjgxOTA1MjJaMIHKMQswCQYDVQQGEwJVUzET
# MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV
# TWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmlj
# YSBPcGVyYXRpb25zMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo3QkYxLUUzRUEt
# QjgwODElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCCAiIw
# DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKT1eXxNUbKJkC/Oby0Hh8s/TOcv
# zzdgMgbTeOzX9bMJogJcOzSReUnf05RnB4EVr9XyXbuaUGPItkO1ODdbx1A5EO6d
# +ftLNkSgWaVdpJhxCHIMxXmCHGLqWHzLc1XVM0cZgvNqhCa0F64VKUQf3CnqsL+x
# ErsY+s6fXtcAbOj7/IXLsN9aAhDjdffm63bRNKFR5gOuzkY5Wkenui6pBhFOm76U
# BoId+ry2v4sWojKOmS/HFvcdzHpWO17Q08foacgJPzg/FZgrt6hrkDFuxNSpZDKJ
# a2sajJDJc/jIgp9NRg+2xMUKLXiK4k2vfJEaOjhTU4dlTbIaZZ4Kt1xwmCRvLqTY
# 3kCFFi8oet48+HmhYdjTWDxNyTFXiHiKWiq9ppgaHccM9Y/DgqgrITLtAca5krWo
# CSF5aIpfaoTR41Fa6aYIo+F1wXd1xWJUj1opeG3LjMzvq2xSNx0K2cblUgjp5Tp3
# NwvpgWnS8yXsk8jfL0ivH2wESJWZKKAzZMNlThFQhsUi0PrQMljM0fSsa7YO/f0/
# /Q7CjHfs/dl+8HmMB6DoH5IFIPRrCL5/rUkWtVz9Rnzdb7m2Aj/TFwsZYcE10SJt
# IXU0V+tXQo8Ip+L2IPYGRCAxiLTYJjwTe6z5TJgDg0VhxYmmNpwEoAF4MF2RjUE9
# 8aDOyRoqEgaF2jH1AgMBAAGjggE2MIIBMjAdBgNVHQ4EFgQUYjTy1R4TFitIDi7o
# 39lqx9YdyGEwHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYDVR0f
# BFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwv
# TWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwGCCsG
# AQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAx
# MCgxKS5jcnQwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAKBggrBgEFBQcDCDANBgkq
# hkiG9w0BAQsFAAOCAgEAHYooKTw76Rnz6b1s9dAgCaj7rFsoNoqQxHf/zYDxdUAx
# r1Gki1gmR2S1r4LpkhUGxkQBEmQqdalgmKLIYFXc+Y+ggw/nMVuvQFgsyiUMlky0
# fcyJ9UEP02Sdg0qD4ZtbJoA+zxVnpQPcJHOOhVnY9sdEf5Q6XZhz9ybUhHcGW+OV
# w3DKSnMEZSd0BF5+7ON9FJ8H50HOaUVj50wTz4nc6+94ytohzOdKuWvjoZcyhYYm
# 3SEEk1/gbklmrJd7yfzPbJHmmgva6IxHOohdfWvAIheFws8WBIo3+8nGvEeIX0HJ
# WKi5/iMJwPw7aY73i2gJKosRG6h1J711DuqspUGicOhhYDH5bRcYBfapqhmaoS6f
# tBvyGfI3JWsnYLZ9nABjbKJfdkyAsZSukNGglZ0/61zlJLopnV/DKEv8oCCOI0+9
# QGK7s8XgsfHlNEVTsdle+ClkOfnGS2RdmJ0DhLbo1mwxLKDHRHWddXfJtjcl2U19
# ERO3pIh9B0LFFflhRsjk12+5UyLLmgHduV+E+A0nKjSp2aQcoTak3hzyLD1KtqOd
# ZwzRtQTGsOQ2pzBqrXUPPBzSUMZfXiCeMZFuCGXocuwPuPHHT5u7Mkcpk/MZ1Msw
# UqhJ0l5XilT+3d09t1TbUdLrQTHYinZN0Z+C1L087NVpMDhS5y6SVuNmRCKF+DYw
# ggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZAAAAAAAVMA0GCSqGSIb3DQEBCwUA
# MIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQD
# EylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0y
# MTA5MzAxODIyMjVaFw0zMDA5MzAxODMyMjVaMHwxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5OGmTOe0
# ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1V/YBf2xK4OK9uT4XYDP/XE/HZveV
# U3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY6GB9alKDRLemjkZrBxTzxXb1hlDcwUTI
# cVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmvHaus9ja+NSZk2pg7uhp7M62AW36M
# EBydUv626GIl3GoPz130/o5Tz9bshVZN7928jaTjkY+yOSxRnOlwaQ3KNi1wjjHI
# NSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3tpK56KTesy+uDRedGbsoy1cCGMFxP
# LOJiss254o2I5JasAUq7vnGpF1tnYN74kpEeHT39IM9zfUGaRnXNxF803RKJ1v2l
# IH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2K26oElHovwUDo9Fzpk03dJQcNIIP8BDy
# t0cY7afomXw/TNuvXsLz1dhzPUNOwTM5TI4CvEJoLhDqhFFG4tG9ahhaYQFzymei
# XtcodgLiMxhy16cg8ML6EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5UPkLiWHzNgY1
# GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9QBXpsxREdcu+N+VLEhReTwDwV2xo3xwgV
# GD94q0W29R6HXtqPnhZyacaue7e3PmriLq0CAwEAAaOCAd0wggHZMBIGCSsGAQQB
# gjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYEFCqnUv5kxJq+gpE8RjUpzxD/LwTu
# MB0GA1UdDgQWBBSfpxVdAF5iXYP05dJlpxtTNRnpcjBcBgNVHSAEVTBTMFEGDCsG
# AQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5odG0wEwYDVR0lBAwwCgYIKwYBBQUH
# AwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud
# EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYD
# VR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwv
# cHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEB
# BE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9j
# ZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwDQYJKoZIhvcNAQELBQAD
# ggIBAJ1VffwqreEsH2cBMSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9MTO1OdfCcTY/
# 2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpTTd2YurYeeNg2LpypglYAA7AFvono
# aeC6Ce5732pvvinLbtg/SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OOPcbzaN9l9qRW
# qveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3+SmJw7wXsFSFQrP8DJ6LGYnn8Atq
# gcKBGUIZUnWKNsIdw2FzLixre24/LAl4FOmRsqlb30mjdAy87JGA0j3mSj5mO0+7
# hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSwethQ/gpY3UA8x1RtnWN0SCyxTkct
# wRQEcb9k+SS+c23Kjgm9swFXSVRk2XPXfx5bRAGOWhmRaw2fpCjcZxkoJLo4S5pu
# +yFUa2pFEUep8beuyOiJXk+d0tBMdrVXVAmxaQFEfnyhYWxz/gq77EFmPWn9y8FB
# SX5+k77L+DvktxW/tM4+pTFRhLy/AsGConsXHRWJjXD+57XQKBqJC4822rpM+Zv/
# Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328y+l7vzhwRNGQ
# 8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEGahC0HVUzWLOhcGbyoYICyzCCAjQCAQEw
# gfihgdCkgc0wgcoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# JTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsT
# HVRoYWxlcyBUU1MgRVNOOjdCRjEtRTNFQS1CODA4MSUwIwYDVQQDExxNaWNyb3Nv
# ZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQB0Xa6YH/LLDEUs
# VMLysn0W/1z2t6CBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw
# MA0GCSqGSIb3DQEBBQUAAgUA5bU8GDAiGA8yMDIyMDIxNTA0Mzc0NFoYDzIwMjIw
# MjE2MDQzNzQ0WjB0MDoGCisGAQQBhFkKBAExLDAqMAoCBQDltTwYAgEAMAcCAQAC
# AhOwMAcCAQACAhJQMAoCBQDlto2YAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisG
# AQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQEFBQAD
# gYEAVAUP0vig5TW5HxmvbI6ML4tpuzn6+l5aMRm5YCc6Qzx+v7+sK92Oqk5RmYa1
# jRe+Ptdl+ujF0pVNNOAS2/jbp7LutZ161nF93HBQa8S5deD+ZxfG9FdVvKMZUzMq
# g6CRObNiab+w4xfM9BSL5Ehztrx7K6qNCfyt3L0IhOqc++oxggQNMIIECQIBATCB
# kzB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAZ8rRTUVCC5LXQAB
# AAABnzANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJ
# EAEEMC8GCSqGSIb3DQEJBDEiBCBtLmed5kgs9OFzQytDl3R/2DCvGjQpo3Pi8sXn
# a96FkjCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIIbxXimiJ4mepedXPA1R
# 6N4qAsl8Qfs/6OynLDdLfFzaMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# UENBIDIwMTACEzMAAAGfK0U1FQguS10AAQAAAZ8wIgQgRFlWdrg7Cs3YoSLvolLr
# nZ3HHy9WhFbskfkmLzlZJeYwDQYJKoZIhvcNAQELBQAEggIASFqQt+ZCN6eW4Xmj
# 4rHBM+Yqc+sNBWT2epc26XCM4pifcqo7bnDV3dAh+fCbwakAjbiWEjRQH5kjjqWL
# f+xjhntFEPMNwKwi9yCtJKqFBqjP1POfhgJpY9khOj03mUFSxmabYQm3O2rbxhG8
# DeCb7lsxeS7rk2uQWt33X1YIrFT0754vfx4gbmaJbVAJG7E0Ydg09z27etfSc6fZ
# E35LTrHOiMIaBWyJzwUpBtS5gNn4LhdYz8OeKPoBi8sILVbDF01IIG5Y3Bun+1cH
# hNqi43rE9qB6pvHE8WawgUphY3gUS3v9tfBj+Clz6+/Vasozhwf5KQhokrNK8gYy
# hYgfH8/yTRrGDb6V4YWeQAJsGUqSWqWzwofbVu8K5e9uo3MHAqFEoxeugcud+yyQ
# pPba1S3dlUK+AhSj3r8YnEZxegi26UUyxqDaoFonkrcIBvdIsnGdEM8AeB7ZmIOv
# CA6D5SnvhM04lpMGTMEgP7/0YXOt5zGip6A9hRncGeWuhZEJYEwUh9T7v3x74DfL
# FUIhBCPWQT/18+wKKIuDf2FS+6xXA5tRfWF6ymT5XGRDHdDOxWf9nl0XDenz7dgP
# /bLrUZBbE2rDW7j1XvdMGCEF0ZAFQmt9kvH5IB/IxDpFiSK/niEeKUi1WRY4P4/t
# zEhDIe8xbTOiCyVVRg1k221YEbs=
# SIG # End signature block