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
# MIInuQYJKoZIhvcNAQcCoIInqjCCJ6YCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# 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/BvW1taslScxMNelDNMYIZjjCCGYoCAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAlKLM6r4lfM52wAAAAACUjAN
# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQga08KTDXE
# 1VI70IXM4pEkse5GW6WyjMqQtZsvubezCDIwQgYKKwYBBAGCNwIBDDE0MDKgFIAS
# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN
# BgkqhkiG9w0BAQEFAASCAQBKtgAPZSQ0VvOEfARMIbja58wWTJk1W4ZKyzMYY6Xc
# SrZ/EvTqpYTAAAo35xRvJL18B1MWoeaV+3x3Ugz6UPeCcg7Gg1UzrkkC52+TCCGr
# 4IQSfMp/fhld4LCzaFfV4fjGC9DvDBatLmxoHc9qW23lc58YDmpXB0GR7EAFI+W/
# TpHi47aA/5y3lwcEZ2z4OaOju8/t6LSiOiK6KcjlmJ1TRhnJ+eoInNqY8oVaIwkW
# 8G6PZUIVTkQBGARWz7ZlkYk0IthjXfOPbrLDb2j1BJiwpTrmbwJlQvJo58cUVseC
# s4tb14n42FMJjc8DNk0BKnnbWHkhimVxAHOwmruTk8FmoYIXGDCCFxQGCisGAQQB
# gjcDAwExghcEMIIXAAYJKoZIhvcNAQcCoIIW8TCCFu0CAQMxDzANBglghkgBZQME
# AgEFADCCAVkGCyqGSIb3DQEJEAEEoIIBSASCAUQwggFAAgEBBgorBgEEAYRZCgMB
# MDEwDQYJYIZIAWUDBAIBBQAEILhg9uDNleCgK36eNhaCMhnXrvOg4rjojVDAHYjF
# SScMAgZiF5bsK/YYEzIwMjIwMzE1MDgzNTE0LjY2OVowBIACAfSggdikgdUwgdIx
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1p
# Y3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEmMCQGA1UECxMdVGhh
# bGVzIFRTUyBFU046RDA4Mi00QkZELUVFQkExJTAjBgNVBAMTHE1pY3Jvc29mdCBU
# aW1lLVN0YW1wIFNlcnZpY2WgghFnMIIHFDCCBPygAwIBAgITMwAAAY/zUajrWnLd
# zAABAAABjzANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0Eg
# MjAxMDAeFw0yMTEwMjgxOTI3NDZaFw0yMzAxMjYxOTI3NDZaMIHSMQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQg
# SXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1Mg
# RVNOOkQwODItNEJGRC1FRUJBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt
# cCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAmVc+/rXP
# Fx6Fk4+CpLrubDrLTa3QuAHRVXuy+zsxXwkogkT0a+XWuBabwHyqj8RRiZQQvdvb
# Oq5NRExOeHiaCtkUsQ02ESAe9Cz+loBNtsfCq846u3otWHCJlqkvDrSr7mMBqwcR
# Y7cfhAGfLvlpMSojoAnk7Rej+jcJnYxIeN34F3h9JwANY360oGYCIS7pLOosWV+b
# xug9uiTZYE/XclyYNF6XdzZ/zD/4U5pxT4MZQmzBGvDs+8cDdA/stZfj/ry+i0XU
# YNFPhuqc+UKkwm/XNHB+CDsGQl+ZS0GcbUUun4VPThHJm6mRAwL5y8zptWEIocbT
# eRSTmZnUa2iYH2EOBV7eCjx0Sdb6kLc1xdFRckDeQGR4J1yFyybuZsUP8x0dOsEE
# oLQuOhuKlDLQEg7D6ZxmZJnS8B03ewk/SpVLqsb66U2qyF4BwDt1uZkjEZ7finIo
# UgSz4B7fWLYIeO2OCYxIE0XvwsVop9PvTXTZtGPzzmHU753GarKyuM6oa/qaTzYv
# rAfUb7KYhvVQKxGUPkL9+eKiM7G0qenJCFrXzZPwRWoccAR33PhNEuuzzKZFJ4De
# aTCLg/8uK0Q4QjFRef5n4H+2KQIEibZ7zIeBX3jgsrICbzzSm0QX3SRVmZH//Aqp
# 8YxkwcoI1WCBizv84z9eqwRBdQ4HYcNbQMMCAwEAAaOCATYwggEyMB0GA1UdDgQW
# BBTzBuZ0a65JzuKhzoWb25f7NyNxvDAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJl
# pxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAx
# MCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3Rh
# bXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoG
# CCsGAQUFBwMIMA0GCSqGSIb3DQEBCwUAA4ICAQDNf9Oo9zyhC5n1jC8iU7NJY39F
# izjhxZwJbJY/Ytwn63plMlTSaBperan566fuRojGJSv3EwZs+RruOU2T/ZRDx4VH
# esLHtclE8GmMM1qTMaZPL8I2FrRmf5Oop4GqcxNdNECBClVZmn0KzFdPMqRa5/0R
# 6CmgqJh0muvImikgHubvohsavPEyyHQa94HD4/LNKd/YIaCKKPz9SA5fAa4phQ4E
# vz2auY9SUluId5MK9H5cjWVwBxCvYAD+1CW9z7GshJlNjqBvWtKO6J0Aemfg6z28
# g7qc7G/tCtrlH4/y27y+stuwWXNvwdsSd1lvB4M63AuMl9Yp6au/XFknGzJPF6n/
# uWR6JhQvzh40ILgeThLmYhf8z+aDb4r2OBLG1P2B6aCTW2YQkt7TpUnzI0cKGr21
# 3CbKtGk/OOIHSsDOxasmeGJ+FiUJCiV15wh3aZT/VT/PkL9E4hDBAwGt49G88gSC
# O0x9jfdDZWdWGbELXlSmA3EP4eTYq7RrolY04G8fGtF0pzuZu43A29zaI9lIr5ul
# KRz8EoQHU6cu0PxUw0B9H8cAkvQxaMumRZ/4fCbqNb4TcPkPcWOI24QYlvpbtT9p
# 31flYElmc5wjGplAky/nkJcT0HZENXenxWtPvt4gcoqppeJPA3S/1D57KL3667ep
# Ir0yV290E2otZbAW8DCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUw
# DQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n
# dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9y
# YXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhv
# cml0eSAyMDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9z
# b2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
# ggIKAoICAQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg
# 4r25PhdgM/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aO
# RmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41
# JmTamDu6GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5
# LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL
# 64NF50ZuyjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9
# QZpGdc3EXzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj
# 0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqE
# UUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0
# kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435
# UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB
# 3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTE
# mr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwG
# A1UdIARVMFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93
# d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNV
# HSUEDDAKBggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNV
# HQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo
# 0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29m
# dC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5j
# cmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jv
# c29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDAN
# BgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4
# sQaTlz0xM7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th54
# 2DYunKmCVgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRX
# ud2f8449xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBew
# VIVCs/wMnosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0
# DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+Cljd
# QDzHVG2dY3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFr
# DZ+kKNxnGSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFh
# bHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7n
# tdAoGokLjzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+
# oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6Fw
# ZvKhggLWMIICPwIBATCCAQChgdikgdUwgdIxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
# EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv
# ZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh
# dGlvbnMgTGltaXRlZDEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046RDA4Mi00QkZE
# LUVFQkExJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoB
# ATAHBgUrDgMCGgMVAD5NL4IEdudIBwdGoCaV0WBbQZpqoIGDMIGApH4wfDELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9z
# b2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwDQYJKoZIhvcNAQEFBQACBQDl2neSMCIY
# DzIwMjIwMzE1MTAyNTIyWhgPMjAyMjAzMTYxMDI1MjJaMHYwPAYKKwYBBAGEWQoE
# ATEuMCwwCgIFAOXad5ICAQAwCQIBAAIBfgIB/zAHAgEAAgIRzjAKAgUA5dvJEgIB
# ADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQow
# CAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBADq6rPY9R6KHLAaLnISK5dOjzTTS
# fpKNXKyZnXP0wNtI8RvQA6ZqCTuypp4IlxKDHJqfBgvoKr+Y+gsWKXTXojzdOvTc
# U/Fs1KQGK7rUv0vEgo26Sn5lRKzIQsQxeZ0X/gSRsXrlVb6k5M/UJBf3Vg7iM7a3
# KtMPbFfbc10pp29LMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# UENBIDIwMTACEzMAAAGP81Go61py3cwAAQAAAY8wDQYJYIZIAWUDBAIBBQCgggFK
# MBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQg7tv2
# m7SAcR9ejizNsBZ1v4JiDwte6wvbMhyn847mzkEwgfoGCyqGSIb3DQEJEAIvMYHq
# MIHnMIHkMIG9BCCXcgVP4sbGC5WOIqbbYi2Y7p0UNZbydKG7o7qDzIXHHzCBmDCB
# gKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV
# BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABj/NRqOtact3M
# AAEAAAGPMCIEIDwy/wx7tl4Ah54BpOzdqeOGBjQK1N9wnUzIhspYXekcMA0GCSqG
# SIb3DQEBCwUABIICAH6d/YSalFE1QRJpFsYKb7QwyOwAJUTnJ9Rq3rKt414+DmXL
# yW0NQ0EE1KR+JED7ojnRsnmX4u2qyWkdo3VQ6a62S2B8bZyR8aahpV3hNLZmWlbk
# cI7eVM1W+VrEwPO1aOyT9UDg2qocigccHriFVvbQJ+I9Q+Z5L6/4O7ttb0s11KAo
# Km/h9mBzz58sU3z9WU0g/4niIhRRBkHsZ28Cpz9bliVRLwwqr9kfUT2G5xyjLyj+
# ntM3yYpas5c3PyMY0lWwLINccWi0MlqZoYc+mvuVpF5xlKxEw2Xy+mB9DED0hwJZ
# Vh9vrP3+c+82OVR/QAPAY6NR6cE/WMxvWqNgyCTRXE3CqkyPeILtS9qC+DgCKPZm
# Ik5ELSbs9O/Ug47dVVRIu4YrkBXZKzeZue8pUeTtUcIC2wLDN+X+QtsU4O4/shuz
# D22WIAUUjliB1g/KWD7DlbIguUJ1ZFRlsf+v45OJnyQj6ICzLN+vWFN+LWZxMVAm
# dbpZXYwXTqg3/saB0DdHnh/IW8ghA0EhlctX/GbNna2vdjp0RbR/3bi45Vwv6t0G
# CJPw65vdLjtnKoIla8YxoxtMFP5xHKKvW+gVwcZ102BH8y8kYBmV+X1H2VPyK+d3
# bIGVm1TXzs0oahyuIY6O5+bqM+x28n0zyhUbVrVnkNYSMJc/9AVaqC3Cqr5+
# SIG # End signature block