Oauth2Toolkit.psm1

function ConvertFrom-Timestamp
{
    param(
        [Parameter(Mandatory = $true)]
        [int]$Timestamp
    )

    $utc = (Get-Date 01.01.1970) + ([System.TimeSpan]::fromseconds($Timestamp))
    $datetime = [datetime]::SpecifyKind($utc, 'Utc').ToLocalTime()

    $datetime
}

function New-AccessToken
{
    param(
        [string]$Tenant,
        [Parameter(ParameterSetName='ClientCredential')]
        [pscredential]$Client,
        [Parameter(ParameterSetName='ClientExplicit')]
        [string]$ClientId,
        [Parameter(ParameterSetName='ClientExplicit')]
        [string]$ClientSecret,
        [string]$RefreshToken
    )

    $authUrl = "https://login.microsoftonline.com/{0}/oauth2/token" -f $Tenant
    $parameters = @{
        grant_type = "refresh_token"
        client_secret= $ClientSecret
        refresh_token = $RefreshToken
        client_id = $ClientId
    }

    $response = Invoke-RestMethod -Uri $authUrl -Method Post -Body $parameters
    $expires = ConvertFrom-Timestamp -Timestamp $response.expires_on
    
    $result = [PSCustomObject]@{
        Expires = $expires
        AccessToken = $response.access_token
    }

    $result
}

function Invoke-OnBehalfOfFlow
{
    # https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-oauth2-on-behalf-of-flow
    param(
        [Parameter(Mandatory = $true)]
        [string]$Tenant,
        [Parameter(Mandatory = $true)]
        [string]$ClientId,
        [Parameter(Mandatory = $true)]
        [string]$clientSecret,
        [Parameter(Mandatory = $true)]
        [string]$AccessToken,
        [Parameter()]
        [string]$Resource = "https://graph.microsoft.com"
    )    

    $payload = @{
        grant_type = "urn:ietf:params:oauth:grant-type:jwt-bearer"
        requested_token_use = "on_behalf_of"
        scope = "openid"
        assertion = $AccessToken
        resource = $Resource
        client_id = $ClientId
        client_secret = $clientSecret
        
    }
    $response = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$Tenant/oauth2/token" -Body $payload

    $response
}

function ConvertTo-AuthorizationHeaders
{
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline)]
        $response
    )

    $headers = @{
        'Content-Type'  = 'application/json'
        'Authorization' = "Bearer " + $response.access_token
        'ExpiresOn'     = (ConvertFrom-Timestamp -Timestamp $response.expires_on)
    }

    $headers
}

function New-OnBehalfOfAccessToken 
{
    param(
        [Parameter(Mandatory = $true)]
        [string]$Tenant,
        [Parameter(Mandatory = $true)]
        [string]$ClientId,
        [Parameter(Mandatory = $true)]
        [string]$clientSecret,
        [Parameter(Mandatory = $true)]
        [string]$AccessToken,
        [Parameter()]
        [string]$ResourcePrincial = "https://graph.microsoft.com"
    )

    #Import-AdalLibrary

    $authority = "https://login.microsoftonline.com/$Tenant"
    $authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
    $clientCredentials = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential" -ArgumentList ($ClientId, $ClientSecret)
    $userAssertion  = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserAssertion" -ArgumentList ($AccessToken)
    $authResult = $authContext.AcquireTokenAsync($ResourcePrincial, $clientCredentials, $userAssertion)
    
    if ($authResult.Result.AccessToken) {
        # Creating header for Authorization token
        $authHeader = @{
            'Content-Type'  = 'application/json'
            'Authorization' = "Bearer " + $authResult.Result.AccessToken
            'ExpiresOn'     = $authResult.Result.ExpiresOn
        }
    
        $authHeader
    }
    elseif ($authResult.Exception) {
        throw "An error occured getting access token: $($authResult.Exception.InnerException)"
    }
}

# Oauth password flow

function Add-Win32HelperType
{
    $nativeHelperTypeDefinition =
    @"
    using System;
    using System.Runtime.InteropServices;
    public static class WinApi
        {
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool SetForegroundWindow(IntPtr hWnd);
        public static bool SetForeground(IntPtr windowHandle)
        {
           return SetForegroundWindow(windowHandle);
        }
 
        [DllImport("user32.dll", SetLastError=true)]
        public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
        public static void KillProcess(IntPtr windowHandle)
        {
            uint pid;
            GetWindowThreadProcessId(windowHandle, out pid);
 
            System.Diagnostics.Process p = System.Diagnostics.Process.GetProcessById((int)pid);
            if(p != null) p.Kill();
        }
    }
"@

    if(-not ([System.Management.Automation.PSTypeName] "WinApi").Type)
    {
        Add-Type -TypeDefinition $nativeHelperTypeDefinition
    }
}

function Invoke-BrowserLogin
{
    param
    (
        [Parameter(HelpMessage='Authorization URL', Mandatory = $true)]
        [ValidateNotNull()]
        [string]$AuthorizationUrl,
        [Parameter(HelpMessage='Redirect URI', Mandatory = $true)]
        [ValidateNotNull()]
        $RedirectUrl,
        $ExpectedSuccessParameter = "code"
    )

    Add-Type -AssemblyName System.Web
    Add-Win32HelperType

    # Create an Internet Explorer Window for the Login Experience
    $ie = New-Object -ComObject InternetExplorer.Application
    $ie.Width = 550
    $ie.Height = 600
    $ie.AddressBar = $false
    $ie.ToolBar = $false
    $ie.StatusBar = $false
    $ie.visible = $true
    $ie.navigate($authorizationUrl)
    $handle = $ie.HWND
    $winForeground = [WinApi]::SetForeground($handle)
    # Grab the window
    $wind = (New-Object -ComObject Shell.Application).Windows() | Where-Object { $_.HWND -eq $handle }
    $sleepCounter = 0

    while ($ie.Busy)
    {
        Start-Sleep -Milliseconds 50
        $sleepCounter++

        if ($sleepCounter -eq 100)
        {
            throw "Unable to connect to $authorizationUrl, timed out waiting for page."
        }
    }

    try
    {
        while($true)
        {
            $urls = @()
            $urls += $wind.LocationUrl | Where-Object { $_ -and $_ -match "(^https?://.+)|(^ftp://)" }
            if (-not $urls) 
            {
                # "No urls found, refreshing window"
                $wind = (New-Object -ComObject Shell.Application).Windows() | Where-Object { $_.HWND -eq $handle }
                if (-not $wind)
                {
                    throw "Could not find IE window with handle: $handle"
                }
            }
            
            foreach ($url in $urls)
            {
                $urlPrefix = "{0}?{1}=" -f $RedirectUrl, $ExpectedSuccessParameter
                if (($url).StartsWith($urlPrefix))
                {
                    $code = $url -replace (".*$($ExpectedSuccessParameter)=") -replace ("&.*") # | Out-File $outputAuth

                    return $code
                }
                elseif (($url).StartsWith($RedirectUrl + "?error="))
                {
                    $error = [System.Web.HttpUtility]::UrlDecode(($a.LocationUrl) -replace (".*error="))

                    throw $error
                }
            }
        }
    }
    finally
    {
        [WinApi]::KillProcess($handle)
    }
}

function Invoke-CodeGrantFlow
{
    param(
        [Parameter(HelpMessage='Redirect Uri', Mandatory = $true)]
        [ValidateNotNull()]
        [string]$RedirectUrl,
        [Parameter(Mandatory = $true)]
        [string]$ClientId,
        [Parameter(Mandatory = $true)]
        [string]$ClientSecret,
        [Parameter(Mandatory = $true)]
        [string]$Tenant,
        [Parameter(Mandatory = $true)]
        [string]$Resource,
        [bool]$AlwaysPrompt = $false
    )

    $authorizationUrl = ("https://login.microsoftonline.com/{0}/oauth2/authorize?response_type=code&client_id={1}&redirect_uri={2}&resource={3}" -f $Tenant, $ClientId, $RedirectUrl, $Resource)
    if($AlwaysPrompt)
    {
        $authorizationUrl += "&prompt=select_account"
    }

    $code = Invoke-BrowserLogin -AuthorizationUrl $authorizationUrl -RedirectUrl $RedirectUrl

    if(-not $code)
    {
        throw "Code Grant Flow failed"
    }

    $url = "https://login.microsoftonline.com/{0}/oauth2/token" -f $Tenant
    $fields = @{
        grant_type = "authorization_code"
        client_id = $clientId
        code = $code
        redirect_uri = $RedirectUrl
        resource = $Resource
        client_secret = $clientSecret
    }
    $response = Invoke-RestMethod -Method Post -Uri $url -Body $fields

    $response
}

function Invoke-DeviceCodeFlow
{
    param(
        [Parameter(Mandatory = $true)]
        [string]$ClientId,
        [Parameter(Mandatory = $true)]
        [string]$ClientSecret,
        [Parameter(Mandatory = $true)]
        [string]$Tenant,
        [Parameter(Mandatory = $true)]
        [string]$Resource
    )

    $postParams = @{
        resource = $Resource
        client_id = $ClientId
    }
    $url = "https://login.microsoftonline.com/{0}/oauth2/devicecode" -f $Tenant
    $response = Invoke-RestMethod -Method Post -Uri $url -Body $postParams

    if(-not $response.device_code)
    {
        throw "Device Code Flow failed"
    }

    $tokenResponse = $null
    $maxDate = (Get-Date).AddSeconds($response.expires_in)

    $url = "https://login.microsoftonline.com/{0}/oauth2/token" -f $Tenant
    $tokenParams = @{
        grant_type = "device_code"
        resource = $Resource
        client_id = "$ClientId"
        code = $response.device_code
    }
    while (!$tokenResponse -and (Get-Date) -lt $maxDate)
    {
        try
        {
            $tokenResponse = Invoke-RestMethod -Method Post -Uri $url -Body $tokenParams
        } 
        catch [System.Net.WebException], [Microsoft.PowerShell.Commands.HttpResponseException]
        {
            if ($_.Exception.Response -eq $null)
            {
                throw
            }
        
            $errBody = $null
            if($PSEdition -eq "Core") 
            {
                $errBody = ConvertFrom-Json ($_.ErrorDetails | Select-Object -ExpandProperty Message)
            }
            else
            {
                $result = $_.Exception.Response.GetResponseStream()
                $reader = New-Object System.IO.StreamReader($result)
                $reader.BaseStream.Position = 0
                $errBody = ConvertFrom-Json $reader.ReadToEnd();
            }
        
            if(-not $errBody -or $errBody.Error -ne "authorization_pending")
            {
                throw
            }

            Start-Sleep($response.interval)
            Write-Host -NoNewline "."
        }
    }

    $tokenResponse
}

function Invoke-ResourceOwnerPasswordGrantFlow
{
    param(
        [Parameter(Mandatory = $true)]
        [string]$ClientId,
        [Parameter(Mandatory = $true)]
        [pscredential]$UserCredentials,
        [Parameter(Mandatory = $true)]
        [string]$Tenant,
        [Parameter(Mandatory = $true)]
        [string]$Resource
    )

    $btsr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($UserCredentials.Password)
    $plainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($btsr)
    [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($btsr)

    $payload = @{
        grant_type = "password"
        client_id = $ClientId
        resource = $Resource
        username = $UserCredentials.UserName
        password = $plainPassword
        
    }
    $response = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$Tenant/oauth2/token" -Body $payload

    $response
}

function Invoke-AdminConsentForApplication
{
    param(
        [Parameter(Mandatory = $true)]
        [string]$ClientId,
        [Parameter(Mandatory = $true)]
        [string]$Tenant,
        [Parameter(Mandatory = $true)]
        [string]$RedirectUrl
    )

    $consentUrl = "https://login.microsoftonline.com/{0}/adminconsent?client_id={1}&state=12345&redirect_uri={2}" -f $Tenant, $ClientId, $RedirectUrl
    
    $response = Invoke-BrowserLogin -AuthorizationUrl $consentUrl -RedirectUrl $RedirectUrl -ExpectedSuccessParameter "admin_consent"

    if($response -ne "True")
    {
        throw "Admin consent failed"
    }
}

# SIG # Begin signature block
# MIIgQgYJKoZIhvcNAQcCoIIgMzCCIC8CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUGRlUBGjo9KQnOegfgfYVAfzs
# I9ygghtxMIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0B
# AQUFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD
# VQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVk
# IElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQsw
# CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
# ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg
# Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg
# +XESpa7cJpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lT
# XDGEKvYPmDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5
# a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g
# 0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1
# roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf
# GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0G
# A1UdDgQWBBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLL
# gjEtUYunpyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3
# cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmr
# EthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+
# fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5Q
# Z7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu
# 838fYxAe+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw
# 8jCCBTAwggQYoAMCAQICEAQJGBtf1btmdVNDtW+VUAgwDQYJKoZIhvcNAQELBQAw
# ZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQ
# d3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBS
# b290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowcjELMAkGA1UE
# BhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2lj
# ZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUg
# U2lnbmluZyBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPjTsxx/
# DhGvZ3cH0wsxSRnP0PtFmbE620T1f+Wondsy13Hqdp0FLreP+pJDwKX5idQ3Gde2
# qvCchqXYJawOeSg6funRZ9PG+yknx9N7I5TkkSOWkHeC+aGEI2YSVDNQdLEoJrsk
# acLCUvIUZ4qJRdQtoaPpiCwgla4cSocI3wz14k1gGL6qxLKucDFmM3E+rHCiq85/
# 6XzLkqHlOzEcz+ryCuRXu0q16XTmK/5sy350OTYNkO/ktU6kqepqCquE86xnTrXE
# 94zRICUj6whkPlKWwfIPEvTFjg/BougsUfdzvL2FsWKDc0GCB+Q4i2pzINAPZHM8
# np+mM6n9Gd8lk9ECAwEAAaOCAc0wggHJMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYD
# VR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMDMHkGCCsGAQUFBwEBBG0w
# azAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsGAQUF
# BzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVk
# SURSb290Q0EuY3J0MIGBBgNVHR8EejB4MDqgOKA2hjRodHRwOi8vY3JsNC5kaWdp
# Y2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMDqgOKA2hjRodHRw
# Oi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3Js
# ME8GA1UdIARIMEYwOAYKYIZIAYb9bAACBDAqMCgGCCsGAQUFBwIBFhxodHRwczov
# L3d3dy5kaWdpY2VydC5jb20vQ1BTMAoGCGCGSAGG/WwDMB0GA1UdDgQWBBRaxLl7
# KgqjpepxA8Bg+S32ZXUOWDAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823I
# DzANBgkqhkiG9w0BAQsFAAOCAQEAPuwNWiSz8yLRFcgsfCUpdqgdXRwtOhrE7zBh
# 134LYP3DPQ/Er4v97yrfIFU3sOH20ZJ1D1G0bqWOWuJeJIFOEKTuP3GOYw4TS63X
# X0R58zYUBor3nEZOXP+QsRsHDpEV+7qvtVHCjSSuJMbHJyqhKSgaOnEoAjwukaPA
# JRHinBRHoXpoaK+bp1wgXNlxsQyPu6j4xRJon89Ay0BEpRPw5mQMJQhCMrI2iiQC
# /i9yfhzXSUWW6Fkd6fp0ZGuy62ZD2rOwjNXpDd32ASDOmTFjPQgaGLOBm0/GkxAG
# /AeB+ova+YJJ92JuoVP6EpQYhS6SkepobEQysmah5xikmmRR7zCCBT8wggQnoAMC
# AQICEARqN2Coe6LGwkVyLzQpADwwDQYJKoZIhvcNAQELBQAwcjELMAkGA1UEBhMC
# VVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0
# LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUgU2ln
# bmluZyBDQTAeFw0xOTA5MTYwMDAwMDBaFw0yMDEwMDcxMjAwMDBaMHwxCzAJBgNV
# BAYTAkNaMQ8wDQYDVQQIEwZQcmFndWUxEjAQBgNVBAcTCVByYWd1ZSAxMDEjMCEG
# A1UEChMaVUFNIEN6ZWNoIFJlcHVibGljLCBzLnIuby4xIzAhBgNVBAMTGlVBTSBD
# emVjaCBSZXB1YmxpYywgcy5yLm8uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
# CgKCAQEAuq5cfBDFBQvMe+Xj1O1mxsCcE0T+JLQtqOZE/AsHCVzMpxCCDHtCSwct
# HHsPVtrKMeFyaLDek3VQWrCMgiErkZtr6j2RIwo33+CwM9aLcjvKaVx1Qjkdlfs8
# zXi+JUKCHNbN2irY5b8wK8DUmMG3kp8iqJWITTATsKdabJUC3qP7KJhuRs6rYoPK
# cjDhn3PBNE9lcDs0vgdp0pRDqYcepk3NTEu2pHW8xG54+/rYNbyy0dbqakTvo+lU
# 6B4Jrob1RFgefz6g6X6HoVOsujalPEacPdbhQmzWuAijzD39r6nVxrOSWaWUDv20
# obphl85MHz7Ezuzi+DqJaZ1ymfymXwIDAQABo4IBxTCCAcEwHwYDVR0jBBgwFoAU
# WsS5eyoKo6XqcQPAYPkt9mV1DlgwHQYDVR0OBBYEFL+kJzPjqsjuCW4WG06ed+gr
# Q0A7MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzB3BgNVHR8E
# cDBuMDWgM6Axhi9odHRwOi8vY3JsMy5kaWdpY2VydC5jb20vc2hhMi1hc3N1cmVk
# LWNzLWcxLmNybDA1oDOgMYYvaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NoYTIt
# YXNzdXJlZC1jcy1nMS5jcmwwTAYDVR0gBEUwQzA3BglghkgBhv1sAwEwKjAoBggr
# BgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAIBgZngQwBBAEw
# gYQGCCsGAQUFBwEBBHgwdjAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNl
# cnQuY29tME4GCCsGAQUFBzAChkJodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20v
# RGlnaUNlcnRTSEEyQXNzdXJlZElEQ29kZVNpZ25pbmdDQS5jcnQwDAYDVR0TAQH/
# BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAqnAvqpnss3KmMh8DhGngUFOSCJI5kDFZ
# 6kR+G5sQqbwhQKiLLKYT/pXopDm1yob9o48Ef9aAb0ILYayq2bSN+sBdf8iX9j44
# EyVOE5mjwQj570ETuGPXrrfE7s7zBYDvLGsuk4AqnWF2HyR9eIsc/8MNl/nXQHsg
# fYh8xsX8HzOpNppP0iY72OUDsKzwwIpH7UtwPNxuDEEanU0KMPUcMStwWXl8V9SZ
# Q/Y35n8HpRu8dzeu8N/jh5o/s2+CVMTuF6xat2p1pxtTWeeDJxYlPReq5IugY8e7
# iJCI7YAvDMsOt7f+OOR22VIXuEO/3tL0zWe9ChLXN1VmNhgcur4y7jCCBmowggVS
# oAMCAQICEAMBmgI6/1ixa9bV6uYX8GYwDQYJKoZIhvcNAQEFBQAwYjELMAkGA1UE
# BhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2lj
# ZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgQXNzdXJlZCBJRCBDQS0xMB4XDTE0
# MTAyMjAwMDAwMFoXDTI0MTAyMjAwMDAwMFowRzELMAkGA1UEBhMCVVMxETAPBgNV
# BAoTCERpZ2lDZXJ0MSUwIwYDVQQDExxEaWdpQ2VydCBUaW1lc3RhbXAgUmVzcG9u
# ZGVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo2Rd/Hyz4II14OD2
# xirmSXU7zG7gU6mfH2RZ5nxrf2uMnVX4kuOe1VpjWwJJUNmDzm9m7t3LhelfpfnU
# h3SIRDsZyeX1kZ/GFDmsJOqoSyyRicxeKPRktlC39RKzc5YKZ6O+YZ+u8/0SeHUO
# plsU/UUjjoZEVX0YhgWMVYd5SEb3yg6Np95OX+Koti1ZAmGIYXIYaLm4fO7m5zQv
# MXeBMB+7NgGN7yfj95rwTDFkjePr+hmHqH7P7IwMNlt6wXq4eMfJBi5GEMiN6ARg
# 27xzdPpO2P6qQPGyznBGg+naQKFZOtkVCVeZVjCT88lhzNAIzGvsYkKRrALA76Tw
# iRGPdwIDAQABo4IDNTCCAzEwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAw
# FgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwggG/BgNVHSAEggG2MIIBsjCCAaEGCWCG
# SAGG/WwHATCCAZIwKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNv
# bS9DUFMwggFkBggrBgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYA
# IAB0AGgAaQBzACAAQwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkA
# dAB1AHQAZQBzACAAYQBjAGMAZQBwAHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAA
# RABpAGcAaQBDAGUAcgB0ACAAQwBQAC8AQwBQAFMAIABhAG4AZAAgAHQAaABlACAA
# UgBlAGwAeQBpAG4AZwAgAFAAYQByAHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAA
# dwBoAGkAYwBoACAAbABpAG0AaQB0ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4A
# ZAAgAGEAcgBlACAAaQBuAGMAbwByAHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkA
# bgAgAGIAeQAgAHIAZQBmAGUAcgBlAG4AYwBlAC4wCwYJYIZIAYb9bAMVMB8GA1Ud
# IwQYMBaAFBUAEisTmLKZB+0e36K+Vw0rZwLNMB0GA1UdDgQWBBRhWk0ktkkynUoq
# eRqDS/QeicHKfTB9BgNVHR8EdjB0MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2Vy
# dC5jb20vRGlnaUNlcnRBc3N1cmVkSURDQS0xLmNybDA4oDagNIYyaHR0cDovL2Ny
# bDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEQ0EtMS5jcmwwdwYIKwYB
# BQUHAQEEazBpMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20w
# QQYIKwYBBQUHMAKGNWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2Vy
# dEFzc3VyZWRJRENBLTEuY3J0MA0GCSqGSIb3DQEBBQUAA4IBAQCdJX4bM02yJoFc
# m4bOIyAPgIfliP//sdRqLDHtOhcZcRfNqRu8WhY5AJ3jbITkWkD73gYBjDf6m7Gd
# JH7+IKRXrVu3mrBgJuppVyFdNC8fcbCDlBkFazWQEKB7l8f2P+fiEUGmvWLZ8Cc9
# OB0obzpSCfDscGLTYkuw4HOmksDTjjHYL+NtFxMG7uQDthSr849Dp3GdId0UyhVd
# kkHa+Q+B0Zl0DSbEDn8btfWg8cZ3BigV6diT5VUW8LsKqxzbXEgnZsijiwoc5ZXa
# rsQuWaBh3drzbaJh6YoLbewSGL33VVRAA5Ira8JRwgpIr7DUbuD0FAo6G+OPPcqv
# ao173NhEMIIGzTCCBbWgAwIBAgIQBv35A5YDreoACus/J7u6GzANBgkqhkiG9w0B
# AQUFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD
# VQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVk
# IElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMjExMTEwMDAwMDAwWjBiMQsw
# CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
# ZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBBc3N1cmVkIElEIENBLTEw
# ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDogi2Z+crCQpWlgHNAcNKe
# VlRcqcTSQQaPyTP8TUWRXIGf7Syc+BZZ3561JBXCmLm0d0ncicQK2q/LXmvtrbBx
# MevPOkAMRk2T7It6NggDqww0/hhJgv7HxzFIgHweog+SDlDJxofrNj/YMMP/pvf7
# os1vcyP+rFYFkPAyIRaJxnCI+QWXfaPHQ90C6Ds97bFBo+0/vtuVSMTuHrPyvAwr
# mdDGXRJCgeGDboJzPyZLFJCuWWYKxI2+0s4Grq2Eb0iEm09AufFM8q+Y+/bOQF1c
# 9qjxL6/siSLyaxhlscFzrdfx2M8eCnRcQrhofrfVdwonVnwPYqQ/MhRglf0HBKIJ
# AgMBAAGjggN6MIIDdjAOBgNVHQ8BAf8EBAMCAYYwOwYDVR0lBDQwMgYIKwYBBQUH
# AwEGCCsGAQUFBwMCBggrBgEFBQcDAwYIKwYBBQUHAwQGCCsGAQUFBwMIMIIB0gYD
# VR0gBIIByTCCAcUwggG0BgpghkgBhv1sAAEEMIIBpDA6BggrBgEFBQcCARYuaHR0
# cDovL3d3dy5kaWdpY2VydC5jb20vc3NsLWNwcy1yZXBvc2l0b3J5Lmh0bTCCAWQG
# CCsGAQUFBwICMIIBVh6CAVIAQQBuAHkAIAB1AHMAZQAgAG8AZgAgAHQAaABpAHMA
# IABDAGUAcgB0AGkAZgBpAGMAYQB0AGUAIABjAG8AbgBzAHQAaQB0AHUAdABlAHMA
# IABhAGMAYwBlAHAAdABhAG4AYwBlACAAbwBmACAAdABoAGUAIABEAGkAZwBpAEMA
# ZQByAHQAIABDAFAALwBDAFAAUwAgAGEAbgBkACAAdABoAGUAIABSAGUAbAB5AGkA
# bgBnACAAUABhAHIAdAB5ACAAQQBnAHIAZQBlAG0AZQBuAHQAIAB3AGgAaQBjAGgA
# IABsAGkAbQBpAHQAIABsAGkAYQBiAGkAbABpAHQAeQAgAGEAbgBkACAAYQByAGUA
# IABpAG4AYwBvAHIAcABvAHIAYQB0AGUAZAAgAGgAZQByAGUAaQBuACAAYgB5ACAA
# cgBlAGYAZQByAGUAbgBjAGUALjALBglghkgBhv1sAxUwEgYDVR0TAQH/BAgwBgEB
# /wIBADB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp
# Z2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNlcnQu
# Y29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDCBgQYDVR0fBHoweDA6oDig
# NoY0aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9v
# dENBLmNybDA6oDigNoY0aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0
# QXNzdXJlZElEUm9vdENBLmNybDAdBgNVHQ4EFgQUFQASKxOYspkH7R7for5XDStn
# As0wHwYDVR0jBBgwFoAUReuir/SSy4IxLVGLp6chnfNtyA8wDQYJKoZIhvcNAQEF
# BQADggEBAEZQPsm3KCSnOB22WymvUs9S6TFHq1Zce9UNC0Gz7+x1H3Q48rJcYaKc
# lcNQ5IK5I9G6OoZyrTh4rHVdFxc0ckeFlFbR67s2hHfMJKXzBBlVqefj56tizfuL
# LZDCwNK1lL1eT7EF0g49GqkUW6aGMWKoqDPkmzmnxPXOHXh2lCVz5Cqrz5x2S+1f
# wksW5EtwTACJHvzFebxMElf+X+EevAJdqP77BzhPDcZdkbkPZ0XN1oPt55INjbFp
# jE/7WeAjD9KqrgB87pxCDs+R1ye3Fu4Pw718CqDuLAhVhSK46xgaTfwqIa1JMYNH
# lXdx3LEbS0scEJx3FMGdTy9alQgpECYxggQ7MIIENwIBATCBhjByMQswCQYDVQQG
# EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
# cnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQgQ29kZSBT
# aWduaW5nIENBAhAEajdgqHuixsJFci80KQA8MAkGBSsOAwIaBQCgeDAYBgorBgEE
# AYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwG
# CisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMCMGCSqGSIb3DQEJBDEWBBQ2deey
# g6Ryc4meADhr9z5qMLWBZTANBgkqhkiG9w0BAQEFAASCAQAq9UXEHGqC2VYB/4LM
# JEnWalVtI4+JWWrW83HGxd+DUImbohbZzfDmeA21yARGfYJD06nMTu5LH9sX3xkO
# 1BHt5RGEJzP2AFQ6zCXuGjxMHFkeZzyIW1jYqFStCtDDv/30TTKlLikxIzFnjLV1
# 4w96I/H+Jf5izxeTmMDmLL/8qcpf3gcbajltLoJ0TPjuGq/WsAERhlabHreU1eFQ
# /XCHZwgxO8G0O14v1YW6ZOpRkavqf7QlaicSe1Iutj+mHh3QrYN32CYurPlq0JLa
# bM0AoZJ8QSmNaadfsNJeWTQJ3dTO77abm6UuZCXXBwOzO/ysEbmMNp4DWxOHBJ+K
# DzX7oYICDzCCAgsGCSqGSIb3DQEJBjGCAfwwggH4AgEBMHYwYjELMAkGA1UEBhMC
# VVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0
# LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgQXNzdXJlZCBJRCBDQS0xAhADAZoCOv9Y
# sWvW1ermF/BmMAkGBSsOAwIaBQCgXTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcB
# MBwGCSqGSIb3DQEJBTEPFw0xOTEwMDQyMDUyMDVaMCMGCSqGSIb3DQEJBDEWBBSz
# I72IewhXE34oML7NtjqPRYHzLjANBgkqhkiG9w0BAQEFAASCAQBVj8fVucitg6MG
# XM3gwJg6CP5EkiVRyr8zoftUjSm2QPwYeDhMaFIzH8lyMu1JssKHUSaCUXseFEaW
# NEgGKO8KIZLJrYweji4ofUVKNqVhCtm1HIy5fsDKZlDBBVxf/rXpL08qN1Oa18wC
# CiQPqM5NrPDL6bh245x/ozzh7UZLRFPuFbqiuA/HTya0OIyVk9YkHgDvH2O2DS1v
# UExqKoCM3RBeldm1wMTNxDoo7VcpjbfbloS5wwJyyinqMwTCWsXCrFjlG2xp+I2w
# +Nrz5w4kMuZAMoSe8nXVE9ZP4f3hVBYHhy0cvMgEXZZ8th2qLJKAvtJiqDcD19c/
# RcFfpB8C
# SIG # End signature block