Framework/Core/SVT/SVTControlAttestation.ps1

using namespace System.Management.Automation
Set-StrictMode -Version Latest 
class SVTControlAttestation 
{
    [SVTEventContext[]] $ControlResults = $null
    hidden [bool] $dirtyCommitState = $false;
    hidden [bool] $abortProcess = $false;
    hidden [ControlStateExtension] $controlStateExtension = $null;
    hidden [AttestControls] $AttestControlsChoice;
    hidden [bool] $bulkAttestMode = $false;
    [AttestationOptions] $attestOptions;
    hidden [PSObject] $ControlSettings ; 
    hidden [OrganizationContext] $OrganizationContext;
    hidden [InvocationInfo] $InvocationContext;
    hidden [Object] $repoProject = @{};
    hidden [AzSKSettings] $AzSKSettings;

    SVTControlAttestation([SVTEventContext[]] $ctrlResults, [AttestationOptions] $attestationOptions, [OrganizationContext] $organizationContext, [InvocationInfo] $invocationContext)
    {
        $this.OrganizationContext = $organizationContext;
        $this.InvocationContext = $invocationContext;
        $this.ControlResults = $ctrlResults;
        $this.AttestControlsChoice = $attestationOptions.AttestControls;
        $this.attestOptions = $attestationOptions;
        $this.controlStateExtension = [ControlStateExtension]::new($this.OrganizationContext, $this.InvocationContext)
        $this.controlStateExtension.UniqueRunId = $(Get-Date -format "yyyyMMdd_HHmmss");
        $this.controlStateExtension.Initialize($true)
        $this.ControlSettings=$ControlSettingsJson = [ConfigurationManager]::LoadServerConfigFile("ControlSettings.json");
        $this.repoProject.projectsWithRepo = @();
        $this.repoProject.projectsWithoutRepo = @();
        if (!$this.AzSKSettings) 
        {    
            $this.AzSKSettings = [ConfigurationManager]::GetAzSKSettings();                
        }
    }

    [AttestationStatus] GetAttestationValue([string] $AttestationCode)
    {
        switch($AttestationCode.ToUpper())
        {
            "1" { return [AttestationStatus]::NotAnIssue;}
            "2" { return [AttestationStatus]::WillNotFix;}
            "3" { return [AttestationStatus]::WillFixLater;}
            "4" { return [AttestationStatus]::ApprovedException;}
            "5" { return [AttestationStatus]::NotApplicable;}
            "6" { return [AttestationStatus]::StateConfirmed;}            
            "9" { 
                    $this.abortProcess = $true;
                    return [AttestationStatus]::None;
                }            
            Default { return [AttestationStatus]::None;}
        }
        return [AttestationStatus]::None
    }

    [ControlState] ComputeEffectiveControlState([ControlState] $controlState, [string] $ControlSeverity, [bool] $isOrganizationControl, [SVTEventContext] $controlItem, [ControlResult] $controlResult)
    {
        Write-Host "$([Constants]::SingleDashLine)" -ForegroundColor Cyan
        Write-Host "ControlId : $($controlState.ControlId)`nControlSeverity : $ControlSeverity`nDescription : $($controlItem.ControlItem.Description)`nCurrentControlStatus : $($controlState.ActualVerificationResult)`n"        
        if(-not $controlResult.CurrentSessionContext.Permissions.HasRequiredAccess)
        {
            Write-Host "Skipping attestation process for this control. You do not have required permissions to evaluate this control." -ForegroundColor Yellow
            return $controlState;
        }
        if(-not $this.isControlAttestable($controlItem, $controlResult))
        {
            Write-Host "This control cannot be attested by policy. Please follow the steps in 'Recommendation' for the control in order to fix the control and minimize exposure to attacks." -ForegroundColor Yellow
            return $controlState;
        }
        $userChoice = ""
        $isPrevAttested = $false;
        if($controlResult.AttestationStatus -ne [AttestationStatus]::None)
        {
            $isPrevAttested = $true;
        }
        $tempCurrentStateObject = $null;
        if($null -ne $controlResult.StateManagement -and $null -ne $controlResult.StateManagement.CurrentStateData)
        {
            $tempCurrentStateObject  = $controlResult.StateManagement.CurrentStateData;
        }

        #display the current state only if the state object is not empty
        if($null -ne $tempCurrentStateObject -and $null -ne $tempCurrentStateObject.DataObject)
        {
            #Current state object was converted to b64 in SetStateData. We need to decode it back to print it in plaintext in PS console.
            Write-Host "Configuration data to be attested:" -ForegroundColor Cyan
            $decodedDataObj = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($tempCurrentStateObject.DataObject))  | ConvertFrom-Json 
            Write-Host "$([JsonHelper]::ConvertToPson($decodedDataObj))"
        }

        if($isPrevAttested -and ($this.AttestControlsChoice -eq [AttestControls]::All -or $this.AttestControlsChoice -eq [AttestControls]::AlreadyAttested))
        {
            #Compute the effective attestation status for support backward compatibility
            $tempAttestationStatus = $controlState.AttestationStatus
            
            while($userChoice -ne '0' -and $userChoice -ne '1' -and $userChoice -ne '2' -and $userChoice -ne '9' )
            {
                Write-Host "Existing attestation details:" -ForegroundColor Cyan
                Write-Host "Attestation Status: $tempAttestationStatus`nVerificationResult: $($controlState.EffectiveVerificationResult)`nAttested By : $($controlState.State.AttestedBy)`nJustification : $($controlState.State.Justification)`n"
                Write-Host "Please select an action from below: `n[0]: Skip`n[1]: Attest`n[2]: Clear Attestation" -ForegroundColor Cyan
                $userChoice = Read-Host "User Choice"
                if(-not [string]::IsNullOrWhiteSpace($userChoice))
                {
                    $userChoice = $userChoice.Trim();
                }
            }
        }
        else
        {
            while($userChoice -ne '0' -and $userChoice -ne '1' -and $userChoice -ne '9' )
            {
                Write-Host "Please select an action from below: `n[0]: Skip`n[1]: Attest" -ForegroundColor Cyan
                $userChoice = Read-Host "User Choice"
                if(-not [string]::IsNullOrWhiteSpace($userChoice))
                {     
                    $userChoice = $userChoice.Trim();
                }
            }
        }
        $Justification=""
        $Attestationstate=""
        $message = ""
        [PSObject] $ValidAttestationStatesHashTable = $this.ComputeEligibleAttestationStates($controlItem, $controlResult);
        [String[]]$ValidAttestationKey = @(0)
        #Sort attestation status based on key value
        if($null -ne $ValidAttestationStatesHashTable)
        {
            $ValidAttestationStatesHashTable | ForEach-Object {
                $message += "`n[{0}]: {1}" -f $_.Value,$_.Name;
                $ValidAttestationKey += $_.Value
            }
        }
        switch ($userChoice.ToUpper()){
            "0" #None
            {                
                            
            }
            "1" #Attest
            {
                $attestationState = ""
                while($attestationState -notin [String[]]($ValidAttestationKey) -and $attestationState -ne '9' )
                {
                    Write-Host "`nPlease select an attestation status from below: `n[0]: Skip$message" -ForegroundColor Cyan
                    $attestationState = Read-Host "User Choice"
                    $attestationState = $attestationState.Trim();
                }
                $attestValue = $this.GetAttestationValue($attestationState);
                if($attestValue -ne [AttestationStatus]::None)
                {
                    $controlState.AttestationStatus = $attestValue;
                }
                elseif($this.abortProcess)
                {
                    return $null;
                }
                elseif($attestValue -eq [AttestationStatus]::None)
                {
                    return $controlState;
                }

                #In case when the user selects ApprovedException as the reason for attesting,
                #they'll be prompted to provide the number of days till that approval expires.
                $exceptionApprovalExpiryDate = ""
                if($controlState.AttestationStatus -eq [AttestationStatus]::ApprovedException)
                {
                    $expiryPeriod = $this.ControlSettings.DefaultAttestationPeriodForExemptControl
                    if([string]::IsNullOrWhiteSpace($this.attestOptions.ApprovedExceptionExpiryDate))
                    {
                        $exceptionApprovalExpiryDate =  ([DateTime]::UtcNow).AddDays($expiryPeriod)
                    }
                    else{
                        try
                        {
                            $maxAllowedExceptionApprovalExpiryDate = ([DateTime]::UtcNow).AddDays($expiryPeriod)                                
                            [datetime]$proposedExceptionApprovalExpiryDate = $this.attestOptions.ApprovedExceptionExpiryDate
                            if($proposedExceptionApprovalExpiryDate -le [DateTime]::UtcNow) 
                            {
                                Write-Host "ExpiryDate should be greater than current date. To attest control using 'ApprovedException' status use '-ApprovedExceptionExpiryDate' parameter to specify the expiry date. Please provide this param in the command with mm/dd/yy date format. For example: -ApprovedExceptionExpiryDate '11/25/20'" -ForegroundColor Yellow;
                                break;
                            }
                            elseif($proposedExceptionApprovalExpiryDate -gt $maxAllowedExceptionApprovalExpiryDate)
                            {
                                Write-Host "`nNote: The exception approval expiry will be set to $($expiryPeriod) days from today.`n" -ForegroundColor Yellow
                                $exceptionApprovalExpiryDate = $maxAllowedExceptionApprovalExpiryDate                                
                            }
                            else
                            {
                                $exceptionApprovalExpiryDate = $proposedExceptionApprovalExpiryDate
                            }
                        }
                        catch
                        {
                            Write-Host "`nThe date needs to be in mm/dd/yy format. For example: 11/25/20." -ForegroundColor Red
                            throw $_.Exception
                        }
                    }
                }
                
                if($controlState.AttestationStatus -ne [AttestationStatus]::None)
                {
                    $Justification = ""
                    while([string]::IsNullOrWhiteSpace($Justification))
                    {
                        $Justification = Read-Host "Justification"
                        try
                        {
                            $SanitizedJustification = [System.Text.UTF8Encoding]::ASCII.GetString([System.Text.UTF8Encoding]::ASCII.GetBytes($Justification));
                            $Justification = $SanitizedJustification;
                        }
                        catch
                        { 
                            # If the justification text is empty then prompting message again to provide justification text.
                        }
                        if([string]::IsNullOrWhiteSpace($Justification))
                        {
                            Write-Host "`nEmpty space or blank justification is not allowed."
                        }
                    }                    
                    $this.dirtyCommitState = $true
                }
                $controlState.EffectiveVerificationResult = [Helpers]::EvaluateVerificationResult($controlState.ActualVerificationResult,$controlState.AttestationStatus);
                
                $controlState.State = $tempCurrentStateObject

                if($null -eq $controlState.State)
                {
                    $controlState.State = [StateData]::new();
                }

                $controlState.State.AttestedBy = [ContextHelper]::GetCurrentSessionUser();
                $controlState.State.AttestedDate = [DateTime]::UtcNow;
                $controlState.State.Justification = $Justification    

                #In case of control exemption, calculating the exception approval(attestation) expiry date beforehand,
                #based on the days entered by the user (default 6 months)
                if($controlState.AttestationStatus -eq [AttestationStatus]::ApprovedException)
                {
                    $controlState.State.ApprovedExceptionID = $this.attestOptions.ApprovedExceptionID
                    $controlState.State.ExpiryDate = $exceptionApprovalExpiryDate.ToString("MM/dd/yyyy");
                }
                break;
            }
            "2" #Clear Attestation
            {
                $this.dirtyCommitState = $true
                #Clears the control state. This overrides the previous attested controlstate.
                $controlState.State = $null;
                $controlState.EffectiveVerificationResult = $controlState.ActualVerificationResult
                $controlState.AttestationStatus = [AttestationStatus]::None
            }
            "9" #Abort
            {
                $this.abortProcess = $true;
                return $null;
            }
            Default
            {

            }
        }

        return $controlState;
    }

    [ControlState] ComputeEffectiveControlStateInBulkMode([ControlState] $controlState, [string] $ControlSeverity, [bool] $isOrganizationControl, [SVTEventContext] $controlItem, [ControlResult] $controlResult)
    {
        Write-Host "$([Constants]::SingleDashLine)" -ForegroundColor Cyan        
        Write-Host "ControlId : $($controlState.ControlId)`nControlSeverity : $ControlSeverity`nDescription : $($controlItem.ControlItem.Description)`nCurrentControlStatus : $($controlState.ActualVerificationResult)`n"
        if(-not $controlResult.CurrentSessionContext.Permissions.HasRequiredAccess)
        {
            Write-Host "Skipping attestation process for this control. You do not have required permissions to evaluate this control. `nNote: If your permissions were elevated recently, please run the 'Disconnect-AzAccount' command to clear the Azure cache and try again." -ForegroundColor Yellow
            return $controlState;
        }
        $userChoice = ""
        if($null -ne $this.attestOptions -and $this.attestOptions.IsBulkClearModeOn)
        {
            if($controlState.AttestationStatus -ne [AttestationStatus]::None)
            {                
                $this.dirtyCommitState = $true
                #Compute the effective attestation status for support backward compatibility
                $tempAttestationStatus = $controlState.AttestationStatus
                
                Write-Host "Existing attestation details:" -ForegroundColor Cyan
                Write-Host "Attestation Status: $tempAttestationStatus`nVerificationResult: $($controlState.EffectiveVerificationResult)`nAttested By : $($controlState.State.AttestedBy)`nJustification : $($controlState.State.Justification)`n"
            }            
            #Clears the control state. This overrides the previous attested controlstate.
            $controlState.State = $null;
            $controlState.EffectiveVerificationResult = $controlState.ActualVerificationResult
            $controlState.AttestationStatus = [AttestationStatus]::None
            return $controlState;
        }
        $ValidAttestationStatesHashTable = $this.ComputeEligibleAttestationStates($controlItem, $controlResult);
        #Checking if control is attestable
        if($this.isControlAttestable($controlItem, $controlResult))
        {    # Checking if the attestation state provided in command parameter is valid for the control
            if( $this.attestOptions.AttestationStatus -in $ValidAttestationStatesHashTable.Name)
            {
            
                        $controlState.AttestationStatus = $this.attestOptions.AttestationStatus;
                        $controlState.EffectiveVerificationResult = [Helpers]::EvaluateVerificationResult($controlState.ActualVerificationResult,$controlState.AttestationStatus);

                        #In case when the user selects ApprovedException as the reason for attesting,
                        #they'll be prompted to provide the number of days till that approval expires.
                        $exceptionApprovalExpiryDate = ""
                        if($controlState.AttestationStatus -eq "ApprovedException")
                        {
                            $expiryPeriod = $this.ControlSettings.DefaultAttestationPeriodForExemptControl
                            if([string]::IsNullOrWhiteSpace($this.attestOptions.ApprovedExceptionExpiryDate))
                            {
                                $exceptionApprovalExpiryDate =  ([DateTime]::UtcNow).AddDays($expiryPeriod)
                            }
                            else{

                                try
                                {                        
                                    $maxAllowedExceptionApprovalExpiryDate = ([DateTime]::UtcNow).AddDays($expiryPeriod)                            
                                    [datetime]$proposedExceptionApprovalExpiryDate = $this.attestOptions.ApprovedExceptionExpiryDate
                                    #([DateTime]::UtcNow).AddDays($numberOfDays)

                                    if($proposedExceptionApprovalExpiryDate -le [DateTime]::UtcNow) 
                                    {
                                        Write-Host "ExpiryDate should be greater than current date. To attest control using 'ApprovedException' status use '-ApprovedExceptionExpiryDate' parameter to specify the expiry date. Please provide this param in the command with mm/dd/yy date format. For example: -ApprovedExceptionExpiryDate '11/25/20'" -ForegroundColor Yellow;
                                        break;
                                    }
                                    elseif($proposedExceptionApprovalExpiryDate -gt $maxAllowedExceptionApprovalExpiryDate)
                                    {
                                        Write-Host "`nNote: The exception approval expiry will be set to $($expiryPeriod) days from today.`n"  -ForegroundColor Yellow
                                        $exceptionApprovalExpiryDate = $maxAllowedExceptionApprovalExpiryDate                                
                                    }
                                    else
                                    {
                                        $exceptionApprovalExpiryDate = $proposedExceptionApprovalExpiryDate
                                    }
                                }
                                catch
                                {
                                    Write-Host "`nThe date needs to be in mm/dd/yy format. For example: 11/25/20." -ForegroundColor Red
                                    throw $_.Exception
                                }
                            }
                        }    
                        if($null -ne $controlResult.StateManagement -and $null -ne $controlResult.StateManagement.CurrentStateData)
                        {
                            $controlState.State = $controlResult.StateManagement.CurrentStateData;
                        }

                        if($null -eq $controlState.State)
                        {
                            $controlState.State = [StateData]::new();
                        }
                        $this.dirtyCommitState = $true
                        $controlState.State.AttestedBy = [ContextHelper]::GetCurrentSessionUser();
                        $controlState.State.AttestedDate = [DateTime]::UtcNow;
                        $controlState.State.Justification = $this.attestOptions.JustificationText    
                        
                        #In case of control exemption, calculating the exception approval(attestation) expiry date beforehand,
                        #based on the days entered by the user (default 6 months)
                        if($controlState.AttestationStatus -eq [AttestationStatus]::ApprovedException)
                        {
                            $controlState.State.ApprovedExceptionID = $this.attestOptions.ApprovedExceptionID
                            $controlState.State.ExpiryDate = $exceptionApprovalExpiryDate.ToString("MM/dd/yyyy");
                        }    
            }
            #if attestation state provided in command parameter is not valid for the control then print warning
            else
            {
                $outvalidSet=$ValidAttestationStatesHashTable.Name -join "," ;
                Write-Host "The chosen attestation state is not applicable to this control. Valid attestation choices are: $outvalidSet" -ForegroundColor Yellow;
                return $controlState ;
            }
        }
        #If control is not attestable then print warning
        else
        {
            Write-Host "This control cannot be attested by policy. Please follow the steps in 'Recommendation' for the control in order to fix the control and minimize exposure to attacks." -ForegroundColor Yellow;
        }
        return $controlState;
    }
    
    [void] StartControlAttestation()
    {
        #Set flag to to run rescan
        $Global:AttestationValue = $false
        try
        {
            #user provided justification text would be available only in bulk attestation mode.
            if($null -ne $this.attestOptions -and  (-not [string]::IsNullOrWhiteSpace($this.attestOptions.JustificationText) -or $this.attestOptions.IsBulkClearModeOn))
            {
                $this.bulkAttestMode = $true;                
                    Write-Host "$([Constants]::SingleDashLine)" -ForegroundColor Yellow                
            }
            else
            {
                Write-Host ("$([Constants]::SingleDashLine)`nNote: Enter 9 during any stage to exit the attestation workflow. This will abort attestation process for the current resource and remaining resources.`n$([Constants]::SingleDashLine)") -ForegroundColor Yellow
            }
            
            if($null -eq $this.ControlResults)
            {
                Write-Host "No control results found." -ForegroundColor Yellow
            }
            
            if ($this.attestOptions.AttestationStatus -eq "ApprovedException" -and  [string]::IsNullOrWhiteSpace($this.attestOptions.ApprovedExceptionID)) {
                Write-Host "Exception id is mandatory for approved exception." -ForegroundColor Cyan
                $exceptionId = Read-Host "Please enter the approved exception id"
                if ([string]::IsNullOrWhiteSpace($exceptionId)) {
                    Write-Host "Exception id is mandatory for approved exception." -ForegroundColor Red
                    break;
                }
                $this.attestOptions.ApprovedExceptionID = $exceptionId
            }
            $this.abortProcess = $false;
            #filtering the controls - Removing all the passed controls
            #Step1 Group By IDs

            #added below where condition to filter only for org and project. so only org and projec controll go into attestation
            
            $filteredControlResults = @()
            $allowedResourcesToAttest = @()

            if([Helpers]::CheckMember($this.ControlSettings,"AttestableResourceTypes") -and $null -ne $this.ControlSettings.AttestableResourceTypes)
            {
                $allowedResourcesToAttest = $this.ControlSettings.AttestableResourceTypes;
            }
            
            $filteredControlResults += ($this.ControlResults | Where {$_.FeatureName -in $allowedResourcesToAttest }) | Group-Object { $_.GetUniqueId() }  
            if((($filteredControlResults | Measure-Object).Count -eq 1 -and ($filteredControlResults[0].Group | Measure-Object).Count -gt 0 -and $null -ne $filteredControlResults[0].Group[0].ResourceContext) `
                -or ($filteredControlResults | Measure-Object).Count -gt 1)
            {
                Write-Host "No. of candidate resources for the attestation: $($filteredControlResults.Count)" -ForegroundColor Cyan
                if ($this.InvocationContext) 
                {
                    if ($this.InvocationContext.BoundParameters["AttestationHostProjectName"]) 
                    {
                        if($this.controlStateExtension.GetControlStatePermission("Organization", ""))
                        { 
                            $this.controlStateExtension.SetProjectInExtForOrg()    
                        }
                        else {
                            Write-Host "Error: Could not configure host project for organization controls attestation.`nThis may be because you may not have correct privilege (requires 'Project Collection Administrator')." -ForegroundColor Red
                        }
                    }
                }
            }
        
            #show warning if the keys count is greater than certain number.
            $counter = 0
            #start iterating resource after resource
            foreach($resource in  $filteredControlResults)
            {
                $isAttestationRepoPresent = $this.ValidateAttestationRepo($resource);
                if($isAttestationRepoPresent)
                {
                    $resourceValueKey = $resource.Name
                    $this.dirtyCommitState = $false;
                    $resourceValue = $resource.Group;        
                    $isOrganizationScan = $false;
                    $counter = $counter + 1
                    if(($resourceValue | Measure-Object).Count -gt 0)
                    {
                        $OrganizationName = $resourceValue[0].OrganizationContext.OrganizationName
                        if($null -ne $resourceValue[0].ResourceContext)
                        {
                            $ResourceId = $resourceValue[0].ResourceContext.ResourceId
                            Write-Host $([String]::Format([Constants]::ModuleAttestStartHeading, $resourceValue[0].FeatureName, $resourceValue[0].ResourceContext.ResourceGroupName, $resourceValue[0].ResourceContext.ResourceName, $counter, $filteredControlResults.Count)) -ForegroundColor Cyan
                        }
                        else
                        {
                            $isOrganizationScan = $true;
                            Write-Host $([String]::Format([Constants]::ModuleAttestStartHeadingSub, $resourceValue[0].FeatureName, $resourceValue[0].OrganizationContext.OrganizationName, $resourceValue[0].OrganizationContext.OrganizationId)) -ForegroundColor Cyan
                        }    
                        
                        if(($resourceValue[0].FeatureName -eq "Organization" -or $resourceValue[0].FeatureName -eq "Project") -and !$this.controlStateExtension.GetControlStatePermission($resourceValue[0].FeatureName, $resourceValue[0].ResourceContext.ResourceName) )
                        {
                        Write-Host "Error: Attestation denied.`nThis may be because you are attempting to attest controls for areas you do not have RBAC permission to." -ForegroundColor Red
                        continue
                        }
                        if($resourceValue[0].FeatureName -eq "Organization" -and !$this.controlStateExtension.GetProject())
                        { 
                            Write-Host "`nNo project defined to store attestation details for organization-specific controls." -ForegroundColor Red
                            Write-Host "Use the '-AttestationHostProjectName' parameter with this command to configure the project that will host attestation details for organization level controls.`nRun 'Get-Help -Name Get-AzSKADOSecurityStatus -Full' for more info." -ForegroundColor Yellow
                            continue
                        }
                        [ControlState[]] $resourceControlStates = @()
                        $count = 0;
                        [SVTEventContext[]] $filteredControlItems = @()
                        $resourceValue | ForEach-Object { 
                            $controlItem = $_;

                            $matchedControlItem = $false;
                            if(($controlItem.ControlResults | Measure-Object).Count -gt 0)
                            {
                                [ControlResult[]] $matchedControlResults = @();
                                $controlItem.ControlResults | ForEach-Object {
                                    $controlResult = $_
                                    if($controlResult.ActualVerificationResult -ne [VerificationResult]::Passed -and $controlResult.ActualVerificationResult -ne [VerificationResult]::Error)
                                    {
                                        if($this.AttestControlsChoice -eq [AttestControls]::All)
                                        {
                                            $matchedControlItem = $true;
                                            $matchedControlResults += $controlResult;
                                            $count++;
                                        }
                                        elseif($this.AttestControlsChoice -eq [AttestControls]::AlreadyAttested -and $controlResult.AttestationStatus -ne [AttestationStatus]::None)
                                        {
                                            $matchedControlItem = $true;
                                            $matchedControlResults += $controlResult;
                                            $count++;
                                        }
                                        elseif($this.AttestControlsChoice -eq [AttestControls]::NotAttested -and  $controlResult.AttestationStatus -eq [AttestationStatus]::None)
                                        {
                                            $matchedControlItem = $true;
                                            $matchedControlResults += $controlResult;
                                            $count++;
                                        }                                    
                                    }
                                }
                            }
                            if($matchedControlItem)
                            {
                                $controlItem.ControlResults = $matchedControlResults;
                                $filteredControlItems += $controlItem;
                            }
                        }
                        #Added below variable to supply in setcontrol to send in controlstateextension to verify resourcetype
                        $FeatureName = "";
                        $resourceName = "";
                        $resourceGroupName = "";
                        if($count -gt 0)
                        {
                            Write-Host "No. of controls that need to be attested: $count" -ForegroundColor Cyan

                            foreach( $controlItem in $filteredControlItems)
                            {
                                $FeatureName = $controlItem.FeatureName
                                $resourceName = $controlItem.ResourceContext.ResourceName
                                $resourceGroupName = $controlItem.ResourceContext.ResourceGroupName
                                $controlId = $controlItem.ControlItem.ControlID
                                $controlSeverity = $controlItem.ControlItem.ControlSeverity
                                $controlResult = $null;
                                $controlStatus = "";
                                $isPrevAttested = $false;
                                if(($controlItem.ControlResults | Measure-Object).Count -gt 0)
                                {                                
                                    foreach( $controlResult in $controlItem.ControlResults)
                                    {
                                        $controlStatus = $controlResult.ActualVerificationResult;
                                    
                                        [ControlState] $controlState = [ControlState]::new($controlId,$controlItem.ControlItem.Id,$controlResult.ChildResourceName,$controlStatus,"1.0");
                                        if($null -ne $controlResult.StateManagement -and $null -ne $controlResult.StateManagement.AttestedStateData)
                                        {                                
                                            $controlState.State = $controlResult.StateManagement.AttestedStateData
                                        }                        
                            
                                        $controlState.AttestationStatus = $controlResult.AttestationStatus
                                        $controlState.EffectiveVerificationResult = $controlResult.VerificationResult

                                        #ADOTodo: This seems to be unused...also, we should look into if 'tolower()' should be done in general for rsrcIds.
                                        $controlState.HashId = [ControlStateExtension]::ComputeHashX($resourceValueKey.ToLower());
                                        $controlState.ResourceId = $resourceValueKey;
                                        if($this.bulkAttestMode)
                                        {
                                            $controlState = $this.ComputeEffectiveControlStateInBulkMode($controlState, $controlSeverity, $isOrganizationScan, $controlItem, $controlResult)                                        
                                        }
                                        else
                                        {
                                            $controlState = $this.ComputeEffectiveControlState($controlState, $controlSeverity, $isOrganizationScan, $controlItem, $controlResult)                                        
                                        }
                                        
                                        $resourceControlStates +=$controlState;
                                        if($this.abortProcess)
                                        {
                                            Write-Host "Aborted the attestation workflow." -ForegroundColor Yellow
                                            return;
                                        }
                                    }
                                }
                                Write-Host $([Constants]::SingleDashLine) -ForegroundColor Cyan
                            }
                        }
                        else
                        {
                            Write-Host "No attestable controls found.`n$([Constants]::SingleDashLine)" -ForegroundColor Yellow
                        }
                        
                        #remove the entries which doesn't have any state
                        #$resourceControlStates = $resourceControlStates | Where-Object {$_.State}
                        #persist the value back to state
                        if($this.dirtyCommitState)
                        {
                            if(($resourceControlStates | Measure-Object).Count -gt 0)
                            {
                                #Set flag to to run rescan
                                $Global:AttestationValue = $true
                                Write-Host "Attestation summary for this resource:" -ForegroundColor Cyan
                                $output = @()
                                $resourceControlStates | ForEach-Object {
                                    $out = "" | Select-Object ControlId, EvaluatedResult, EffectiveResult, AttestationChoice
                                    $out.ControlId = $_.ControlId
                                    $out.EvaluatedResult = $_.ActualVerificationResult
                                    $out.EffectiveResult = $_.EffectiveVerificationResult
                                    $out.AttestationChoice = $_.AttestationStatus.ToString()
                                    $output += $out
                                }
                                Write-Host ($output | Format-Table ControlId, EvaluatedResult, EffectiveResult, AttestationChoice | Out-String) -ForegroundColor Cyan
                            }

                            Write-Host "Committing the attestation details for this resource..." -ForegroundColor Cyan
                            $this.controlStateExtension.SetControlState($resourceValueKey, $resourceControlStates, $false, $FeatureName, $resourceName, $resourceGroupName)
                            Write-Host "Commit succeeded." -ForegroundColor Cyan
                        }
                        
                        if($null -ne $resourceValue[0].ResourceContext)
                        {
                            $ResourceId = $resourceValue[0].ResourceContext.ResourceId
                            Write-Host $([String]::Format([Constants]::CompletedAttestAnalysis, $resourceValue[0].FeatureName, $resourceValue[0].ResourceContext.ResourceGroupName, $resourceValue[0].ResourceContext.ResourceName)) -ForegroundColor Cyan
                        }
                        else
                        {
                            $isOrganizationScan = $true;
                            Write-Host $([String]::Format([Constants]::CompletedAttestAnalysisSub, $resourceValue[0].FeatureName, $resourceValue[0].OrganizationContext.OrganizationName, $resourceValue[0].OrganizationContext.OrganizationId)) -ForegroundColor Cyan
                        }    
                    }
                }
                else 
                {
                    continue;
                }
            }
        }
        finally
        {
            $folderPath = Join-Path $([Constants]::AzSKAppFolderPath) "Temp" | Join-Path -ChildPath $($this.controlStateExtension.UniqueRunId)
            [Helpers]::CleanupLocalFolder($folderPath);
        }
    }

    [bool] ValidateAttestationRepo([Object] $resource)
    {
        if($resource.Group[0].ResourceContext.ResourceTypeName -eq 'Organization')
        {
            $projectName = $this.controlStateExtension.GetProject();
        }
        elseif($resource.Group[0].ResourceContext.ResourceTypeName -eq 'Project')
        {
            $projectName = $resource.Group[0].ResourceContext.ResourceName;
        }
        else
        {
            $projectName = $resource.Group[0].ResourceContext.ResourceGroupName;
        }
        #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")) {
            $projectName = $this.ControlSettings.ProjectToStoreAttestation;
        }
        
        if($projectName -in $this.repoProject.projectsWithRepo)
        {
            return $true;
        }
        elseif($projectName -in $this.repoProject.projectsWithoutRepo)
        {
            return $false;
        }
        elseif(-not [string]::IsNullOrEmpty($projectName))
        {
            $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)}
                if($null -ne $webRequest)
                {
                    $this.repoProject.projectsWithRepo += $projectName
                    return $true;
                }
                else
                {
                    Write-Host $([Constants]::SingleDashLine) -ForegroundColor Red
                    Write-Host "`nAttestation repository was not found in [$projectName] project" -ForegroundColor Red
                    Write-Host "See more at https://aka.ms/adoscanner/attestation `n" -ForegroundColor Yellow
                    Write-Host $([Constants]::SingleDashLine) -ForegroundColor Red
                    $this.repoProject.projectsWithoutRepo += $projectName
                    return $false;
                }
            }
            catch
            {
                Write-Host $([Constants]::SingleDashLine) -ForegroundColor Red
                Write-Host "`nAttestation repository was not found in [$projectName] project" -ForegroundColor Red
                Write-Host "See more at https://aka.ms/adoscanner/attestation `n" -ForegroundColor Yellow
                Write-Host $([Constants]::SingleDashLine) -ForegroundColor Red
                $this.repoProject.projectsWithoutRepo += $projectName
                return $false;
            }
        }
        elseif($this.controlStateExtension.PrintParamPolicyProjErr -eq $true ){
            Write-Host $([Constants]::SingleDashLine) -ForegroundColor Red
            Write-Host -ForegroundColor Red "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. `n`r(c) Run Set-AzSKPolicySettings -EnableOrgControlAttestation `$true"
            Write-Host $([Constants]::SingleDashLine) -ForegroundColor Red
            return $false;
        }
        else{
            return $false;
        }
    }

    [bool] isControlAttestable([SVTEventContext] $controlItem, [ControlResult] $controlResult)
    {
        # If None is found in array along with other attestation status, 'None' will get precedence.
        if(($controlItem.ControlItem.ValidAttestationStates | Measure-Object).Count -gt 0 -and ($controlItem.ControlItem.ValidAttestationStates | Where-Object { $_.Trim() -eq [AttestationStatus]::None } | Measure-Object).Count -gt 0)
        { 
            return $false
        }
        else
        {
            return $true
        }
    }

    [PSObject] ComputeEligibleAttestationStates([SVTEventContext] $controlItem, [ControlResult] $controlResult)
    {
        [System.Collections.ArrayList] $ValidAttestationStates = $null
        #Default attestation state
        if($null -ne $this.ControlSettings.DefaultValidAttestationStates){
            $ValidAttestationStates = $this.ControlSettings.DefaultValidAttestationStates | Select-Object -Unique
        }
        #Additional attestation state
        if($null -ne $controlItem.ControlItem.ValidAttestationStates)
        { 
            $ValidAttestationStates += $controlItem.ControlItem.ValidAttestationStates | Select-Object -Unique
        }
        $ValidAttestationStates = $ValidAttestationStates.Trim() | Select-Object -Unique
        #if control not in grace, disable WillFixLater option
        if(-not $controlResult.IsControlInGrace)
        {
            if(($ValidAttestationStates | Where-Object { $_ -eq [AttestationStatus]::WillFixLater} | Measure-Object).Count -gt 0)
            {
                $ValidAttestationStates.Remove("WillFixLater")
            }
        }
        $ValidAttestationStatesHashTable = [Constants]::AttestationStatusHashMap.GetEnumerator() | Where-Object { $_.Name -in $ValidAttestationStates } | Sort-Object value
        
        if($this.attestOptions.IsExemptModeOn)
        {
            $ValidAttestationStatesHashTable += [Constants]::AttestationStatusHashMap.GetEnumerator() | Where-Object { $_.Name -eq [AttestationStatus]::ApprovedException }
        }
        
        return $ValidAttestationStatesHashTable;
    }
}

# SIG # Begin signature block
# MIIjkQYJKoZIhvcNAQcCoIIjgjCCI34CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDNQVNyd8Nt8Yeq
# 4RWxQX6J0B+or9DhREjpfNUhGwFtxqCCDYEwggX/MIID56ADAgECAhMzAAAB32vw
# LpKnSrTQAAAAAAHfMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjAxMjE1MjEzMTQ1WhcNMjExMjAyMjEzMTQ1WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQC2uxlZEACjqfHkuFyoCwfL25ofI9DZWKt4wEj3JBQ48GPt1UsDv834CcoUUPMn
# s/6CtPoaQ4Thy/kbOOg/zJAnrJeiMQqRe2Lsdb/NSI2gXXX9lad1/yPUDOXo4GNw
# PjXq1JZi+HZV91bUr6ZjzePj1g+bepsqd/HC1XScj0fT3aAxLRykJSzExEBmU9eS
# yuOwUuq+CriudQtWGMdJU650v/KmzfM46Y6lo/MCnnpvz3zEL7PMdUdwqj/nYhGG
# 3UVILxX7tAdMbz7LN+6WOIpT1A41rwaoOVnv+8Ua94HwhjZmu1S73yeV7RZZNxoh
# EegJi9YYssXa7UZUUkCCA+KnAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUOPbML8IdkNGtCfMmVPtvI6VZ8+Mw
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDYzMDA5MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAnnqH
# tDyYUFaVAkvAK0eqq6nhoL95SZQu3RnpZ7tdQ89QR3++7A+4hrr7V4xxmkB5BObS
# 0YK+MALE02atjwWgPdpYQ68WdLGroJZHkbZdgERG+7tETFl3aKF4KpoSaGOskZXp
# TPnCaMo2PXoAMVMGpsQEQswimZq3IQ3nRQfBlJ0PoMMcN/+Pks8ZTL1BoPYsJpok
# t6cql59q6CypZYIwgyJ892HpttybHKg1ZtQLUlSXccRMlugPgEcNZJagPEgPYni4
# b11snjRAgf0dyQ0zI9aLXqTxWUU5pCIFiPT0b2wsxzRqCtyGqpkGM8P9GazO8eao
# mVItCYBcJSByBx/pS0cSYwBBHAZxJODUqxSXoSGDvmTfqUJXntnWkL4okok1FiCD
# Z4jpyXOQunb6egIXvkgQ7jb2uO26Ow0m8RwleDvhOMrnHsupiOPbozKroSa6paFt
# VSh89abUSooR8QdZciemmoFhcWkEwFg4spzvYNP4nIs193261WyTaRMZoceGun7G
# CT2Rl653uUj+F+g94c63AhzSq4khdL4HlFIP2ePv29smfUnHtGq6yYFDLnT0q/Y+
# Di3jwloF8EWkkHRtSuXlFUbTmwr/lDDgbpZiKhLS7CBTDj32I0L5i532+uHczw82
# oZDmYmYmIUSMbZOgS65h797rj5JJ6OkeEUJoAVwwggd6MIIFYqADAgECAgphDpDS
# 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/BvW1taslScxMNelDNMYIVZjCCFWICAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAd9r8C6Sp0q00AAAAAAB3zAN
# BglghkgBZQMEAgEFAKCBsDAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgfgoExZmP
# lTGcQGV2SfL9RR4A8EKiB4VaZeDfbO8AD0UwRAYKKwYBBAGCNwIBDDE2MDSgFIAS
# AE0AaQBjAHIAbwBzAG8AZgB0oRyAGmh0dHBzOi8vd3d3Lm1pY3Jvc29mdC5jb20g
# MA0GCSqGSIb3DQEBAQUABIIBAKVGi+j7YijgCUEpRrHit6bkLOGcXrLsPqx7z/OD
# jnE+rUuijhbU68ag6mC53kNWvUadSIe49LI3iZUizk2kCZ5YG1nnpfoU+diPMDk+
# fjsqlR/NQNkyWsO1o0cI/TlMtdzZNdjm+WpwJsYiFujiRfcj1WROkXiQLIB2YwgM
# buZKKvRUVuJh2iC+1BvANkcr5O2RYq4C0ctk0ESSzhh75X1WYu1vNzQOjS221hLy
# kxdLr1qa1zqSm5d9pBu/hDp8PbezOnd+m+CvKmcXlSD94gZV3Wk2BCK4TQT7L0gW
# jPsAymPdktnbGzXs0qER5q8Up2W9VedRSnqOA1GdFExBus+hghLuMIIS6gYKKwYB
# BAGCNwMDATGCEtowghLWBgkqhkiG9w0BBwKgghLHMIISwwIBAzEPMA0GCWCGSAFl
# AwQCAQUAMIIBVQYLKoZIhvcNAQkQAQSgggFEBIIBQDCCATwCAQEGCisGAQQBhFkK
# AwEwMTANBglghkgBZQMEAgEFAAQg/L2bCrNkXPsgL3Jmi/+JnieW01IytsX0NaNG
# Dnom0aUCBmA85p4XTxgTMjAyMTAzMTUxMDU4NDIuODkxWjAEgAIB9KCB1KSB0TCB
# zjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMg
# TWljcm9zb2Z0IE9wZXJhdGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxl
# cyBUU1MgRVNOOjc4ODAtRTM5MC04MDE0MSUwIwYDVQQDExxNaWNyb3NvZnQgVGlt
# ZS1TdGFtcCBTZXJ2aWNloIIOQTCCBPUwggPdoAMCAQICEzMAAAFchtLj7Dn2izgA
# AAAAAVwwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh
# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD
# b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw
# MTAwHhcNMjEwMTE0MTkwMjE3WhcNMjIwNDExMTkwMjE3WjCBzjELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWljcm9zb2Z0IE9w
# ZXJhdGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjc4
# ODAtRTM5MC04MDE0MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2
# aWNlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0CorPq8qH2JAsmgS
# 8lwJTB3l+dq3BBeYhkyUnzi/iewy5+d8lsbrbd/9Tw4G7WzI5c5ntXMc54L/6shm
# vNwlBpDyvmUJCOf1+IbeOT6mo9IVGXfD1gYWOi7L8XG5IDqz8y/tvQZLRtodOUkW
# BG4MoGAGxNqAZHhJGYecV2tKFPe2TVPdYBItMYhJ4YbHiLQPIO7PzNBWamkvz4FT
# KI+KvRb9dk6y4DoUTGPeBO/JMt+INWGY1zDM+/ktCWshWKvSbb7tQNNjyKfMeX/Y
# KUfg3ja6ptaT0fYjiukIJxRZIHDWbwN7iFOxMZARPuqJH4V8js9CUlD715/sA0B+
# U9I2GwIDAQABo4IBGzCCARcwHQYDVR0OBBYEFF/zFKw5KHKAkAV/uJp7LWMYwbo+
# MB8GA1UdIwQYMBaAFNVjOlyKMZDzQ3t8RhvFM2hahW1VMFYGA1UdHwRPME0wS6BJ
# oEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01p
# Y1RpbVN0YVBDQV8yMDEwLTA3LTAxLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYB
# BQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljVGlt
# U3RhUENBXzIwMTAtMDctMDEuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYI
# KwYBBQUHAwgwDQYJKoZIhvcNAQELBQADggEBAHh5TPbXfiBzDhwj9TLZ7aOQ7u16
# krtPlZe3vpr8DP+l00I3oHUPpBhFEcv3QmYaVkx1S3Ab8DoT1Go2oO/1odDz/YUs
# Vyus05OANDRyNn/0zHyy2jXuTitbbZC9Ng5AEHXii40CwOWhn1qpz9C2aLwkUd3o
# xzu8TmgOB5UabfLx6vtSAufiCRMhifyV5M9j0fbK6gt9dtDxeuXRZYUFuZmbq3cM
# Qb6vqtoiY0ns+sFAeel1fEKOMXlY08xg14oRYD5GTIDkUPlgDS4pe2U13keC/Bxa
# j8AIbK4+W7HBgFwMJlAUVq2i/S42M6xDEQxGADOkDm+oQ47H9NQRgWRxEEkwggZx
# MIIEWaADAgECAgphCYEqAAAAAAACMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQg
# Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0xMDA3MDEyMTM2NTVa
# Fw0yNTA3MDEyMTQ2NTVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n
# dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9y
# YXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIIB
# IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqR0NvHcRijog7PwTl/X6f2mU
# a3RUENWlCgCChfvtfGhLLF/Fw+Vhwna3PmYrW/AVUycEMR9BGxqVHc4JE458YTBZ
# sTBED/FgiIRUQwzXTbg4CLNC3ZOs1nMwVyaCo0UN0Or1R4HNvyRgMlhgRvJYR4Yy
# hB50YWeRX4FUsc+TTJLBxKZd0WETbijGGvmGgLvfYfxGwScdJGcSchohiq9LZIlQ
# YrFd/XcfPfBXday9ikJNQFHRD5wGPmd/9WbAA5ZEfu/QS/1u5ZrKsajyeioKMfDa
# TgaRtogINeh4HLDpmc085y9Euqf03GS9pAHBIAmTeM38vMDJRF1eFpwBBU8iTQID
# AQABo4IB5jCCAeIwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFNVjOlyKMZDz
# Q3t8RhvFM2hahW1VMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQE
# AwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQ
# W9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNv
# bS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBa
# BggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0
# LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MIGgBgNV
# HSABAf8EgZUwgZIwgY8GCSsGAQQBgjcuAzCBgTA9BggrBgEFBQcCARYxaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL1BLSS9kb2NzL0NQUy9kZWZhdWx0Lmh0bTBABggr
# BgEFBQcCAjA0HjIgHQBMAGUAZwBhAGwAXwBQAG8AbABpAGMAeQBfAFMAdABhAHQA
# ZQBtAGUAbgB0AC4gHTANBgkqhkiG9w0BAQsFAAOCAgEAB+aIUQ3ixuCYP4FxAz2d
# o6Ehb7Prpsz1Mb7PBeKp/vpXbRkws8LFZslq3/Xn8Hi9x6ieJeP5vO1rVFcIK1GC
# RBL7uVOMzPRgEop2zEBAQZvcXBf/XPleFzWYJFZLdO9CEMivv3/Gf/I3fVo/HPKZ
# eUqRUgCvOA8X9S95gWXZqbVr5MfO9sp6AG9LMEQkIjzP7QOllo9ZKby2/QThcJ8y
# Sif9Va8v/rbljjO7Yl+a21dA6fHOmWaQjP9qYn/dxUoLkSbiOewZSnFjnXshbcOc
# o6I8+n99lmqQeKZt0uGc+R38ONiU9MalCpaGpL2eGq4EQoO4tYCbIjggtSXlZOz3
# 9L9+Y1klD3ouOVd2onGqBooPiRa6YacRy5rYDkeagMXQzafQ732D8OE7cQnfXXSY
# Ighh2rBQHm+98eEA3+cxB6STOvdlR3jo+KhIq/fecn5ha293qYHLpwmsObvsxsvY
# grRyzR30uIUBHoD7G4kqVDmyW9rIDVWZeodzOwjmmC3qjeAzLhIp9cAvVCch98is
# TtoouLGp25ayp0Kiyc8ZQU3ghvkqmqMRZjDTu3QyS99je/WZii8bxyGvWbWu3EQ8
# l1Bx16HSxVXjad5XwdHeMMD9zOZN+w2/XU/pnR4ZOC+8z1gFLu8NoFA12u8JJxzV
# s341Hgi62jbb01+P3nSISRKhggLPMIICOAIBATCB/KGB1KSB0TCBzjELMAkGA1UE
# BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc
# BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWljcm9zb2Z0
# IE9wZXJhdGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNO
# Ojc4ODAtRTM5MC04MDE0MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT
# ZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQCe4qWjxp8oR5Wcfl3rI/ieTmnwTKCBgzCB
# gKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV
# BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBBQUA
# AgUA4/kxJTAiGA8yMDIxMDMxNTA1MDQzN1oYDzIwMjEwMzE2MDUwNDM3WjB0MDoG
# CisGAQQBhFkKBAExLDAqMAoCBQDj+TElAgEAMAcCAQACAhgJMAcCAQACAhGQMAoC
# BQDj+oKlAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEA
# AgMHoSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQEFBQADgYEAvXCgK7J1i8f6XHca
# oYC1J2gvIoMsm8aXW/Ny049RzdBPYk9xXmzOvslHknP8Jvy+t9LEg+L7IPgE7+dm
# UjCixl8LAWA5QZxpBUnPBNsIGLlzKLjSVUbUT2DNyzrN+AP7602ewI+Yc535PJQ2
# OXmjBl6R6MFb+3Nw6NrJW/wWHmAxggMNMIIDCQIBATCBkzB8MQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGlt
# ZS1TdGFtcCBQQ0EgMjAxMAITMwAAAVyG0uPsOfaLOAAAAAABXDANBglghkgBZQME
# AgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJ
# BDEiBCDCEUgjy97zncrK3mVjxAxfaxasGUHbzAKF4yUnsiPhQDCB+gYLKoZIhvcN
# AQkQAi8xgeowgecwgeQwgb0EIE8tZFfCIE9sADBJzKQgK1A99C4giEZvFe+0XI8M
# Gea1MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAFc
# htLj7Dn2izgAAAAAAVwwIgQgRAqfbILLm3r3lx9RKDsL5wo+pXpqtWdqVdMNPAHU
# CjgwDQYJKoZIhvcNAQELBQAEggEAA9Psrh8FAWYKLUszEbyHGhY1CGUrx9H341mr
# mRCZo2KW7/TaQZaNH8rT/QvTE/ttu4+4+pklRffBJz9vT8Hm3OOVtQVct916lDNa
# toWuk5mHUy4pdL1LzBINRJ7tSeMgcAGn2+5Z8zoheieuF7HhgtguO9gtD11T15Pj
# YgpRDNbhOuPSCZ1JOohFHWCzFdVuGobqljLcYvrcaJdm/Pq20ql2fkG4IwFDsdsS
# P6QTiZPM9XLAJalmCiPyW4+MbMLRnWbf2K7bpZoMdiVP3TlXPYXSrm7rII3zQJd+
# lwawS3xYjCQRv8I4msBOZ4JMalzGMnPzyEcFzlAxAzgTFMUUIQ==
# SIG # End signature block