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;
    hidden [bool] $isApprovedExceptionEnforced = $false
    hidden [PSObject] $approvedExceptionControlsList = @();

    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();
        }
        if ([Helpers]::CheckMember($this.ControlSettings, "EnforceApprovedException") -and ($this.ControlSettings.EnforceApprovedException -eq $true)) {
            if ([Helpers]::CheckMember($this.ControlSettings, "ApprovedExceptionSettings") -and (($this.ControlSettings.ApprovedExceptionSettings.ControlsList | Measure-Object).Count -gt 0)) {
                $this.isApprovedExceptionEnforced = $true
                $this.approvedExceptionControlsList = $this.ControlSettings.ApprovedExceptionSettings.ControlsList
            }
        }
    }

    [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;
                }

                <#
                If any enforce approved exception is enabled and control is part of approved exception enabled controls, end user needs to provide exception id and expiry date (default expiry date will be allocated incase user dont enter any expiry date)
                #>

                $exceptionApprovalExpiryDate = ""
                if (($controlState.AttestationStatus -eq [AttestationStatus]::ApprovedException) -or ( $this.isApprovedExceptionEnforced -and $this.approvedExceptionControlsList -contains $controlState.ControlId)) {
                    $exceptionId = ""
                    $approvedExceptionExpiryDate = ""
                    # If enforce approved exception is enabled, prompt the user with respective message configured in org policy to fetch the exception id
                    if ($this.isApprovedExceptionEnforced) {
                        $approvedExceptionPromptMessage = ""
                        if ([Helpers]::CheckMember($this.ControlSettings, "ApprovedExceptionSettings")) {
                            if ($controlState.AttestationStatus -eq [AttestationStatus]::ApprovedException) {
                                if (-not [string]::IsNullOrWhiteSpace($this.ControlSettings.ApprovedExceptionSettings.ApprovedExceptionPromptMessage)) {
                                    $approvedExceptionPromptMessage = $this.ControlSettings.ApprovedExceptionSettings.ApprovedExceptionPromptMessage
                                }
                            }
                            else {
                                if (-not [string]::IsNullOrWhiteSpace($this.ControlSettings.ApprovedExceptionSettings.NonApprovedExceptionPromptMessage)) {
                                    $approvedExceptionPromptMessage = $this.ControlSettings.ApprovedExceptionSettings.NonApprovedExceptionPromptMessage
                                }
                            }
                            if([string]::IsNullOrWhiteSpace($approvedExceptionPromptMessage)) {
                                $approvedExceptionPromptMessage = $this.ControlSettings.ApprovedExceptionSettings.DefaultPromptMessage
                            }
                            Write-Host $approvedExceptionPromptMessage -ForegroundColor Cyan
                        }
                    }
                    while ([string]::IsNullOrWhiteSpace($exceptionId)) {
                        $exceptionId = Read-Host "Please enter the approved exception id"
                        if ([string]::IsNullOrWhiteSpace($exceptionId)) {
                            Write-Host "Exception id is mandatory for approved exception." -ForegroundColor Red
                        }
                        else {
                            $this.attestOptions.ApprovedExceptionID = $exceptionId
                        }
                    }
                    $approvedExceptionExpiryDate = Read-Host "Please enter the approved exception expiry date (mm/dd/yy)"
                    $expiryPeriod = $this.ControlSettings.DefaultAttestationPeriodForExemptControl
                    if([string]::IsNullOrWhiteSpace($approvedExceptionExpiryDate))
                    {
                        $exceptionApprovalExpiryDate =  ([DateTime]::UtcNow).AddDays($expiryPeriod)
                    }
                    else{
                        try
                        {
                            $maxAllowedExceptionApprovalExpiryDate = ([DateTime]::UtcNow).AddDays($expiryPeriod)
                            [datetime]$proposedExceptionApprovalExpiryDate = $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
                            Write-Host "`Skipping the attestation for this instance." -ForegroundColor Red
                            break;
                        }
                    }
                }

                if($controlState.AttestationStatus -ne [AttestationStatus]::None)
                {
                    # Justification is not needed when approved exception is enforced
                    if ($controlState.AttestationStatus -ne "ApprovedException" -and -not ($this.isApprovedExceptionEnforced -and  $this.approvedExceptionControlsList -contains $controlState.ControlId)) {
                        $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 -or ( $this.isApprovedExceptionEnforced -and $this.approvedExceptionControlsList -contains $controlState.ControlId))
                {
                    $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" -or ($this.isApprovedExceptionEnforced -and ($this.approvedExceptionControlsList -contains $controlState.ControlId)))
                        {
                            $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
                                    Write-Host "`Skipping the attestation for this instance." -ForegroundColor Red
                                    break;
                                }
                            }
                        }
                        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 -or ($this.isApprovedExceptionEnforced -and ($this.approvedExceptionControlsList -contains $controlState.ControlId)))
                        {
                            $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
                if ($this.isApprovedExceptionEnforced) {
                    $bulkAttestedControl = $this.ControlResults.ControlItem[0].ControlID ;
                    #Blocking bulk attestation for multiple resources as approved exception id will not be provided for bulk resources
                    if($this.approvedExceptionControlsList -contains $bulkAttestedControl) {
                        #if bulk attestation is for single resource, continue with the attestation
                        $exceptionId = ""
                        if ([string]::IsNullOrWhiteSpace($this.attestOptions.ApprovedExceptionID) -or [string]::IsNullOrWhiteSpace($this.attestOptions.ApprovedExceptionExpiryDate)) {
                            Write-Host "This control can only be attested using approved exception as mandated by your org." -ForegroundColor Cyan
                            # If enforce approved exception is enabled, prompt the user with respective message configured in org policy to fetch the exception id
                            $approvedExceptionPromptMessage = ""
                            if ([Helpers]::CheckMember($this.ControlSettings, "ApprovedExceptionSettings")) {
                                if ($this.attestOptions.AttestationStatus -eq "ApprovedException") {
                                    if (-not [string]::IsNullOrWhiteSpace($this.ControlSettings.ApprovedExceptionSettings.ApprovedExceptionPromptMessage)) {
                                        $approvedExceptionPromptMessage = $this.ControlSettings.ApprovedExceptionSettings.ApprovedExceptionPromptMessage
                                    }
                                }
                                else {
                                    if (-not [string]::IsNullOrWhiteSpace($this.ControlSettings.ApprovedExceptionSettings.NonApprovedExceptionPromptMessage)) {
                                        $approvedExceptionPromptMessage = $this.ControlSettings.ApprovedExceptionSettings.NonApprovedExceptionPromptMessage
                                    }
                                }
                                if([string]::IsNullOrWhiteSpace($approvedExceptionPromptMessage)) {
                                    $approvedExceptionPromptMessage =  $this.ControlSettings.ApprovedExceptionSettings.DefaultPromptMessage
                                }
                                Write-Host $approvedExceptionPromptMessage -ForegroundColor Cyan
                            }
                            # Try fetching the exception id from the user until he provides the value
                            while ([string]::IsNullOrWhiteSpace($exceptionId)) {
                                $exceptionId = Read-Host "Please enter the approved exception id"
                                if ([string]::IsNullOrWhiteSpace($exceptionId)) {
                                    Write-Host "Exception id is mandatory for approved exception." -ForegroundColor Red
                                }
                                else {
                                    $this.attestOptions.ApprovedExceptionID = $exceptionId
                                }
                            }
                            $approvedExceptionExpiryDate = Read-Host "Please enter the approved exception expiry date (mm/dd/yy)"
                            $this.attestOptions.ApprovedExceptionExpiryDate = $approvedExceptionExpiryDate
                        }
                    }
                }
            }
            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
        # Add approved exception to list of valid attestation states if it is not present already.
        if ($this.attestOptions.IsExemptModeOn -and $ValidAttestationStatesHashTable.Name -notcontains [AttestationStatus]::ApprovedException)
        {
            $ValidAttestationStatesHashTable += [Constants]::AttestationStatusHashMap.GetEnumerator() | Where-Object { $_.Name -eq [AttestationStatus]::ApprovedException }
        }

        return $ValidAttestationStatesHashTable;
    }
}

# SIG # Begin signature block
# MIIjlAYJKoZIhvcNAQcCoIIjhTCCI4ECAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDRA2C7zh0Fa+ML
# tP3qT1Fq55QS5PTCm5SF3OlTaJj9t6CCDYEwggX/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/BvW1taslScxMNelDNMYIVaTCCFWUCAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAd9r8C6Sp0q00AAAAAAB3zAN
# BglghkgBZQMEAgEFAKCBsDAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgUjG6Ud6b
# aIEP/Pi5lxa5PBqNGJFOqufUDYAWMBGuEEQwRAYKKwYBBAGCNwIBDDE2MDSgFIAS
# AE0AaQBjAHIAbwBzAG8AZgB0oRyAGmh0dHBzOi8vd3d3Lm1pY3Jvc29mdC5jb20g
# MA0GCSqGSIb3DQEBAQUABIIBAEJFQacH0exfBXdpmh/uXA3LdZ+aVtwOobUtrVpO
# 4fP+InFG7xfGUkLINimtjJFhMrx772+TIO8AooAUcgFvBVqf98QbKXswFtICiiNj
# p/EnPKTBX7aOxA4WNkeEySfVzKt+Xet554K+pXsMlSQttbJG5Tu+tJuc9zH6Kqdk
# MrmuHok831H1kKFe9HmfG5dVLFUeQ62ZkVDDALORN8NzzWpSUAYTyqbKOydFVuHp
# nEH2xK3a5YwIM8zj74csvt3BtENWOQzNeCNxZnTzG+ljDIgMN48kQTNvKKG9a5I+
# GH1ymRKhA3lcFNDi45yHbv+R0wZPbMFFTw7n2p2J6lqmALmhghLxMIIS7QYKKwYB
# BAGCNwMDATGCEt0wghLZBgkqhkiG9w0BBwKgghLKMIISxgIBAzEPMA0GCWCGSAFl
# AwQCAQUAMIIBVQYLKoZIhvcNAQkQAQSgggFEBIIBQDCCATwCAQEGCisGAQQBhFkK
# AwEwMTANBglghkgBZQMEAgEFAAQgKoR4PPVly6KLTkD+nqwm5WrvpKEvR558wfS9
# 7ASVOikCBmBjLQAfwxgTMjAyMTA0MTUxMTM5MjAuMTUyWjAEgAIB9KCB1KSB0TCB
# zjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMg
# TWljcm9zb2Z0IE9wZXJhdGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxl
# cyBUU1MgRVNOOjMyQkQtRTNENS0zQjFEMSUwIwYDVQQDExxNaWNyb3NvZnQgVGlt
# ZS1TdGFtcCBTZXJ2aWNloIIORDCCBPUwggPdoAMCAQICEzMAAAFi0P4C8wHlzUkA
# AAAAAWIwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh
# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD
# b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw
# MTAwHhcNMjEwMTE0MTkwMjIyWhcNMjIwNDExMTkwMjIyWjCBzjELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWljcm9zb2Z0IE9w
# ZXJhdGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjMy
# QkQtRTNENS0zQjFEMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2
# aWNlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA74ah1Pa5wvcyvYNC
# y/YQs1tK8rIGlh1Qq1QFaJmYVXLXykb+m5yCStzmL227wJjsalZX8JA2YcbaZV5I
# cwm9vAJz8AC/sk/dsUK3pmDvkhtVI04YDV6otuZCILpQB9Ipcs3d0e1Dl2KKFvdi
# bOk0/0rRxU9l+/Yxeb5lVTRERLxzI+Rd6Xv5QQYT6Sp2IE0N1vzIFd3yyO773T5X
# ifNgL5lZbtIUnYUVmUBKlVoemO/54aiFeVBpIG+YzhDTF7cuHNAzxWIbP1wt4VIq
# AV9JjuqLMvvBSD56pi8NTKM9fxrERAeaTS2HbfBYfmnRZ27Czjeo0ijQ5DSZGi0E
# rvWfKQIDAQABo4IBGzCCARcwHQYDVR0OBBYEFMvEShFgSkO3OnzgHlaVk3aQ/ipr
# MB8GA1UdIwQYMBaAFNVjOlyKMZDzQ3t8RhvFM2hahW1VMFYGA1UdHwRPME0wS6BJ
# oEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01p
# Y1RpbVN0YVBDQV8yMDEwLTA3LTAxLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYB
# BQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljVGlt
# U3RhUENBXzIwMTAtMDctMDEuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYI
# KwYBBQUHAwgwDQYJKoZIhvcNAQELBQADggEBAC1BrcOhdhtb9xcAJtxVIUZ7iALw
# ZewXFIdPcmDAVT810k5xuRwVNW9Onq+WZO8ebqwiOSdEEHReLU0FOo/DbS7q79Ps
# Kdz/PSBPqZ/1ysjRVH0L5HUK2N7NgpkR1lnt+41BaOzJ+00OFDL5GqeqvK3RWh7M
# tqWF6KKcfNkP/hjiFlg9/S7xNK/Vl8q10HB5YbdBTQun8j1Jsih6YMb3tFQsxw++
# ra5+FSnc4yJhAYvVaqTKRKepEmwzYhwDiXh2ag80/p0uDkOvs1WhgogwidpBVmNL
# AMxmFavK9+LNfRKvPIuCQw+EsxWR8vFBBJDfs14WTsXVF94CQ1YCHqYI5EEwggZx
# 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+P3nSISRKhggLSMIICOwIBATCB/KGB1KSB0TCBzjELMAkGA1UE
# BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc
# BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWljcm9zb2Z0
# IE9wZXJhdGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNO
# OjMyQkQtRTNENS0zQjFEMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT
# ZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQCas/oKGtvPRrHuznufk+indULyDKCBgzCB
# gKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV
# BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBBQUA
# AgUA5CIaSTAiGA8yMDIxMDQxNTA1NTAwMVoYDzIwMjEwNDE2MDU1MDAxWjB3MD0G
# CisGAQQBhFkKBAExLzAtMAoCBQDkIhpJAgEAMAoCAQACAiFwAgH/MAcCAQACAhFl
# MAoCBQDkI2vJAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAI
# AgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQEFBQADgYEAKwYD6U67pEQA
# dSNnDsw1pA3/FthZd+rCpWRCURWO0iZSNFpmnFc95L7uBTv97IM/4zg7aWP7TK3y
# /zCR05z8KHvhzS8TbE0K/Rm8MQcs8t7CRAqEsKavTnc/75Wiw72acpNOyGbY9hy8
# VOAZdtu7h+F3hOBukbti9cmnUljcAMsxggMNMIIDCQIBATCBkzB8MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQg
# VGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAWLQ/gLzAeXNSQAAAAABYjANBglghkgB
# ZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3
# DQEJBDEiBCCppgnnhiK7f9ToxkS4de7nbW/Yu5l8SfO+99VRSxOmqDCB+gYLKoZI
# hvcNAQkQAi8xgeowgecwgeQwgb0EIIqqGJX7PA0OulTsNEHsyLnvGLoYE1iwaOBm
# qrapUwoyMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0
# b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh
# dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMA
# AAFi0P4C8wHlzUkAAAAAAWIwIgQgVdPAamQPpbGE5+SiMHOalFWLBxEnnhsuxbC1
# +CFyGAAwDQYJKoZIhvcNAQELBQAEggEAJc8cPwU2TDykhvQQucMRYXmQ4/Bycndy
# J22FMixIUy9jMmhYNaCVb0VrNyNq0LDKoxtk9sS5j3dIdvsVQqjPUQ2CSSrbgem5
# bW1S1XVT42wnlZNUvM2yGYX6P3yuKXyBG8dOjpInTln4PMhA35peS3yZPU1ZcqEW
# gQcMTBM8YFXQk73KQOR+l9YZZpUOJVsgs3EDfa3Gt2iAUnjDpNrRjoktlBzDKZv1
# hyJeITdq1CPktCbXeUU4MKLMN/y9txNfZk0BYa8mhowJJkJTlpUGObtC29MJsSKm
# P3oRZ/ABmggRYcp7mm/zlu8/5JFssQILcMnqgttOGaHEvqYju4B3nA==
# SIG # End signature block