Framework/Core/STMapping/ServiceMappingCacheHelper.ps1

Set-StrictMode -Version Latest
class ServiceMappingCacheHelper {

    hidden static [ServiceMappingCacheHelper] $ServiceMappingCacheHelperInstance;
    hidden [bool] $UseAzureStorageAccount;
    hidden [string] $OrganizationName;
    # Resource Caching Storage settings to cache mappings
    hidden [string] $CacheStorageName;# Storage account name for Caching
    hidden [string] $CacheStorageRG;# Storage resource group name for Caching
    hidden [string] $CacheTable;#Storage table to cache resource mapping
    hidden [object] $CacheStorageAccountCtx;
    hidden [string] $SharedKey
    hidden [object] $hmacsha 

    ServiceMappingCacheHelper([string] $orgName) {
        $this.OrganizationName = $orgName;
        # Resources Caching Storage settings
        $this.CacheStorageName = $env:CacheStorageName;
        $this.CacheStorageRG = $env:CacheStorageRG;
        $this.CacheTable = $env:CacheTable;         
        #get storage details
        if ($this.CacheStorageRG -and $this.CacheStorageName) {
            $keys = Get-AzStorageAccountKey -ResourceGroupName $this.CacheStorageRG -Name $this.CacheStorageName
            if ($null -ne $keys)
            {             
               #storage context to cache resource mappings
               $CacheStorageContext = New-AzStorageContext -StorageAccountName $this.CacheStorageName -StorageAccountKey $keys[0].Value -Protocol Https                                
               $this.CacheStorageAccountCtx = $CacheStorageContext.Context; 
               $this.SharedKey = $keys[0].Value;
               $this.hmacsha = New-Object System.Security.Cryptography.HMACSHA256
               $this.hmacsha.key = [Convert]::FromBase64String($this.SharedKey) 
            }
                         
        }
        
    }
    
    #Return ServiceMappingCacheHelper instance
    hidden static [ServiceMappingCacheHelper] GetInstance([string] $orgName) {
        [ServiceMappingCacheHelper]::ServiceMappingCacheHelperInstance = [ServiceMappingCacheHelper]::new($orgName)
        return [ServiceMappingCacheHelper]::ServiceMappingCacheHelperInstance
    }

    #function to search for existing mapping based on hash id
    hidden [object] GetWorkItemByHashAzureTable([string] $resourceType, [string] $pipelineType, [string] $pipelineId,[string] $resourceId, [string] $projectID) 
    {    
        #get storage table data.
        $azTableMappingInfo = @();   
        try {            
            $azTableMappingInfo += $this.GetTableEntity($projectID,$pipelineId,$pipelineType, $resourceId,$resourceType); 
            if ($azTableMappingInfo -and $azTableMappingInfo.count -gt 0) {                            
                return $azTableMappingInfo;
            }
        }
        catch {
            Write-Host $_
            Write-Host "Could not access storage account." -ForegroundColor Red
        }  
        
        return $azTableMappingInfo;
    }

    hidden [bool] InsertMappingInfoInTable( [string]  $orgName, [string]  $projectID, [string]  $pipelineID, [string]  $serviceTreeID,[string]  $pipelineLastModified,[string]  $resourceID,[string]  $resourceType,[string]  $resourceName,[string]  $pipelineType,[string]  $mappingExpiration) 
    {
        try 
        {                   
           #Get table filterd by name.
           $storageTables = @();
           $storageTables += Get-AzStorageTable -Context $this.CacheStorageAccountCtx | Select Name;

           #create table if table not found.
           if ( !$storageTables -or ($storageTables.count -eq 0) -or !($storageTables.Name -eq $this.CacheTable) ) {
               New-AzStorageTable $this.CacheTable -Context $this.CacheStorageAccountCtx;
           }

           $isDataAddedInTable = $this.AddDataInTable($orgName,$projectID,$pipelineID,$serviceTreeID,$pipelineLastModified,$resourceID,$resourceType,$resourceName,$pipelineType, $mappingExpiration)
           return $isDataAddedInTable;           
        }
        catch {
            return $false;
        } 
        return $false
    }

    hidden [string] GenerateSearchQuery($projectID,$pipelineId,$pipelineType, $resourceId,$resourceType,$hash)
    {
        $query = 'PartitionKey eq ''{0}''' -f $hash;
        if($resourceType -eq "All")
        {
           return 'OrgName eq ''{0}'' and ProjectID eq ''{1}''' -f $this.OrganizationName, $projectID; 
        }
        if($resourceType -eq "VariableGroup" -or $resourceType -eq "SecureFile" -and ![string]::IsNullOrEmpty($resourceId)) 
        {
            return 'RowKey eq ''{0}''' -f $hash;
        }
        if($resourceType -eq "VariableGroup" -or $resourceType -eq "SecureFile" -and [string]::IsNullOrEmpty($resourceId)) 
        {
            return 'OrgName eq ''{0}'' and PipelineType eq ''{1}''' -f $this.OrganizationName, $pipelineType;
        }
        return $query;
    }

    hidden [object] GetTableEntity($projectID,$pipelineId,$pipelineType, $resourceId,$resourceType) {
        try 
        {
            $hash = $this.GetHashedTag($projectID, $pipelineID, $pipelineType,$resourceID,$resourceType)            
            $query =$this.GenerateSearchQuery($projectID,$pipelineId,$pipelineType, $resourceId,$resourceType,$hash)                         
            $resource = '$filter='+[System.Web.HttpUtility]::UrlEncode($query);
            $table_url = "https://{0}.table.core.windows.net/{1}?{2}" -f $this.CacheStorageName, $this.CacheTable, $resource
            $headers = $this.GetHeader($this.CacheTable)
            $item = Invoke-RestMethod -Method Get -Uri $table_url -Headers $headers -ContentType "application/json"
            return $item.value;
        }
        catch
        {
            #Write-Host $_
            Write-Host "Could not fetch the entry for partition key from table storage or the table was not found.";
            return $null
        }
    }

    hidden [bool] AddDataInTable([string]  $orgName, [string]  $projectID, [string]  $pipelineID, [string]  $serviceTreeID,[string]  $pipelineLastModified,[string]  $resourceID,[string]  $resourceType,[string]  $resourceName,[string]  $pipelineType,[string]  $mappingExpiration) 
    {        
        $partitionKey = $this.GetHashedTag($projectID, $pipelineID, $pipelineType,"","");
        $rowKey = $this.GetHashedTag($projectID, $pipelineID, $pipelineType,$resourceID,$resourceType)
           
        try 
        {
            #Add data in table.
            $entity = @{"PartitionKey" = $partitionKey; "RowKey" = $rowKey; "OrgName" = $orgName; "ProjectID" = $projectID; "PipelineID" = $pipelineID;"ServiceTreeID" = $serviceTreeID;"PipelineLastModified" = $pipelineLastModified;"ResourceID" = $resourceID;"ResourceType" = $resourceType;"ResourceName" = $resourceName;"PipelineType" = $pipelineType;  "MappingExpiration" = $MappingExpiration};
            $table_url = "https://{0}.table.core.windows.net/{1}" -f $this.CacheStorageName, $this.CacheTable
            $headers = $this.GetHeader($this.CacheTable);
            $body = $entity | ConvertTo-Json
            $item = Invoke-RestMethod -Method POST -Uri $table_url -Headers $headers -Body $body -ContentType "application/json"
            return $true;
        }
        catch
        {
            Write-Host $_
            Write-Host "Could not push an entry in the table for row key [$rowKey]";
            return $false;
        }
    }

    hidden [bool] UpdateTableEntity([string]  $orgName, [string]  $projectID, [string]  $pipelineID, [string]  $serviceTreeID,[string]  $pipelineLastModified,[string]  $resourceID,[string]  $resourceType,[string]  $resourceName,[string]  $pipelineType,[string]  $mappingExpiration) 
    {
        $partitionKey = $this.GetHashedTag($projectID, $pipelineID, $pipelineType,"","");
        $rowKey = $this.GetHashedTag($projectID, $pipelineID, $pipelineType,$resourceID,$resourceType)
        
        try {
            #Update data in table.
            $tableName = $this.CacheTable;
            $entity = @{"OrgName" = $orgName; "ProjectID" = $projectID; "PipelineID" = $pipelineID;"ServiceTreeID" = $serviceTreeID;"PipelineLastModified" = $pipelineLastModified;"ResourceID" = $resourceID;"ResourceType" = $resourceType;"ResourceName" = $resourceName;"PipelineType" = $pipelineType;  "MappingExpiration" = $MappingExpiration};
            $body = $entity | ConvertTo-Json
            $version = "2017-04-17"
            $resource = "$tableName(PartitionKey='$PartitionKey',RowKey='$Rowkey')"
            $table_url = "https://$($this.CacheStorageName).table.core.windows.net/$resource"
            $GMTTime = (Get-Date).ToUniversalTime().toString('R')
            $stringToSign = "$GMTTime`n/$($this.CacheStorageName)/$resource"

            $signature = $this.hmacsha.ComputeHash([Text.Encoding]::UTF8.GetBytes($stringToSign))
            $signature = [Convert]::ToBase64String($signature)
            $body = $entity | ConvertTo-Json
            $headers = @{
                'x-ms-date'      = $GMTTime
                Authorization    = "SharedKeyLite " + $this.CacheStorageName + ":" + $signature
                "x-ms-version"   = $version
                Accept           = "application/json;odata=minimalmetadata"
                'If-Match'       = "*"
            }
            Invoke-RestMethod -Method PUT -Uri $table_url -Headers $headers -Body $body -ContentType "application/json;odata=minimalmetadata"

            return $true;
        }
        catch
        {
            Write-Host $_
            Write-Host "Could not update entry in the table for row key [$RowKey]";
            return $false;
        }
    }

    hidden [object] GetHeader($tableName)
    {
        $version = "2017-07-29"
        $GMTTime = (Get-Date).ToUniversalTime().toString('R')
        $stringToSign = "$GMTTime`n/$($this.CacheStorageName)/$tableName"
        
        $signature = $this.hmacsha.ComputeHash([Text.Encoding]::UTF8.GetBytes($stringToSign))
        $signature = [Convert]::ToBase64String($signature)
        $headers = @{
            'x-ms-date'    = $GMTTime
            Authorization  = "SharedKeyLite " + $this.CacheStorageName + ":" + $signature
            "x-ms-version" = $version
            Accept         = "application/json;odata=minimalmetadata"
        }
        return $headers
    }
    
    #function to compute hash and return the tag
    hidden [string] GetHashedTag([string] $projectID, [string] $pipelineID, [string] $pipelineType,[string] $resourceID,[string] $resourceType) { 
        $stringToHash = "$projectID#$pipelineID#$pipelineType";  
        if(![string]::IsNullOrEmpty($resourceType))
        {    
            $stringToHash = "$projectID#$pipelineID##$pipelineType$resourceID#$resourceType";
        }   
        return $this.ComputeHashX($stringToHash);     
    }

    hidden [string] ComputeHashX([string] $dataToHash) {
        return [Helpers]::ComputeHashShort($dataToHash, [Constants]::AutoBugLogTagLen)
    }
}

# SIG # Begin signature block
# MIInuQYJKoZIhvcNAQcCoIInqjCCJ6YCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCnc4Cdn2Fi/P1I
# oiiMJnzgdHZORBnx24QR5uDk+WtExqCCDYEwggX/MIID56ADAgECAhMzAAACUosz
# qviV8znbAAAAAAJSMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMjU5WhcNMjIwOTAxMTgzMjU5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDQ5M+Ps/X7BNuv5B/0I6uoDwj0NJOo1KrVQqO7ggRXccklyTrWL4xMShjIou2I
# sbYnF67wXzVAq5Om4oe+LfzSDOzjcb6ms00gBo0OQaqwQ1BijyJ7NvDf80I1fW9O
# L76Kt0Wpc2zrGhzcHdb7upPrvxvSNNUvxK3sgw7YTt31410vpEp8yfBEl/hd8ZzA
# v47DCgJ5j1zm295s1RVZHNp6MoiQFVOECm4AwK2l28i+YER1JO4IplTH44uvzX9o
# RnJHaMvWzZEpozPy4jNO2DDqbcNs4zh7AWMhE1PWFVA+CHI/En5nASvCvLmuR/t8
# q4bc8XR8QIZJQSp+2U6m2ldNAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUNZJaEUGL2Guwt7ZOAu4efEYXedEw
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDY3NTk3MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAFkk3
# uSxkTEBh1NtAl7BivIEsAWdgX1qZ+EdZMYbQKasY6IhSLXRMxF1B3OKdR9K/kccp
# kvNcGl8D7YyYS4mhCUMBR+VLrg3f8PUj38A9V5aiY2/Jok7WZFOAmjPRNNGnyeg7
# l0lTiThFqE+2aOs6+heegqAdelGgNJKRHLWRuhGKuLIw5lkgx9Ky+QvZrn/Ddi8u
# TIgWKp+MGG8xY6PBvvjgt9jQShlnPrZ3UY8Bvwy6rynhXBaV0V0TTL0gEx7eh/K1
# o8Miaru6s/7FyqOLeUS4vTHh9TgBL5DtxCYurXbSBVtL1Fj44+Od/6cmC9mmvrti
# yG709Y3Rd3YdJj2f3GJq7Y7KdWq0QYhatKhBeg4fxjhg0yut2g6aM1mxjNPrE48z
# 6HWCNGu9gMK5ZudldRw4a45Z06Aoktof0CqOyTErvq0YjoE4Xpa0+87T/PVUXNqf
# 7Y+qSU7+9LtLQuMYR4w3cSPjuNusvLf9gBnch5RqM7kaDtYWDgLyB42EfsxeMqwK
# WwA+TVi0HrWRqfSx2olbE56hJcEkMjOSKz3sRuupFCX3UroyYf52L+2iVTrda8XW
# esPG62Mnn3T8AuLfzeJFuAbfOSERx7IFZO92UPoXE1uEjL5skl1yTZB3MubgOA4F
# 8KoRNhviFAEST+nG8c8uIsbZeb08SeYQMqjVEmkwggd6MIIFYqADAgECAgphDpDS
# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla
# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT
# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG
# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S
# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz
# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7
# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u
# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33
# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl
# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP
# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB
# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF
# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM
# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ
# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud
# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO
# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0
# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p
# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw
# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA
# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY
# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj
# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd
# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ
# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf
# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ
# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j
# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B
# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96
# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7
# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I
# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIZjjCCGYoCAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAlKLM6r4lfM52wAAAAACUjAN
# BglghkgBZQMEAgEFAKCBsDAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQg/YSv6FiP
# XFcaE0eBSnFe5pJ6qLnQx9xO08mWzKAQFv8wRAYKKwYBBAGCNwIBDDE2MDSgFIAS
# AE0AaQBjAHIAbwBzAG8AZgB0oRyAGmh0dHBzOi8vd3d3Lm1pY3Jvc29mdC5jb20g
# MA0GCSqGSIb3DQEBAQUABIIBAEXwh7fDKM/p5x8l0uHMvkmvxmyBa1Js6b/Tvi4z
# cE1Qs9hhEYuX0teD9wI29g02OSrSAG+MalalA84PbgAjuc3AFsxNqF7dHyrexw1x
# iGazmSOLmt4r96sQtbxHJzgV8Z09z7ANhOEvL2LkFXKZhBqDyd6CnngYudTHTLc1
# +MeDPbRiJuGSZiB7JCr8xGmVKKqpwJrT5xE3Vcsn++oo6YBuWuMb0epxfg3sFlPx
# +7Kt/6/GpqWQ/v8Ptb3QI2vlHXbPqeRJ0CD68n20lj7S0O7NUwFSnpiDEIk5TajF
# fZ1aunjHlA1nRFCVw0TfOx98Rh43ckMIV3jS8XGPHwe7ADuhghcWMIIXEgYKKwYB
# BAGCNwMDATGCFwIwghb+BgkqhkiG9w0BBwKgghbvMIIW6wIBAzEPMA0GCWCGSAFl
# AwQCAQUAMIIBWQYLKoZIhvcNAQkQAQSgggFIBIIBRDCCAUACAQEGCisGAQQBhFkK
# AwEwMTANBglghkgBZQMEAgEFAAQgPMFdZMM6GO2KmgU5XzGRfiHlNKErWuR0g1Qw
# IKQ8IpgCBmHCHsxKYBgTMjAyMjAxMTMxMjU0MDQuMzQ1WjAEgAIB9KCB2KSB1TCB
# 0jELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMk
# TWljcm9zb2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1U
# aGFsZXMgVFNTIEVTTjoyQUQ0LTRCOTItRkEwMTElMCMGA1UEAxMcTWljcm9zb2Z0
# IFRpbWUtU3RhbXAgU2VydmljZaCCEWUwggcUMIIE/KADAgECAhMzAAABhnjlGYn4
# JEvMAAEAAAGGMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
# EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv
# ZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBD
# QSAyMDEwMB4XDTIxMTAyODE5MjczOVoXDTIzMDEyNjE5MjczOVowgdIxCzAJBgNV
# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w
# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29m
# dCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEmMCQGA1UECxMdVGhhbGVzIFRT
# UyBFU046MkFENC00QjkyLUZBMDExJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0
# YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDAjcbZ
# am/oHgiMB+uB8mmd0849g7Vh3z6+V+gjExbeB0INP7Mhtp+DXik67S3R6RRDHrSn
# s9p0fg6Oeo0gTWrqV0f2e2PWAh2Xgerit0QdNnokV0TbgNJtWiqpH5HgjDjDcY9t
# 9zZDeR/LIXKP4M6GYJbD8VmJNVOVPht16PIBbqv8mfh+vfEuNu+EhNq2vfpXLLOB
# DRjhavvcfeBRwuNi7SqIe60MNvr6n7IMEaYoXOc5bzBW3sP67ZUQmgTomUrQSlUt
# m6x1LOF5y5TAlfFva7KABleWxr98eXBb1ieUGowcn6Kb0e4rlfjHz/kHl2S4ihfm
# VYaMUxsPYDou78+ZQHiErQIXkbVhpS0GswTvcMAqTKmTtISbcGUlfBj8atWhdZhQ
# YQfJ+uQuTCzRGgQymggSB5tk0qqNHKdEmBHh88IqsSHASJNMBzgNcZyLgcc6brgR
# DWD9IMcwWogpVLGhRuQZt0o0oeGZqG4isDLjB72zutkmyS95lhmIOa0C0G3+BCiP
# FtnW870LXVK2GSuaSRMwtB/1wPOVUQF67oqYdfZLN7qCCd7cjhzL/khQucdneszh
# mklzSzYqkYsdpWsRDLjH+YCfjJph+B4fcwQBaRWPL+pMOHpwMIX+DLPdNpAO28Wc
# ArvQuq1sS8E90Gl4Ib+GT2XSVpjPCLLIZj8eowIDAQABo4IBNjCCATIwHQYDVR0O
# BBYEFBm2o0UD72Z0S7+HfdSEcw3rCFwuMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl
# 0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0
# LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAy
# MDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93
# d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1T
# dGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAww
# CgYIKwYBBQUHAwgwDQYJKoZIhvcNAQELBQADggIBAMPuZ6Eljd9McoLiGP7AFYHz
# nji5omIwwgeeEr041MztWtpNjPHRT9NwsnqDHDW1HMzm67ySzAk2Uc2ntF52wCLC
# +JVBlX0AvwhtlEslPA16ELCT4FVxjaCHdkZmbHy5q09mtG57KGFNMPY+8VUut/CH
# aWIMb90Q80gdMqPv0OURw8hag4JSnunQ5EzBD3mRVqJulfz2m+OE+XYWbQIE7eld
# cmDRvJ2lDl0MNO/+pvT5ZgX+81URT8ygwRCqVRZa5cQJOrHpNrIm4snq5TsrlDJO
# RD+XbgiEaMPN/kARk6sg1jORZXI19Q6kjGcqxZME3aKOln9O6fmquaj280gNPSWh
# uCe6Vp7Xs1oQ72iIQkkfW1Dfnd2G5GL4DTQ9HvzWJiXMXklTUOsR8TI3HwJaARGL
# 3QsqxiCFkEIONDcOImN9Rkuo414esl9yaHPn9t+bz5oBpQ+lkV4/SDQiid3pc2Th
# iJhtY8Wih9zQvBypIAu24gDLPp/d35RplmynjVTiEIigaPqGgMi5Tzf1uj+Zn8CA
# RLAbEhezSBlToD7aohR7rRB0D3r3BZLO5wo6KyeD0cJJksXV2pzdBRrCvQLRTjXv
# zgqj29yQAbdqTBi5UZyzqEz9KoSGh72MfB7henzUKtMHWX34Qh26QJs/STLPHRZn
# O156IM3mt2KJBH2YEm6WMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJmQAAAAAA
# FTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0
# aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1WjB8MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy
# b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEBBQADggIP
# ADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjKNVf2AX9s
# SuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhgfWpSg0S3
# po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJprx2rrPY2
# vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/dvI2k45GP
# sjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka97aSueik3
# rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKRHh09/SDP
# c31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM9ituqBJR6L8F
# A6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyOArxCaC4Q
# 6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItboKaDIV1f
# MHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6bMURHXLv
# jflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6tAgMBAAGj
# ggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQWBBQqp1L+
# ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacbUzUZ6XIw
# XAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMBMG
# A1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsG
# A1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJc
# YmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9z
# b2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIz
# LmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWlj
# cm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0
# MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/qXBS2Pk5H
# ZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6U03dmLq2
# HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2CDPVtI1TkeFN1
# JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis9/kpicO8
# F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTpkbKpW99J
# o3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0sHrYUP4K
# WN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138eW0QBjloZ
# kWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJsWkBRH58
# oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7Fx0ViY1w
# /ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0dFtq0Z4+
# 7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQtB1VM1iz
# oXBm8qGCAtQwggI9AgEBMIIBAKGB2KSB1TCB0jELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3Bl
# cmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjoyQUQ0LTRC
# OTItRkEwMTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIj
# CgEBMAcGBSsOAwIaAxUAAa7YNHNaQqWOZfJJfWSiscvh8yeggYMwgYCkfjB8MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy
# b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIFAOWKRk0w
# IhgPMjAyMjAxMTMxNDMzNDlaGA8yMDIyMDExNDE0MzM0OVowdDA6BgorBgEEAYRZ
# CgQBMSwwKjAKAgUA5YpGTQIBADAHAgEAAgIZ+DAHAgEAAgIRxjAKAgUA5YuXzQIB
# ADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQow
# CAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAKIWtk308KmIAIT87PJTgIoyeaNn
# NepiDidydE6NwilBgWfTq3FCkOy1GWkqVlCF6m18Z6CnXuK7FmyyJl7OEQTcEJXU
# WP3G2Z+jlabxjJ7nonm9kLKuwurLhhx/gOLybDeiW9iPGW8SQ9fo9KOuN/Z/VGTd
# BX/rAQpKCdRV1XshMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# UENBIDIwMTACEzMAAAGGeOUZifgkS8wAAQAAAYYwDQYJYIZIAWUDBAIBBQCgggFK
# MBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgYYwv
# 2J5sA3Y9okbv9phz5V/M3AJRVKam72pfvms/S0cwgfoGCyqGSIb3DQEJEAIvMYHq
# MIHnMIHkMIG9BCAamYjgsiwIVMaJjJ9EBHubsVraC7FU0jDXuZwCKrxCfjCBmDCB
# gKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV
# BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABhnjlGYn4JEvM
# AAEAAAGGMCIEIClsq4f7No/IGezWsybvgTZq3vTmW9zeFUziSF/OlMDvMA0GCSqG
# SIb3DQEBCwUABIICAEKcsu25396wo9DIK21y060uO/kuGOVD5kRs1zDkud44xRGw
# v+TYPGD68ltBo6l6b+v2pdA1DgZK6B8GlVPRG8VKFhW8Ai3PJaXoSmfDJjrgsXOU
# DLYfShDRW7ww6ny83zvEFLVGyMqalN0UWvR/O/Q+EThxzF+Cb0eg+bsA+zByFtRW
# gC5nk5XkLzVogsLRlv4cuogAIVfidNmUuEgn+YUE9qDx4aqmdUgVrTmBY7jqHGws
# AXhy5PbUvClZECRZ7eebI5IdQGXno6UHC3i+Gtqb0nntJRAcNG6UPD03zUrHhjXC
# /E4VdL8IqhQzoQXw0AKjiCRiLKwWf2wmjDtqPujo974WW+OMhfNEGRNVQTDq0b+E
# qC3dd80ePcNbyToPyn0R9BMdFu1M3PgO4Du0BWfQUlnaNAsBDCj2Z2pEEZf3jOaU
# ggTTRdQOfiCvOLiopKRczNoIk1f+uQqGSHISLjyRN4cKdp9DsQ03sCl5ajz/L9nC
# GcUEreibP+4EeCNU0mydIGJZvk5oR5lC9faXpfdjhePQJck6rIZAYEpyyN5IBWvE
# 5C+LobYZq0yMxlKvZ9KzQ0PJroht/kIr7UPdPnRlYrWNg9bmcpiYyXfP8xuHsH3R
# bZxPKWgtCLCaRrFV08eyUwABNpkETQtVDQmH8vz7/XvjNUQ6sDszkNMeMGHs
# SIG # End signature block