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
# MIInzgYJKoZIhvcNAQcCoIInvzCCJ7sCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDzD6oK+67MzfTD
# mecs7qpAgpNgi/amWx6FvlgSa7rs3aCCDYUwggYDMIID66ADAgECAhMzAAACzfNk
# v/jUTF1RAAAAAALNMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjIwNTEyMjA0NjAyWhcNMjMwNTExMjA0NjAyWjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDrIzsY62MmKrzergm7Ucnu+DuSHdgzRZVCIGi9CalFrhwtiK+3FIDzlOYbs/zz
# HwuLC3hir55wVgHoaC4liQwQ60wVyR17EZPa4BQ28C5ARlxqftdp3H8RrXWbVyvQ
# aUnBQVZM73XDyGV1oUPZGHGWtgdqtBUd60VjnFPICSf8pnFiit6hvSxH5IVWI0iO
# nfqdXYoPWUtVUMmVqW1yBX0NtbQlSHIU6hlPvo9/uqKvkjFUFA2LbC9AWQbJmH+1
# uM0l4nDSKfCqccvdI5l3zjEk9yUSUmh1IQhDFn+5SL2JmnCF0jZEZ4f5HE7ykDP+
# oiA3Q+fhKCseg+0aEHi+DRPZAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU0WymH4CP7s1+yQktEwbcLQuR9Zww
# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh
# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ3MDUzMDAfBgNVHSMEGDAW
# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw
# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx
# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB
# AE7LSuuNObCBWYuttxJAgilXJ92GpyV/fTiyXHZ/9LbzXs/MfKnPwRydlmA2ak0r
# GWLDFh89zAWHFI8t9JLwpd/VRoVE3+WyzTIskdbBnHbf1yjo/+0tpHlnroFJdcDS
# MIsH+T7z3ClY+6WnjSTetpg1Y/pLOLXZpZjYeXQiFwo9G5lzUcSd8YVQNPQAGICl
# 2JRSaCNlzAdIFCF5PNKoXbJtEqDcPZ8oDrM9KdO7TqUE5VqeBe6DggY1sZYnQD+/
# LWlz5D0wCriNgGQ/TWWexMwwnEqlIwfkIcNFxo0QND/6Ya9DTAUykk2SKGSPt0kL
# tHxNEn2GJvcNtfohVY/b0tuyF05eXE3cdtYZbeGoU1xQixPZAlTdtLmeFNly82uB
# VbybAZ4Ut18F//UrugVQ9UUdK1uYmc+2SdRQQCccKwXGOuYgZ1ULW2u5PyfWxzo4
# BR++53OB/tZXQpz4OkgBZeqs9YaYLFfKRlQHVtmQghFHzB5v/WFonxDVlvPxy2go
# a0u9Z+ZlIpvooZRvm6OtXxdAjMBcWBAsnBRr/Oj5s356EDdf2l/sLwLFYE61t+ME
# iNYdy0pXL6gN3DxTVf2qjJxXFkFfjjTisndudHsguEMk8mEtnvwo9fOSKT6oRHhM
# 9sZ4HTg/TTMjUljmN3mBYWAWI5ExdC1inuog0xrKmOWVMIIHejCCBWKgAwIBAgIK
# YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm
# aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw
# OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD
# VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG
# 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la
# UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc
# 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D
# dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+
# lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk
# kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6
# A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd
# X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL
# 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd
# sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3
# T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS
# 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI
# bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL
# BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD
# uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv
# c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
# MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
# MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF
# BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h
# cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA
# YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn
# 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7
# v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b
# pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/
# KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy
# CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp
# mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi
# hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb
# BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS
# oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL
# gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX
# cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGZ8wghmbAgEBMIGVMH4x
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p
# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAALN82S/+NRMXVEAAAAA
# As0wDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw
# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIGtP
# Ckw1xNVSO9CFzOKRJLHuRlulsozKkLWbL7m3swgyMEIGCisGAQQBgjcCAQwxNDAy
# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20wDQYJKoZIhvcNAQEBBQAEggEAob90Iej6OaKFhfxqxbypsBAn598gRCZakPVX
# 6NJEYrB1vz5c8PqOLJLOtS2z7kxJwiyJ5Vz0GjX8rnJT7bRNloJw0983N2N7PVQI
# RSijtpUItjn0AeFkQR1sIlYnDTATdu2twNbO3kVSM3fB2SbOchsC49eeoDsab6hp
# AMVvtQqOIsTZVF2719wn52R/SkoqmYhIgoMK/rsL/rle6itA35VbONtLkxZwwxye
# eDskIenuCLJmyvt9lBE42hs3TQdb8G/6rEEzZ3085AZIoXYkoLeW2bAPCJ4Ww4eq
# G1U1WD8/igt2ZdvxcgdWPiOEZxuV/wi1YjmOk/Go8wURrwXPXaGCFykwghclBgor
# BgEEAYI3AwMBMYIXFTCCFxEGCSqGSIb3DQEHAqCCFwIwghb+AgEDMQ8wDQYJYIZI
# AWUDBAIBBQAwggFZBgsqhkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGE
# WQoDATAxMA0GCWCGSAFlAwQCAQUABCClIek2a4cfv+qNqczjAmMzMseWjj4eG22L
# YqmJdySmwQIGZBtBThWfGBMyMDIzMDQxMzEwNDcwMC40NzNaMASAAgH0oIHYpIHV
# MIHSMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQL
# EyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsT
# HVRoYWxlcyBUU1MgRVNOOkZDNDEtNEJENC1EMjIwMSUwIwYDVQQDExxNaWNyb3Nv
# ZnQgVGltZS1TdGFtcCBTZXJ2aWNloIIReDCCBycwggUPoAMCAQICEzMAAAG59gAN
# ZVRPvAMAAQAAAbkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# UENBIDIwMTAwHhcNMjIwOTIwMjAyMjE3WhcNMjMxMjE0MjAyMjE3WjCB0jELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9z
# b2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMg
# VFNTIEVTTjpGQzQxLTRCRDQtRDIyMDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt
# U3RhbXAgU2VydmljZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAONJ
# Pslh9RbHyQECbUIINxMF5uQkyN07VIShITXubLpWnANgBCLvCcJl7o/2HHORnsRc
# mSINJ/qclAmLIrOjnYnrbocAnixiMEXC+a1sZ84qxYWtEVY7VYw0LCczY+86U/8s
# hgxqsaezKpWriPOcpV1Sh8SsOxf30yO7jvld/IBA3T6lHM2pT/HRjWk/r9uyx0Q4
# atx0mkLVYS9y55/oTlKLE00h792S+maadAdy3VgTweiwoEOXD785wv3h+fwH/wTQ
# tC9lhAxhMO4p+OP9888Wxkbl6BqRWXud54RTzqp2Vr+yen1Q1A6umyMB7Xq0snIY
# G5B1Acc4UgJlPQ/ZiMkqgxQNFCWQvz0G9oLgSPD8Ky0AkX22PcDOboPuNT4RceWP
# X0UVZUsX9IUgs7QF41HiQSwEeOOHGyrfQdmSslATrbmH/18M5QrsTM5JINjct9G4
# 2xqN8VF9Z8WOiGMjNbvlpcEmmysYl5QyhrEDoFnQTU7bFrD3JX0fIfu1sbLWeBqX
# wbp4Z8yACTtphK2VbzOvi4vc0RCmRNzvYQQ2PjZ7NaTXE4Gu3vggAJ+rtzUTAfJo
# tvOSqcMgNwLZa1Y+ET/lb0VyjrYwFuHtg0QWyQjP5350LTpv086pyVUh4A3w/Os5
# hTGFZgFe5bCyMnpY09M0yPdHaQ/56oYUsSIcyKyVAgMBAAGjggFJMIIBRTAdBgNV
# HQ4EFgQUt7A4cdtYQ5oJjE1ZqrSonp41RFIwHwYDVR0jBBgwFoAUn6cVXQBeYl2D
# 9OXSZacbUzUZ6XIwXwYDVR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3Nv
# ZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUy
# MDIwMTAoMSkuY3JsMGwGCCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1l
# LVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUB
# Af8EDDAKBggrBgEFBQcDCDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQAD
# ggIBAM3cZ7NFUHRMsLKzjl7rJPIkv7oJ+s9kkut0hZif9WSt60SzYGULp1zmdPqc
# +w8eHTkhqX0GKCp2TTqSzBXBhwHOm8+p6hUxNlDewGMZUos952aTXblAT3OKBnfV
# BLQyUavrSjuJGZAW30cNY3rjVDUlGD+VygQHySaDaviJQbK6/6fQvUUFoqIk3ldG
# fjnAtnebsVlqh6WWamVc5AZdpWR1jSzN/oxKYqc1BG4SxxlPtcfrAdBz/cU4bxVX
# qAAf02NZscvJNpRnOALf5kVo2HupJXCsk9TzP5PNW2sTS3TmwhIQmPxr0E0UqOoj
# UrBJUOhbITAxcnSa/IMluL1HXRtLQZI+xs2eRtuPOUsKUW71/1YeqsYCLHLvu82c
# eDVQQvP7GHEEkp2kEjiofbjYErBo2iCEaxxeX4Z9HvAgA4MsQkbn6e4EFQf13sP+
# Kn3XgMIvJbqLJeFcQja+SUeOXu5cfkxe0GzTNojdyIwzaHlhOflVRZNrxee3B+yZ
# wd3JHDIvv71uSI/SIzzt9cU2GyHQVqxBSrRtKW6W8Vw7zpVvoVsIv3ljxg+7NiGS
# lXX1s7zbBNDMUj9OnzOlHK/3mrOU8YEuRf6RwakW5UCeGamy5MiKu2YuyKiGBCv4
# OGhPstNe7ALkEOh8BX12t4ntuYu+gw9L6yCPY0jWYaQtzAP9MIIHcTCCBVmgAwIB
# AgITMwAAABXF52ueAptJmQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UE
# BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc
# BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0
# IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1
# WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCC
# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O
# 1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZn
# hUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t
# 1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxq
# D89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmP
# frVUj9z6BVWYbWg7mka97aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSW
# rAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv
# 231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zb
# r17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYcten
# IPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQc
# xWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17a
# j54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQAB
# MCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQU
# n6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEw
# QTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9E
# b2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQB
# gjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/
# MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJ
# oEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01p
# Y1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYB
# BQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9v
# Q2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3h
# LB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x
# 5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74p
# y27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1A
# oL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbC
# HcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB
# 9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNt
# yo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3
# rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcV
# v7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A24
# 5oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lw
# Y1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGCAtQwggI9AgEBMIIBAKGB2KSB1TCB
# 0jELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMk
# TWljcm9zb2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1U
# aGFsZXMgVFNTIEVTTjpGQzQxLTRCRDQtRDIyMDElMCMGA1UEAxMcTWljcm9zb2Z0
# IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUAx2IeGHhk58MQkzzS
# WknGcLjfgTqggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAN
# BgkqhkiG9w0BAQUFAAIFAOfiFdwwIhgPMjAyMzA0MTMxMzQ3MDhaGA8yMDIzMDQx
# NDEzNDcwOFowdDA6BgorBgEEAYRZCgQBMSwwKjAKAgUA5+IV3AIBADAHAgEAAgIH
# lDAHAgEAAgIV8DAKAgUA5+NnXAIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEE
# AYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GB
# AFPWK/WsCzwdJjI5AXf0BeI6XvPI+D+Is9uHqPl/1R4eWtsrsLGTS9wozlIIA9aV
# uOO5h/4PHrniyis1DEa/33aEmh+sHKxfvGOYBMa7rEAqM4hWcpP4pQgxGqQ3Q+uW
# mp75GUoMfl1ctZjaD2yFrWIdGQ8ETwGqpXtIscXu9+DlMYIEDTCCBAkCAQEwgZMw
# fDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMd
# TWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAG59gANZVRPvAMAAQAA
# AbkwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRAB
# BDAvBgkqhkiG9w0BCQQxIgQgG30KDMzj7nySxZXVsHLBf/yTlA3V252MFQg68UgU
# Bo4wgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCBk60bO8W85uTAfJVEO3vX2
# aLaQFcgcGpdwsOoi+foP9DCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
# EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv
# ZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBD
# QSAyMDEwAhMzAAABufYADWVUT7wDAAEAAAG5MCIEIJfR1ND2MLyWLbQMJtzdI45o
# nD25Eiddv6LJHxqTvmloMA0GCSqGSIb3DQEBCwUABIICANs6o7Nm/WXDTRe+UnfQ
# QjfMzRIO0+Ey14G7Sshxn8V8DVH2ElW6gQu1JPc5IA4Gsk1NduAHGeY/whmuUVTu
# Wg+BTaDHu5Yy0+n3iO6LugA4f37ro5FjDEe0gwGGprRVUoymwX+Lz8nDq2FjdLZi
# 0LnLcPb/TVr4vztHxjj776JhJVK+i6Yzi4+24qaBCv9zZesfXaPoc/LckzX7xfy1
# CivSFd389WH7kKhmfOD6lZrUQqgKH2sd9J8Lh2cCk3jEkxPjZILaC1O9TJ9iul9P
# XVnmhxHN6F1XOkFyjdow9mLSPOF6mqdzSsUdShfPOQzLSUPHRLvfDe25uDC08bKK
# RJ/Yi9BMxCYC6+SI9+zV1gP9kVjmuQ12/HwbQukHT8rrBnuaeO7D/Jr/o2ij04Ek
# a4a7KIO/1jtw+EQd2NOJc6PnIYBvW84oDXAGR/r1jb+ICvomauSFsj5LpiY9TwQd
# coJtsPbL2j3xE3laPbQQc4he/oF6wYfhjiTqXFRXurVqOwynK0IUqle41lyOXCkb
# 8ZQM6CXeBoonKVgP3mzZ4+Y1CdNgYbl35AFXu+ie1rXt3ufQZImO4Z6fc8bboSmj
# DoDd/vzQkQO43Q8uAz0sWPXaDSszLZ7Y0t+aaJztW5LqgUjrIj+kinct298PGEbE
# 6F8AnZgA3EITV9MXLejci++A
# SIG # End signature block