Framework/Managers/ControlStateExtension.ps1

Set-StrictMode -Version Latest

class ControlStateExtension
{
    hidden [PSObject] $AzSDKResourceGroup = $null;
    hidden [PSObject] $AzSDKStorageAccount = $null;
    hidden [PSObject] $AzSDKStorageContainer = $null;
    hidden [PSObject] $ControlStateIndexer = $null;    
    hidden [int] $HasControlStateReadPermissions = -1;
    hidden [int] $HasControlStateWritePermissions = -1;
    hidden [string]    $IndexerBlobName ="Resource.index.json"


    ControlStateExtension()
    {

    }

    hidden [void] Initialize([bool] $CreateResourcesIfNotExists)
    {
        $this.GetAzSDKControlStateContainer($CreateResourcesIfNotExists)
    }

    hidden [PSObject] GetAzSDKRG([bool] $createIfNotExists)
    {
        $azSDKConfigData = [ConfigurationManager]::GetAzSdkConfigData()

        $resourceGroup = Get-AzureRmResourceGroup -Name $azSDKConfigData.AzSDKRGName -ErrorAction SilentlyContinue
        if($createIfNotExists -and ($null -eq $resourceGroup -or ($resourceGroup | Measure-Object).Count -eq 0))
        {
            if([Helpers]::NewAzSDKResourceGroup($azSDKConfigData.AzSDKRGName, [Constants]::AzSDKRGLocation, ""))
            {
                $resourceGroup = Get-AzureRmResourceGroup -Name $azSDKConfigData.AzSDKRGName -ErrorAction SilentlyContinue
            }
        }
        $this.AzSDKResourceGroup = $resourceGroup
        return $resourceGroup;
    }

    hidden [void] GetAzSDKStorageAccount($createIfNotExists)
    {
        if($null -eq $this.AzSDKResourceGroup)
        {
            $this.GetAzSDKRG($createIfNotExists);
        }
        if($null -ne $this.AzSDKResourceGroup)
        {
            $StorageAccount = Get-AzureRmStorageAccount -ResourceGroupName $this.AzSDKResourceGroup.ResourceGroupName | Where-Object {$_.StorageAccountName -like 'azsdk*'} -ErrorAction SilentlyContinue 

            #if no storage account found then it assumes that there is no control state feature is not used and if there are more than one storage account found it assumes the same
            if($createIfNotExists -and ($null -eq $StorageAccount -or ($StorageAccount | Measure-Object).Count -eq 0))
            {
                $storageAccountName = ("azsdk" + (Get-Date).ToUniversalTime().ToString("yyyyMMddHHmmss"));    
                $storageObject = [Helpers]::NewAzsdkCompliantStorage($storageAccountName, $this.AzSDKResourceGroup.ResourceGroupName, [Constants]::AzSDKRGLocation)
                if($null -ne $storageObject -and ($storageObject | Measure-Object).Count -gt 0)
                {
                    $StorageAccount = Get-AzureRmStorageAccount -ResourceGroupName $this.AzSDKResourceGroup.ResourceGroupName | Where-Object {$_.StorageAccountName -like 'azsdk*'} -ErrorAction SilentlyContinue                     
                }                    
            }
            $this.AzSDKStorageAccount = $StorageAccount;
        }
    }

    hidden [void] GetAzSDKControlStateContainer([bool] $createIfNotExists)
    {
        $ContainerName = "azsdk-controls-state"        
        if($null -eq $this.AzSDKStorageAccount)
        {
            $this.GetAzSDKStorageAccount($createIfNotExists)
        }
        if($null -eq $this.AzSDKStorageAccount)
        {
            #No storage account => no permissions at all
            $this.HasControlStateReadPermissions = 0
            $this.HasControlStateWritePermissions = 0
            return;
        }
        
        try
        {
            #Able to read the container then read permissions are good
            $containerObject = Get-AzureStorageContainer -Context $this.AzSDKStorageAccount.Context -Name $ContainerName -ErrorAction Stop
            $this.AzSDKStorageContainer = $containerObject;
            $this.HasControlStateReadPermissions = 1
        }
        catch
        {
            #Resetting permissions in the case of exception
            $this.HasControlStateReadPermissions = 0
            $this.HasControlStateWritePermissions = 0
            try
            {
                if($createIfNotExists)
                {
                    New-AzureStorageContainer -Context $this.AzSDKStorageAccount.Context -Name $ContainerName -ErrorAction SilentlyContinue
                    $this.HasControlStateWritePermissions = 1
                }
                $containerObject = $containerObject = Get-AzureStorageContainer -Context $this.AzSDKStorageAccount.Context -Name $ContainerName -ErrorAction SilentlyContinue
                $this.AzSDKStorageContainer = $containerObject;
                $this.HasControlStateReadPermissions = 1
            }
            catch
            {
                #Do nothing
            }
        }        
    }
    
    hidden [bool] ComputeControlStateIndexer()
    {
        #check for permission validation
        if($this.HasControlStateReadPermissions -le 0) 
        {
            return $false;
        }
        #return if you don't have the required state attestation configuration during the runtime evaluation
        if( $null -eq $this.AzSDKResourceGroup -or $null -eq $this.AzSDKStorageAccount -or $null -eq $this.AzSDKStorageContainer)
        {
            return $false;
        }
        $StorageAccount = $this.AzSDKStorageAccount;
        $containerObject = $this.AzSDKStorageContainer;
        $ContainerName = ""
        if($null -ne $this.AzSDKStorageContainer)
        {
            $ContainerName = $this.AzSDKStorageContainer.Name
        }        
        $indexerBlob = $null;
        try
        {
            $indexerBlob = Get-AzureStorageBlob -Container $ContainerName -Blob $this.IndexerBlobName -Context $StorageAccount.Context -ErrorAction Stop
        }
        catch
        {
            #Do Nothing. Below code would create a default indexer.
        }
        [ControlStateIndexer[]] $indexerObjects = @();
        $this.ControlStateIndexer  = $indexerObjects
        if($null -eq $indexerBlob)
        {            
            return $true;
        }
        $AzSDKTemp = [Constants]::AzSdkAppFolderPath + "\Temp\ServerControlState";
        if(-not (Test-Path -Path $AzSDKTemp))
        {
            mkdir -Path $AzSDKTemp -Force
        }
        Get-AzureStorageBlobContent -CloudBlob $indexerBlob.ICloudBlob -Context $StorageAccount.Context -Destination $AzSDKTemp -Force
        $indexerObject = Get-ChildItem -Path "$AzSDKTemp\$($this.IndexerBlobName)" -Force | Get-Content | ConvertFrom-Json 
        $this.ControlStateIndexer += $indexerObject;
        return $true;
    }

    hidden [PSObject] GetControlState([string] $id)
    {
        try
        {
            [ControlState[]] $controlStates = @();
            $retVal = $this.ComputeControlStateIndexer();
            if($null -ne $this.ControlStateIndexer -and  $retVal)
            {
                $indexes = @();
                $indexes += $this.ControlStateIndexer 

                $selectedIndex = $indexes | Where-Object { $_.ResourceId -eq $id -and $_.ExpiryTime -gt [DateTime]::UtcNow}

                if(($selectedIndex | Measure-Object).Count -gt 0)
                {
                    $controlStateBlobName = $selectedIndex.HashId + ".json"
                    $azSDKConfigData = [ConfigurationManager]::GetAzSdkConfigData()
                    #$azSDKConfigData.$AzSDKRGName
                    #Look of is there is a AzSDK RG and AzSDK Storage account
                    $StorageAccount = $this.AzSDKStorageAccount;                        
                    $containerObject = $this.AzSDKStorageContainer
                    $ContainerName = ""
                    if($null -ne $this.AzSDKStorageContainer)
                    {
                        $ContainerName = $this.AzSDKStorageContainer.Name
                    }
                    $controlStateBlob = Get-AzureStorageBlob -Container $ContainerName -Blob $controlStateBlobName -Context $StorageAccount.Context -ErrorAction SilentlyContinue

                    if($null -eq $controlStateBlob)
                    {
                        return $controlStates;
                    }
                    $AzSDKTemp = [Constants]::AzSdkAppFolderPath + "\Temp\ServerControlState";
                    if(-not (Test-Path -Path $AzSDKTemp))
                    {
                        mkdir -Path $AzSDKTemp -Force
                    }
                    Get-AzureStorageBlobContent -CloudBlob $controlStateBlob.ICloudBlob -Context $StorageAccount.Context -Destination $AzSDKTemp -Force
                    $ControlStatesJson = Get-ChildItem -Path "$AzSDKTemp\$controlStateBlobName" -Force | Get-Content | ConvertFrom-Json 

                    if($null -ne $ControlStatesJson)
                    {                    
                        $ControlStatesJson | ForEach-Object {
                            try
                            {
                                $controlStates += [ControlState] $_;
                            }
                            catch 
                            {
                                [EventBase]::PublishGenericException($_);
                            }
                        }
                    }
                }
            }
            return $controlStates;
        }
        finally{
            $this.CleanTempFolder();
        }
    }

    hidden [void] SetControlState([string] $id, [ControlState[]] $controlStates, [bool] $Override)
    {        
        $AzSDKTemp = [Constants]::AzSdkAppFolderPath + "\Temp\ServerControlState";                
        if(-not (Test-Path "$AzSDKTemp\ControlState"))
        {
            mkdir -Path "$AzSDKTemp\ControlState" -ErrorAction Stop | Out-Null
        }

        $hash = [Helpers]::ComputeHash($id);
        $indexerPath = "$AzSDKTemp\ControlState\$($this.IndexerBlobName)"
        $fileName = "$AzSDKTemp\ControlState\$hash.json"    
        
        $StorageAccount = $this.AzSDKStorageAccount;                        
        $containerObject = $this.AzSDKStorageContainer
        $ContainerName = ""
        if($null -ne $this.AzSDKStorageContainer)
        {
            $ContainerName = $this.AzSDKStorageContainer.Name
        }
        $finalControlStates = $controlStates;
        if($Override)
        {
            # in the case of override, just persist what is evaluated in the current context. No merging with older data
            $this.UpdateControlIndexer($id, $controlStates);
            $finalControlStates = $controlStates | Where-Object { $_.State};
        }
        else
        {
            #merge with the exiting if found
            $persistedControlStates = $this.GetPersistedControlStates("$hash.json");
            $finalControlStates = $this.MergeControlStates($persistedControlStates, $controlStates);
            $this.UpdateControlIndexer($id, $finalControlStates);
        }
        [Helpers]::ConvertToJsonCustom($finalControlStates) | Out-File $fileName -Force        

        if($null -ne $this.ControlStateIndexer)
        {                
            [Helpers]::ConvertToJsonCustom($this.ControlStateIndexer) | Out-File $indexerPath -Force
            $controlStateArray = Get-ChildItem -Path "$AzSDKTemp\ControlState"                
            $controlStateArray | ForEach-Object { Set-AzureStorageBlobContent -File $_.FullName -Container $ContainerName -BlobType Block -Context $StorageAccount.Context -Force }
        }
        else
        {
            #clean up the container as there is no indexer
            Get-AzureStorageBlob -Container $ContainerName -Context $StorageAccount.Context | Remove-AzureStorageBlob  
        }
    }

    hidden [ControlState[]] GetPersistedControlStates([string] $controlStateBlobName)
    {
        $AzSDKTemp = [Constants]::AzSdkAppFolderPath + "\Temp\ServerControlState";
        if(-not (Test-Path "$AzSDKTemp\ExistingControlStates"))
        {
            mkdir -Path "$AzSDKTemp\ExistingControlStates" -ErrorAction Stop | Out-Null
        }
        $StorageAccount = $this.AzSDKStorageAccount;                        
        $containerObject = $this.AzSDKStorageContainer
        $ContainerName = ""
        if($null -ne $this.AzSDKStorageContainer)
        {
            $ContainerName = $this.AzSDKStorageContainer.Name
        }
        [ControlState[]] $ControlStatesJson = @()
        try
        {
            $controlStateBlob = Get-AzureStorageBlob -Container $ContainerName -Blob $controlStateBlobName -Context $StorageAccount.Context -ErrorAction SilentlyContinue
            Get-AzureStorageBlobContent -CloudBlob $controlStateBlob.ICloudBlob -Context $StorageAccount.Context -Destination "$AzSDKTemp\ExistingControlStates" -Force
            $ControlStatesJson = Get-ChildItem -Path "$AzSDKTemp\ExistingControlStates\$controlStateBlobName" -Force | Get-Content | ConvertFrom-Json 
        }
        catch
        {
            $ControlStatesJson = @()
        }
        return $ControlStatesJson
    }

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

        return $computedControlStates;
    }

    hidden [void] UpdateControlIndexer([string] $id, [ControlState[]] $controlStates)
    {
        $this.ControlStateIndexer = $null;
        $retVal = $this.ComputeControlStateIndexer();
        $StorageAccount = $this.AzSDKStorageAccount;                        
        $containerObject = $this.AzSDKStorageContainer
        $ContainerName = ""
        if($null -ne $this.AzSDKStorageContainer)
        {
            $ContainerName = $this.AzSDKStorageContainer.Name
        }
        if($retVal)
        {                
            $tempHash = [Helpers]::ComputeHash($id);
            $filteredIndexerObject = $this.ControlStateIndexer | Where-Object { $_.HashId -eq $tempHash}
            if($null -ne $filteredIndexerObject)
            {
                if(($controlStates | Measure-Object).Count -le 0)
                {
                    $this.ControlStateIndexer = $this.ControlStateIndexer | Where-Object { $_.HashId -ne $tempHash}
                }
                else
                {
                    $filteredIndexerObject.ExpiryTime = [DateTime]::UtcNow.AddMonths(3);
                    $filteredIndexerObject.AttestedBy =  [Helpers]::GetCurrentSessionUser();
                    $filteredIndexerObject.AttestedDate = [DateTime]::UtcNow;
                    $filteredIndexerObject.Version = "1.0";
                }
            }
            else
            {
                $currentIndexObject = [ControlStateIndexer]::new();
                $currentIndexObject.ResourceId = $id
                $currentIndexObject.HashId = $tempHash;
                $currentIndexObject.ExpiryTime = [DateTime]::UtcNow.AddMonths(3);
                $currentIndexObject.AttestedBy = [Helpers]::GetCurrentSessionUser();
                $currentIndexObject.AttestedDate = [DateTime]::UtcNow;
                $currentIndexObject.Version = "1.0";
                $this.ControlStateIndexer += $currentIndexObject;            
            }
        }
    }
    
    [bool] HasControlStateReadAccessPermissions()
    {
        if($this.HasControlStateReadPermissions -le 0)
        {
            return $false;
        }
        else
        {
            return $true;
        }
    }

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

    hidden [void] CleanTempFolder()
    {
        $AzSDKTemp = [Constants]::AzSdkAppFolderPath + "\Temp";                
        if(Test-Path "$AzSDKTemp")
        {
            rmdir -Path $AzSDKTemp -Recurse -Force -ErrorAction Stop | Out-Null
        }

    }

    
}
# SIG # Begin signature block
# MIIj/wYJKoZIhvcNAQcCoIIj8DCCI+wCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCC/QWM9uZYm8hUz
# TCxAdawFs90fY6RzNWZIw00K/XQkhKCCDZMwggYRMIID+aADAgECAhMzAAAAjoeR
# pFcaX8o+AAAAAACOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMTYxMTE3MjIwOTIxWhcNMTgwMjE3MjIwOTIxWjCBgzEL
# MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v
# bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjENMAsGA1UECxMETU9Q
# UjEeMBwGA1UEAxMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMIIBIjANBgkqhkiG9w0B
# AQEFAAOCAQ8AMIIBCgKCAQEA0IfUQit+ndnGetSiw+MVktJTnZUXyVI2+lS/qxCv
# 6cnnzCZTw8Jzv23WAOUA3OlqZzQw9hYXtAGllXyLuaQs5os7efYjDHmP81LfQAEc
# wsYDnetZz3Pp2HE5m/DOJVkt0slbCu9+1jIOXXQSBOyeBFOmawJn+E1Zi3fgKyHg
# 78CkRRLPA3sDxjnD1CLcVVx3Qv+csuVVZ2i6LXZqf2ZTR9VHCsw43o17lxl9gtAm
# +KWO5aHwXmQQ5PnrJ8by4AjQDfJnwNjyL/uJ2hX5rg8+AJcH0Qs+cNR3q3J4QZgH
# uBfMorFf7L3zUGej15Tw0otVj1OmlZPmsmbPyTdo5GPHzwIDAQABo4IBgDCCAXww
# HwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0OBBYEFKvI1u2y
# FdKqjvHM7Ww490VK0Iq7MFIGA1UdEQRLMEmkRzBFMQ0wCwYDVQQLEwRNT1BSMTQw
# MgYDVQQFEysyMzAwMTIrYjA1MGM2ZTctNzY0MS00NDFmLWJjNGEtNDM0ODFlNDE1
# ZDA4MB8GA1UdIwQYMBaAFEhuZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEsw
# SaBHoEWGQ2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0Nv
# ZFNpZ1BDQTIwMTFfMjAxMS0wNy0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsG
# AQUFBzAChkVodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01p
# Y0NvZFNpZ1BDQTIwMTFfMjAxMS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkq
# hkiG9w0BAQsFAAOCAgEARIkCrGlT88S2u9SMYFPnymyoSWlmvqWaQZk62J3SVwJR
# avq/m5bbpiZ9CVbo3O0ldXqlR1KoHksWU/PuD5rDBJUpwYKEpFYx/KCKkZW1v1rO
# qQEfZEah5srx13R7v5IIUV58MwJeUTub5dguXwJMCZwaQ9px7eTZ56LadCwXreUM
# tRj1VAnUvhxzzSB7pPrI29jbOq76kMWjvZVlrkYtVylY1pLwbNpj8Y8zon44dl7d
# 8zXtrJo7YoHQThl8SHywC484zC281TllqZXBA+KSybmr0lcKqtxSCy5WJ6PimJdX
# jrypWW4kko6C4glzgtk1g8yff9EEjoi44pqDWLDUmuYx+pRHjn2m4k5589jTajMW
# UHDxQruYCen/zJVVWwi/klKoCMTx6PH/QNf5mjad/bqQhdJVPlCtRh/vJQy4njpI
# BGPveJiiXQMNAtjcIKvmVrXe7xZmw9dVgh5PgnjJnlQaEGC3F6tAE5GusBnBmjOd
# 7jJyzWXMT0aYLQ9RYB58+/7b6Ad5B/ehMzj+CZrbj3u2Or2FhrjMvH0BMLd7Hald
# G73MTRf3bkcz1UDfasouUbi1uc/DBNM75ePpEIzrp7repC4zaikvFErqHsEiODUF
# he/CBAANa8HYlhRIFa9+UrC4YMRStUqCt4UqAEkqJoMnWkHevdVmSbwLnHhwCbww
# ggd6MIIFYqADAgECAgphDpDSAAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYD
# VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe
# MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3Nv
# ZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5
# MDlaFw0yNjA3MDgyMTA5MDlaMH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
# cG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIw
# MTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQ
# TTS68rZYIZ9CGypr6VpQqrgGOBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULT
# iQ15ZId+lGAkbK+eSZzpaF7S35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYS
# L+erCFDPs0S3XdjELgN1q2jzy23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494H
# DdVceaVJKecNvqATd76UPe/74ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZ
# PrGMXeiJT4Qa8qEvWeSQOy2uM1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5
# bmR/U7qcD60ZI4TL9LoDho33X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGS
# rhwjp6lm7GEfauEoSZ1fiOIlXdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADh
# vKwCgl/bwBWzvRvUVUvnOaEP6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON
# 7E1JMKerjt/sW5+v/N2wZuLBl4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xc
# v3coKPHtbcMojyyPQDdPweGFRInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqw
# iBfenk70lrC8RqBsmNLg1oiMCwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMC
# AQAwHQYDVR0OBBYEFEhuZOVQBdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQM
# HgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1Ud
# IwQYMBaAFHItOgIxkEO5FAVO4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0
# dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0Nl
# ckF1dDIwMTFfMjAxMV8wM18yMi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUF
# BzAChkJodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0Nl
# ckF1dDIwMTFfMjAxMV8wM18yMi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGC
# Ny4DMIGDMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtp
# b3BzL2RvY3MvcHJpbWFyeWNwcy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcA
# YQBsAF8AcABvAGwAaQBjAHkAXwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZI
# hvcNAQELBQADggIBAGfyhqWY4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4s
# PvjDctFtg/6+P+gKyju/R6mj82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKL
# UtCw/WvjPgcuKZvmPRul1LUdd5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7
# pKkFDJvtaPpoLpWgKj8qa1hJYx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft
# 0N3zDq+ZKJeYTQ49C/IIidYfwzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4
# MnEnGn+x9Cf43iw6IGmYslmJaG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxv
# FX1Fp3blQCplo8NdUmKGwx1jNpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG
# 0QaxdR8UvmFhtfDcxhsEvt9Bxw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf
# 0AApxbGbpT9Fdx41xtKiop96eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkY
# S//WsyNodeav+vyL6wuA6mk7r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrv
# QQqxP/uozKRdwaGIm1dxVk5IRcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIV
# wjCCFb4CAQEwgZUwfjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEoMCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAA
# AI6HkaRXGl/KPgAAAAAAjjANBglghkgBZQMEAgEFAKCBsDAZBgkqhkiG9w0BCQMx
# DAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkq
# hkiG9w0BCQQxIgQgA3UKB1Y7p7Pj4QAwiKQCPBgCGT6kPbvhoS0Mkoljb1YwRAYK
# KwYBBAGCNwIBDDE2MDSgEoAQAEEAegBTAEQASwAyADUAMqEegBxodHRwczovL2Fr
# YS5tcy9henNka29zc2RvY3MgMA0GCSqGSIb3DQEBAQUABIIBAJsHpBgubRimKxS/
# GUSLUXRCIVUD4xS2iIZpMrx8vsv4i+UPFXnOiuiqrzieQUNpKTkuoHNg9KtUDfmD
# S3IVDw5clgLmc4f+iaMHxIH+QDWw1Qs+rjvnUQa9hTFxgiYxgLKFsIEVaR6c9Rs0
# 8aLsFKit7YucgVOUC7F90bG+PYUxSYd0c/uXGeox63gnII3D5AYJZg/JP4qqCQGj
# NWN8FILFXU0z2w9y3BwP4PyqZXsEdBiRO/BkAJD/TDaWNww0gwlwyV5WqHgP1zIm
# rZBUOpiT7p8nHkkt+L4HPTi9D+a9LdRr8Aurxc3Q+uNPpbQVGqYLcyGUQOSOm/FP
# pLMe/5OhghNKMIITRgYKKwYBBAGCNwMDATGCEzYwghMyBgkqhkiG9w0BBwKgghMj
# MIITHwIBAzEPMA0GCWCGSAFlAwQCAQUAMIIBOgYLKoZIhvcNAQkQAQSgggEpBIIB
# JTCCASECAQEGCisGAQQBhFkKAwEwMTANBglghkgBZQMEAgEFAAQgLOC12PrpV6sI
# ndYOpZipRZiuEI96cxi3DKPJ5Dv/8gcCBlmSITMYABgTMjAxNzA5MDUwOTM3MjYu
# NjIzWjAEgAIB9KCBuaSBtjCBszELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjENMAsGA1UECxMETU9QUjEnMCUGA1UECxMebkNpcGhlciBEU0UgRVNO
# OjE0OEMtQzRCOS0yMDY2MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT
# ZXJ2aWNloIIO0DCCBnEwggRZoAMCAQICCmEJgSoAAAAAAAIwDQYJKoZIhvcNAQEL
# BQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNV
# BAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4X
# DTEwMDcwMTIxMzY1NVoXDTI1MDcwMTIxNDY1NVowfDELMAkGA1UEBhMCVVMxEzAR
# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3Rh
# bXAgUENBIDIwMTAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpHQ28
# dxGKOiDs/BOX9fp/aZRrdFQQ1aUKAIKF++18aEssX8XD5WHCdrc+Zitb8BVTJwQx
# H0EbGpUdzgkTjnxhMFmxMEQP8WCIhFRDDNdNuDgIs0Ldk6zWczBXJoKjRQ3Q6vVH
# gc2/JGAyWGBG8lhHhjKEHnRhZ5FfgVSxz5NMksHEpl3RYRNuKMYa+YaAu99h/EbB
# Jx0kZxJyGiGKr0tkiVBisV39dx898Fd1rL2KQk1AUdEPnAY+Z3/1ZsADlkR+79BL
# /W7lmsqxqPJ6Kgox8NpOBpG2iAg16HgcsOmZzTznL0S6p/TcZL2kAcEgCZN4zfy8
# wMlEXV4WnAEFTyJNAgMBAAGjggHmMIIB4jAQBgkrBgEEAYI3FQEEAwIBADAdBgNV
# HQ4EFgQU1WM6XIoxkPNDe3xGG8UzaFqFbVUwGQYJKwYBBAGCNxQCBAweCgBTAHUA
# YgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU
# 1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2Ny
# bC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIw
# MTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0w
# Ni0yMy5jcnQwgaAGA1UdIAEB/wSBlTCBkjCBjwYJKwYBBAGCNy4DMIGBMD0GCCsG
# AQUFBwIBFjFodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vUEtJL2RvY3MvQ1BTL2Rl
# ZmF1bHQuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAFAAbwBsAGkA
# YwB5AF8AUwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQAH
# 5ohRDeLG4Jg/gXEDPZ2joSFvs+umzPUxvs8F4qn++ldtGTCzwsVmyWrf9efweL3H
# qJ4l4/m87WtUVwgrUYJEEvu5U4zM9GASinbMQEBBm9xcF/9c+V4XNZgkVkt070IQ
# yK+/f8Z/8jd9Wj8c8pl5SpFSAK84Dxf1L3mBZdmptWvkx872ynoAb0swRCQiPM/t
# A6WWj1kpvLb9BOFwnzJKJ/1Vry/+tuWOM7tiX5rbV0Dp8c6ZZpCM/2pif93FSguR
# JuI57BlKcWOdeyFtw5yjojz6f32WapB4pm3S4Zz5Hfw42JT0xqUKloakvZ4argRC
# g7i1gJsiOCC1JeVk7Pf0v35jWSUPei45V3aicaoGig+JFrphpxHLmtgOR5qAxdDN
# p9DvfYPw4TtxCd9ddJgiCGHasFAeb73x4QDf5zEHpJM692VHeOj4qEir995yfmFr
# b3epgcunCaw5u+zGy9iCtHLNHfS4hQEegPsbiSpUObJb2sgNVZl6h3M7COaYLeqN
# 4DMuEin1wC9UJyH3yKxO2ii4sanblrKnQqLJzxlBTeCG+SqaoxFmMNO7dDJL32N7
# 9ZmKLxvHIa9Zta7cRDyXUHHXodLFVeNp3lfB0d4wwP3M5k37Db9dT+mdHhk4L7zP
# WAUu7w2gUDXa7wknHNWzfjUeCLraNtvTX4/edIhJEjCCBNowggPCoAMCAQICEzMA
# AAC0Qzoc/ra6UokAAAAAALQwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt
# U3RhbXAgUENBIDIwMTAwHhcNMTYwOTA3MTc1NjU4WhcNMTgwOTA3MTc1NjU4WjCB
# szELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjENMAsGA1UECxME
# TU9QUjEnMCUGA1UECxMebkNpcGhlciBEU0UgRVNOOjE0OEMtQzRCOS0yMDY2MSUw
# IwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIIBIjANBgkqhkiG
# 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4IFPu7XRMDo/gwC3zFaf95usurNdZBEegtZ6
# 1+4g+7PRCAFjl1enwuJMVqi1V9ugxt+z0RixHn6RrBkZUW1z/p4tbSRCIMTI70Zp
# 0G8cTGFqlDMPlD7bom8lKr8Z0s4DOlIVgEVlG/3Ptf83smhmmWWt7v++gU1Dngt4
# CdYqjz2KtAcz2bBQJFHvf/Uk1BUMj3YY2Fa8tW2jKXTYdQdIQBmOZhiRAgJwG0Hb
# +SehGXXGlqj6QS+7esU0pjCgl5PHGmwAWoK2jABnksvMTdJsqePEXnkLAZWuqKS5
# Iv75RV4/fRkbYZw3dNmjUcXuSNlUMxSDX7LnD3uwH8mXvpmFcQIDAQABo4IBGzCC
# ARcwHQYDVR0OBBYEFAyTq0XUbAt3L/MrV/PpJMSHB/RfMB8GA1UdIwQYMBaAFNVj
# OlyKMZDzQ3t8RhvFM2hahW1VMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwu
# bWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1RpbVN0YVBDQV8yMDEw
# LTA3LTAxLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93
# d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljVGltU3RhUENBXzIwMTAtMDct
# MDEuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwDQYJKoZI
# hvcNAQELBQADggEBAAK1YVugp9JqrCYvtsI0o3T7qHuQsYItqIkSXaT2ggtZPSfn
# eh15LPjLcs9Ha+9v3uuSbe6v+16hkYR4419Re8SXMeBQje26mfeIKr9RauIj5DdH
# 3WbixYUI7P51cet6bUmJJSEdnY4W5Fik5qiVtZu0k6GKLLicITq8AVEfmOCf8+3q
# UMy7N4QpavAibKVPrhMReWZkcCejDPq03ky7UH7En3/pgVEE3q4UX+YODBCBukas
# O2IS57XRCjDw0yns+tNwMW4KeiRRwiLmDiK3Q1GqU1Ui9SS159N1eCmhOltpCuCt
# fJnPn7SSKAd+qnDEMoZbSg7YRLb1PmcfecPyK1OhggN5MIICYQIBATCB46GBuaSB
# tjCBszELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT
# B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjENMAsGA1UE
# CxMETU9QUjEnMCUGA1UECxMebkNpcGhlciBEU0UgRVNOOjE0OEMtQzRCOS0yMDY2
# MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiUKAQEwCQYF
# Kw4DAhoFAAMVAAfAlZeuLk5uydN19tmJUZiLIG06oIHCMIG/pIG8MIG5MQswCQYD
# VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe
# MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMQ0wCwYDVQQLEwRNT1BSMScw
# JQYDVQQLEx5uQ2lwaGVyIE5UUyBFU046NERFOS0wQzVFLTNFMDkxKzApBgNVBAMT
# Ik1pY3Jvc29mdCBUaW1lIFNvdXJjZSBNYXN0ZXIgQ2xvY2swDQYJKoZIhvcNAQEF
# BQACBQDdWK0EMCIYDzIwMTcwOTA1MDQ1NjM2WhgPMjAxNzA5MDYwNDU2MzZaMHcw
# PQYKKwYBBAGEWQoEATEvMC0wCgIFAN1YrQQCAQAwCgIBAAICBzoCAf8wBwIBAAIC
# HBgwCgIFAN1Z/oQCAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAaAK
# MAgCAQACAwehIKEKMAgCAQACAwehIDANBgkqhkiG9w0BAQUFAAOCAQEALwY679N2
# 1lyYyv02M9hqGtWKqwzo5L9jFJwzVISPiB11A44qmS2N0VG0NjLrpoSYeVBQcopx
# 8q6MpEScwoBLY9jn5KBEjTfWxOyMEXoVPP01zMJ1PqXqVDNQeeHrwtBi5ewEB+sH
# vFeniiQLLavuRZOLLi+MJ7SI2aIifcxk2gzSyzj0NwWpFUZH8uKBhWN9qXM0IJoj
# PSw8YeJqcjaHA8M2lWiDzoLFuhvoppkXU5jQA+i7PpjSU7GR9c125iaw9G+yScBB
# /WVbMAbhm29cYwNG4AJ0cWhJP9T+cdJdFqav9Of91V7wy2ExAM5+hTyO9oB+8rjk
# pi8eGCYOTwO5wjGCAvUwggLxAgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
# EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv
# ZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBD
# QSAyMDEwAhMzAAAAtEM6HP62ulKJAAAAAAC0MA0GCWCGSAFlAwQCAQUAoIIBMjAa
# BgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIEIFqLH6hl
# 1LC1APVOk9co+1xNJmhJKMo4GlMJ2IIFJMQ/MIHiBgsqhkiG9w0BCRACDDGB0jCB
# zzCBzDCBsQQUB8CVl64uTm7J03X22YlRmIsgbTowgZgwgYCkfjB8MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQg
# VGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAALRDOhz+trpSiQAAAAAAtDAWBBRvckTu
# xLqEMp+p73tMOlL/8PMB4DANBgkqhkiG9w0BAQsFAASCAQAE4FK5kVcr3DNTgD3p
# Naj1Nj7kVHU/t58wwcYn7DLrtzbwpRE/06zgB2y4AUQBvFPtFctYtUkXvbuFkG1X
# i+HzxcBb3XwhNQoyebOpx6wdXJOa9030j4oVoi5wmHLikq4o2jpzyxwhRIvghVex
# D5lpcLOGWTIhCUFc1pZA3eO2cw7+d7PNcZulPdnw3PhIPZfngia+jBEAO8sWaPFu
# 6jhL8JcMOZ5aqouHHYzaYORyKJ+sxDcpuCIaTYMBErZughIuFn7W4SyQqGZ9GuV0
# dV2m5ghu82ok7/L64EQM0AEYrro4QnXcAojrNdS5GqGXccK60JurvOXlhKw/BDLH
# HgMQ
# SIG # End signature block