gOAuth.psm1
#!/usr/bin/env pwsh <# Copyright 2020 Google LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. #> Add-Type -TypeDefinition @" public enum GoogleOAuthToken { Access, Refresh, Id } "@ $DefaultClientSecret = @" { "installed": { "client_id" : "32555940559.apps.googleusercontent.com", "client_secret" : "ZmssLNjJy2998hD4CTg2ejr2", "project_id" : "powershell", "auth_uri" : "https://accounts.google.com/o/oauth2/auth", "token_uri" : "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url" : "https://www.googleapis.com/oauth2/v1/certs", "redirect_uris" : [ "urn:ietf:wg:oauth:2.0:oob", "http://localhost" ] } } "@ $BaseScopes = @( 'https://www.googleapis.com/auth/accounts.reauth', 'https://www.googleapis.com/auth/userinfo.email' ) $DefaultScopes = @( 'https://www.googleapis.com/auth/appengine.admin', 'https://www.googleapis.com/auth/compute', 'https://www.googleapis.com/auth/cloud-platform' ) | Sort-Object Function ConvertFrom-gOAuthUnixTimestamp { Param( [Int32] $Timestamp, [switch] $UTC ) $UTC.IsPresent ? [System.TimeZone]::CurrentTimeZone.ToUniversalTime([datetime]::UnixEpoch.AddSeconds($Timestamp)) : [System.TimeZone]::CurrentTimeZone.ToLocalTime([datetime]::UnixEpoch.AddSeconds($Timestamp)) } function Get-gOAuthDecodeJwt { [CmdletBinding()] param ( [Parameter(Mandatory)] [string] $ProjectId, [string] $Token = ( Get-gOAuthToken -Projectid $Projectid -Token Id -Verbose:$False ) ) If ( -not $Token ) { throw 'ID token is empty' } $p = $Token.Split('.')[1].replace('-', '+').replace('_', '/') switch ($p.Length % 4) { 1 { $p = $p.Substring(0, $p.Length - 1) } 2 { $p = $p + "==" } 3 { $p = $p + "=" } } $t = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($p)) | ConvertFrom-Json $t.iat = ConvertFrom-gOAuthUnixTimestamp -Timestamp $t.iat $t.exp = ConvertFrom-gOAuthUnixTimestamp -Timestamp $t.exp If ( $VerbosePreference ) { $t | ConvertTo-Json | Write-Verbose -Verbose:$VerbosePreference } $t } Function Get-gOAuthToken { [cmdletbinding()] Param( [string] $ProjectId = $(throw [System.ArgumentException]"ProjectId is required"), [GoogleOAuthToken] $Token, [switch] $All ) Process { $t = If ( $All.IsPresent ) { [GoogleOAuthToken]::GetValues([GoogleOAuthToken]) } Else { $Token } $t | % { $authorization = switch ($_.ToString()) { 'Refresh' { Get-gOAuthAuthorization -ProjectId $ProjectId } Default { Get-gOAuthAuthorizationRefreshed -ProjectId $ProjectId } } If ( $VerbosePreference ) { $authorization | ConvertTo-Json -Depth 99 | Write-Verbose -Verbose:$true } $v = $authorization."$($_)_token" if ($VerbosePreference -and $_ -eq [GoogleOAuthToken]::Id ) { [void](Get-gOAuthDecodeJwt -ProjectId $ProjectId -Token $v -Verbose) } $v } } } Function Open-gOAuthUri { param( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [string[]] $Uri ) begin { $allUris = @() } process { $allUris += switch -regex ($Uri) { '^[a-z]+:' { $_ } # protocol scheme present, use as-is default { 'http://' + $_ } # default to http:// } } end { if ($env:OS -eq 'Windows_NT') { # use Start-Process # Note: Start-Process accepts neither pipeline input nor multiple # paths to start. $allUris | ForEach-Object { Start-Process -FilePath $_ } } elseif ((uname) -eq 'Darwin') { # macOS: use native `open` CLI open $allUris } else { $b = '/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe' If ( Test-Path $b ) { $allUris | % { &$b $_ } } else { # Linux: assume that xdg-open is available $allUris | Write-Host xdg-open $allUris } } } } Function Test-gOAuthCommand { Param ( [string] $Command ) $beforePreference = $ErrorActionPreference $ErrorActionPreference = 'stop' try { if (Get-Command $Command) { $True } } Catch { $False } Finally { $ErrorActionPreference = $beforePreference } } Function Start-gOAuthApiConsole { Open-gOAuthUri -Uri https://console.developers.google.com/ } Function Set-gOAuthClientSecret { [CmdletBinding()] param ( [string[]] $Json, $ProjectId ) $value = $Json -join ' ' $value | Write-Verbose -Verbose:$VerbosePreference $p = $ProjectId ?? ( $value | ConvertFrom-Json | % { $_.installed; $_.web } | Select-Object -First 1 | Select-Object -ExpandProperty project_id ) If ( -not $p ) { throw 'ProjectId is missing' } $facility = 'gOAuth-' + $p.ToLowerInvariant() Set-gSecret -Facility $facility -Name Client -Value $Value -Verbose:$VerbosePreference } function Set-gOAuthClientSecretFromFile { [CmdletBinding()] param ( [Parameter( Position = 0, Mandatory = $True, HelpMessage = "Enter file name of downloaded JSON secrets file", ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] [ValidateNotNullOrEmpty()] [Alias("name", "file", "path")] [string[]]$PSPath, $ProjectId ) Process { Set-gOAuthClientSecret -ProjectId $ProjectId -Json (@( Get-Content $PSPath ) -join ' ') } } Function Get-gOAuthClientSecret { [CmdletBinding()] param( [string] $ProjectId ) $value = If ( $ProjectId -and ($ProjectId -ne 'powershell')) { Get-gSecret -Facility ( 'gOAuth-' + $ProjectId.ToLowerInvariant() ) -Name Client -Verbose:$VerbosePreference } Else { $DefaultClientSecret } If ( $Value ) { $value | Write-Verbose -Verbose:$VerbosePreference $value | ConvertFrom-Json | % { $_.installed; $_.web } | Select-Object -First 1 } Else { throw [System.ArgumentException]"Use Set-gOAuthClientSecret to import the credentials downloaded from https://console.developers.google.com/apis/credentials?project=$ProjectId" } } Function Invoke-gOAuthLogin { [cmdletbinding()] Param( [string] $LoginHint = $null, $ProjectId = 'powershell', [string[]] $Scopes = $DefaultScopes, [string[]] $Base = $BaseScopes ) $i = Get-gOAuthClientSecret -ProjectId $ProjectId -Verbose:$VerbosePreference $codeChallenge = [System.Guid]::NewGuid().ToString().Replace('-', '') $r = ( $i.redirect_uris ?? $i.javascript_origins ) | Select-Object -First 1 $requestUri = $i.auth_uri + '?redirect_uri=' + [System.Uri]::EscapeDataString($r) + '&client_id=' + [System.Uri]::EscapeDataString($i.client_id) + '&scope=' + [System.Uri]::EscapeDataString((($Base + $Scopes | Sort-Object) -join ' ')) + '&project_id=' + [System.Uri]::EscapeDataString($i.project_id) + '&code_challenge=' + [System.Uri]::EscapeDataString($codeChallenge) + '&code_challenge_method=plain' + '&prompt=select_account' + '&response_type=code' + '&include_granted_scopes=true' + '&access_type=offline' if ( $LoginHint ) { $requestUri += '&login_hint=' + [System.Uri]::EscapeDataString($LoginHint) } if ( Test-gOAuthCommand Set-Clipboard ) { Set-Clipboard -Value $requestUri } $requestUri | Write-Verbose -Verbose:$VerbosePreference Open-gOAuthUri $requestUri -Verbose:$VerbosePreference $Code = Read-Host -Prompt 'Paste the code from the Webpage here, if the browser does not start, uri is on the clipboard!' $body = @{ code = $Code; client_id = $i.client_id; client_secret = $i.client_secret; redirect_uri = $i.redirect_uris[0]; grant_type = "authorization_code"; code_verifier = $codeChallenge; }; Set-gOAuthAuthorization ` -ProjectId $ProjectId ` -AuthorizationResponse (Invoke-RestMethod -Uri $i.token_uri -Method POST -Body $body -Verbose:$VerbosePreference) } Function Set-gOAuthAuthorization { [CmdletBinding()] Param( [Parameter(Mandatory)] [string] $ProjectId, $AuthorizationResponse ) $value = $AuthorizationResponse | ConvertTo-Json -Depth 99 Set-gSecret -Facility ('gOAuth-' + $ProjectId.ToLowerInvariant()) -Name AuthorizationResponse -Value $value -Verbose:$VerbosePreference Set-gSecret -Facility ('gOAuth-' + $ProjectId.ToLowerInvariant()) -Name AuthorizationRefreshed -Value $value -Verbose:$VerbosePreference } Function Set-gOAuthAuthorizationRefreshed { [CmdletBinding()] Param( [Parameter(Mandatory)] [string] $ProjectId, $AuthorizationResponse ) $value = $AuthorizationResponse | ConvertTo-Json -Depth 99 Set-gSecret -Facility ('gOAuth-' + $ProjectId.ToLowerInvariant()) -Name AuthorizationRefreshed -Value $value -Verbose:$VerbosePreference } Function Get-gOAuthAuthorization { [CmdletBinding()] Param( [Parameter(Mandatory)] [string] $ProjectId ) Get-gSecret -Facility ('gOAuth-' + $ProjectId.ToLowerInvariant()) -Name AuthorizationResponse -Verbose:$VerbosePreference | % { If ( [string]::IsNullOrEmpty($_)) { throw ('Authorization does not exist, please login with project {0}!' -f $ProjectId) } $_ } | ConvertFrom-Json -Depth 99 } Function Get-gOAuthAuthorizationRefreshed { [CmdletBinding()] Param( [Parameter(Mandatory)] [string] $ProjectId ) Get-gSecret -Facility ('gOAuth-' + $ProjectId.ToLowerInvariant()) -Name AuthorizationRefreshed -Verbose:$VerbosePreference | % { If ( [string]::IsNullOrEmpty($_)) { throw ('Authorization does not exist, please login with project {0}!' -f $ProjectId) } $_ } | ConvertFrom-Json -Depth 99 } Function Get-gOAuthAccessToken { [CmdletBinding()] Param( [Parameter(Mandatory)] [string] $ProjectId, [switch] $Refresh = (Get-gOAuthDecodeJwt -ProjectId $ProjectId).exp -lt (Get-Date).AddSeconds(300) ) if ( $Refresh.IsPresent ) { $client = Get-gOAuthClientSecret -Project $ProjectId -Verbose:$VerbosePreference 'Refreshing access token for {0}' -f $client.client_id | Write-Verbose -Verbose:$VerbosePreference $body = @{ client_id = $client.client_id; client_secret = $client.client_secret; grant_type = "refresh_token"; refresh_token = ( Get-gOAuthToken -ProjectId $ProjectId -Token Refresh ) }; Set-gOAuthAuthorizationRefreshed ` -ProjectId $ProjectId ` -AuthorizationResponse (Invoke-RestMethod -Uri $client.token_uri -Method POST -Body $body -Verbose:$VerbosePreference) } Get-gOAuthToken -Projectid $ProjectId -Token Access -Verbose:$VerbosePreference } Function Get-gOAuthAccessTokenSDK { (gcloud auth application-default print-access-token --format json | ConvertFrom-Json).access_token } Function Invoke-gOAuthRestMethod { [cmdletbinding()] Param( [string] $ProjectId = $(throw [System.ArgumentException]"ProjectId is required"), [System.Uri] $Uri = $(throw [System.ArgumentException]"Uri is required"), $RetryCount = 1, $ContentType = 'application/json', $Body = $null, $Method = $null, [switch] $UseGoogleCloudSDK ) $AccessToken = $UseGoogleCloudSDK.IsPresent ? (Get-gOAuthAccessTokenSDK) : (Get-gOAuthAccessToken -ProjectId $ProjectId) $m = $Method ?? ( $Body ? 'Post' : 'Get' ) 'Method: {0}' -f $m | Write-Verbose -Verbose:$VerbosePreference if ( $Body ) { Try { $b = ConvertTo-Json -InputObject $Body -Depth 100 -EscapeHandling Default } catch { throw 'Unable to convert body to JSON' } } do { $Retry = $false try { if ( $Body ) { If ( $VerbosePreference ) { $b | Write-Verbose -Verbose:$VerbosePreference } Invoke-RestMethod -Headers @{Authorization = "Bearer $AccessToken" } -Uri $Uri -Method $m -ContentType $ContentType -Body $b -Verbose:$VerbosePreference } else { Invoke-RestMethod -Headers @{Authorization = "Bearer $AccessToken" } -Uri $Uri -Method $m -Verbose:$VerbosePreference } } catch [System.Net.WebException], [System.Net.Http.HttpRequestException] { $_ | Write-Verbose -Verbose:$VerbosePreference if ( $_.Exception.Response.StatusCode -ne 401 ) { throw } $Retry = $RetryCount-- -gt 0 if (-not $Retry ) { throw } $AccessToken = $UseGoogleCloudSDK.IsPresent ? (Get-gOAuthAccessTokenSDK) : (Get-gOAuthAccessToken -ProjectId $ProjectId -Refresh) } } while ( $Retry ) } Function Invoke-gOAuthRestMethodPaged { [cmdletbinding()] Param( [Parameter(Mandatory)] [string] $ProjectId, [Parameter(Mandatory)] [System.Uri] $Uri, $Body = $null, $Method = $null, $Filter, [string] $Item = 'items', [int] $MaxResults = 99, [int] $RetryCount = 1, [string] $ContentType = 'application/json', [switch] $UseGoogleCloudSDK ) Begin { $results = @() $nextPageToken = $false } Process { do { $pagedUri = $Uri.AbsoluteUri + ( $Uri.Query ? '&' : '?' ) + "maxResults=$maxResults" + ($nextPageToken ? ('&pageToken={0}' -f [System.Uri]::EscapeDataString($nextPageToken)) : '') + ($Filter ? ('&filter={0}' -f [System.Uri]::EscapeDataString($Filter)) : '') $pagedUri | Write-Verbose -Verbose:$VerbosePreference $result = Invoke-gOAuthRestMethod ` -ProjectId $ProjectId ` -Uri $pagedUri ` -RetryCount $RetryCount ` -ContentType $ContentType ` -Body $Body ` -Method $Method ` -UseGoogleCloudSDK:$UseGoogleCloudSDK ` -Verbose:$VerbosePreference If ($VerbosePreference) { $result | ConvertTo-Json | Write-Verbose -Verbose:$VerbosePreference } $results += $result.$Item $nextPageToken = $result.nextPageToken 'nextPageToken: {0}' -f $nextPageToken | Write-Verbose -Verbose:$VerbosePreference } while ( $nextPageToken ) } End { $results } } # SIG # Begin signature block # MIIdEQYJKoZIhvcNAQcCoIIdAjCCHP4CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU6Rmn5fzS+2maTefGv6kbJy38 # Fi2gghiiMIIFNTCCAx2gAwIBAgIQT/hgdSzRMK1Ptmol1X/K6zANBgkqhkiG9w0B # AQsFADAOMQwwCgYDVQQDEwNnb2QwIBcNMTYwOTA4MTUxOTE5WhgPMjA1OTExMDIy # MjE2MzNaMA4xDDAKBgNVBAMTA2dvZDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC # AgoCggIBAJKirTyUVPFLWIgo8xg/YiWwYZyKxwqJ/TdOI4sX61Xm8gzxxOxPc7H1 # mqIG0ZSZQ6hSWb/JmGBm9BVctD78dDIEMMxkIfhsZQVF3wPLwc150zFpRFXSxnMR # ivQeULzpS7aNhVhaHxX/H0YSIPUn7MU8vbGQWozheo9gljHTfjcAsHhZ94kuPFI1 # 3p5A5TfQf4AWBb21CSniqoUlfu8iYrUebYQTAvU3Lm55uOy5lVHcwlekFq40plRG # jIPaoZT97L2LBxu+NaP9or8SXSUhKxhHiVAgYSD2trWVVDMuwsyDRmn61ORtMQDT # TMZ9W+HoujUiATMxNDhUawlZNM8fn6d5SirqP97jrzUpZKmnKOHzWGbdz6xjDwm8 # eEKXj770zdfOQhuxh1gxfrUzf5Wa/NwV3Sj40pHfRO0dOj3llIMldpIyo5pMb+3W # yea7FJfBf3KeTf+SQmgS0e1d2OEHKTalkThnUVUpgES2QqmWJ0iL4RgQor7EwajN # g5qsR1QqQX1Q8A9NycxwA/YxT7pQhropEyhddVEjEWOONL5YLH8/4tN5FgrxOMwE # u0+q+hCphttRBFAbEjGexhTjENmNMmgTIHb9jCkIFwTkWqUv1/0g8+UzRmC5vWf9 # G9gSmPACzY/ATZRsUA+Z+EQaiCVU5je0ZMOXV4QpZJ8BxDFfjTujAgMBAAGjgYww # gYkwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF # MAMBAf8wHQYDVR0OBBYEFOnDw0HoRV9Yspg5xBYu0pfyshkUMBAGCSsGAQQBgjcV # AQQDAgECMCMGCSsGAQQBgjcVAgQWBBR85TAn1w27aOhVU80oamRgW9vkajANBgkq # hkiG9w0BAQsFAAOCAgEAPoHPAzHnLZaoQFetJbyK/iZdGCCc8DXGViMbhBsNH6rM # mk97uunVWQ0JZ/vTbfjYFe9tlDrk6HnYholJG8RqaMHcQEST/0iReP6DFXHIMkVX # qy/z54EMgk15NIV1qhPCrORQdz+1HauDta/S0rin1K2jndFhSTddtpr3ky1NLr2G # JPFDP7TZqSsnZCjqoWvp+s2ETtbreg28hh+RF7/lHnesWgMqbhMbBQMeR2D/Q+5B # L58ul1T6QNWzcrhR7LkI15AYjH8WNot9wBS7HM7cYOwhRN/vj1QdFAqX/6+/cGO7 # RqeELL12SzZibkmDsF2vGKxeANxuRMg/3RxMaBpAdnw4loSFW+lFb1rHHcvl7baM # D60iW/zNx+6rccVL/Mvclv20FvsrbWq7gTZjjjKLEzmYcCA9260glnQAdh6coZ9d # RbPmKFmcrHos5n8MSdk2ca/CAKLrKj1b01iYUHVG70/1Yb8tr9OaIB5SLTq23Bqf # ztcxtKNyqV6Jv0o40kxfTUmGqyWzoBgdEzxhxHUYDTOaSLg/igSGzzOnpjWXMKUU # DNBL/+cBXbwECtYQwMHS39bjfDrd9q8R3pmu3rAljXOCyVnjUQznecK2hpxThPCy # EHF2OMTd9Y2X9/DwhIsHt0+F1k1TkiHbpvJeIpiVlR4cDMzHL7ebPbMyKCkrcMIw # ggYmMIIEDqADAgECAhMiAAADPr7I+iou2FbsAAIAAAM+MA0GCSqGSIb3DQEBCwUA # MA4xDDAKBgNVBAMTA2dvZDAeFw0xOTEyMjYyMjI5MTRaFw0zOTEyMjEyMjI5MTRa # MBgxFjAUBgNVBAMTDURhdmlkIEt1YmVsa2EwggEiMA0GCSqGSIb3DQEBAQUAA4IB # DwAwggEKAoIBAQDClmZJB+vLBNeC9nHtVnNDR0BwPYd4PY+gMAKguycRavMNfsBq # v0X5nobE/PBH/F5wwHHXYCYw7CqHLd93xLo9xSzxeOMVZvAoxxh3MtLSW6ljLYYw # 40azaTOd1Geio47FJFIWjNca0hBFBrp/bpNmBUiXjZTXC/fcqGzSMnfB8zhGF1NC # m9bKwzzhQN39mvHsJCUuw4Y7WSN4eF7445tznnptAIYV91cu4gBHVGHebiNCRA9X # s9yBPXfA5aDxteAOEdMiALt6T/iEKi5Y/3bcca2MPaoO2N4UBttpaa71QLJuJy8v # mS7J/x69jPKAy6GxKcyPralTCpYZP+Ny3GQlAgMBAAGjggJxMIICbTA+BgkrBgEE # AYI3FQcEMTAvBicrBgEEAYI3FQiC69dxgsC2J4GdlwiB9vE6hLrhc4EMgqDXS4ak # szsCAWUCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwCwYDVR0PBAQDAgeAMBsGCSsG # AQQBgjcVCgQOMAwwCgYIKwYBBQUHAwMwHQYDVR0OBBYEFFxbLr96KqbNM28qgUFO # zlDF6OHoMB8GA1UdIwQYMBaAFOnDw0HoRV9Yspg5xBYu0pfyshkUMIHJBgNVHR8E # gcEwgb4wgbuggbiggbWGgbJsZGFwOi8vL0NOPWdvZCxDTj1kYzUsQ049Q0RQLENO # PVB1YmxpYyUyMEtleSUyMFNlcnZpY2VzLENOPVNlcnZpY2VzLENOPUNvbmZpZ3Vy # YXRpb24sREM9a2lya2xhbmQsREM9a3ViZWxrYSxEQz1vcmc/Y2VydGlmaWNhdGVS # ZXZvY2F0aW9uTGlzdD9iYXNlP29iamVjdENsYXNzPWNSTERpc3RyaWJ1dGlvblBv # aW50MIHBBggrBgEFBQcBAQSBtDCBsTCBrgYIKwYBBQUHMAKGgaFsZGFwOi8vL0NO # PWdvZCxDTj1BSUEsQ049UHVibGljJTIwS2V5JTIwU2VydmljZXMsQ049U2Vydmlj # ZXMsQ049Q29uZmlndXJhdGlvbixEQz1raXJrbGFuZCxEQz1rdWJlbGthLERDPW9y # Zz9jQUNlcnRpZmljYXRlP2Jhc2U/b2JqZWN0Q2xhc3M9Y2VydGlmaWNhdGlvbkF1 # dGhvcml0eTAcBgNVHREEFTATgRFkYXZpZEBrdWJlbGthLmNvbTANBgkqhkiG9w0B # AQsFAAOCAgEAd3dIoAhEkFVhNMwSOk1ANilSvTAaAX67KPwraPvC7UfNszX7hcRr # h4I8TsZSvhtPNP6nMobnHyLFdXEJRQzY6i9FEwqdH7V+GhAdSnayuSiJIfYtExgo # SmNdtyBhka6s8y/4VBaYbhq5bm6YQFMzH+k9+YbnQgENoJ4NumQ2KU/qY/s814G4 # yJ0lO5AlD1PZ/nnYL5JVF3e90LLWbZSYOP7xfKf+CVQpe+FtujS7EDID/s5MP1Pk # 5geEGT0kOQxbyzjt/vRkm41bvhcQfyxu2mbAjY8MCYJ6EX4ekge2TOBsC3Z+er0i # rCfKnbfacGNj07AdA8HiV3yLQht7KgU3/hW49J7s3mLbTxZn1Uk5U5CCSJpUw+JV # wlV2sI/0jTb4ET8h2K3LtF7l+C3brPi/c/c7kd9vlsk3uZtmlRzTi5d/Aj/83+oq # NVjQUd3TCUFBOXjycK06Ku8e91weLBrHpex1rf09dUE2GbWNlrxRvnUCn6KNg2WW # EREK089DfGtPF6SAfs2GV1A7UOL3rllNbaIIg/3CUx7Wt7usmCKYnEB/tDorqGZP # a0A1P2/BT7M4niJogBBB9eMoEtdfsFr26qb8NQR8CA4OKF84ReIjb1AR7yA4HoOU # Oy5MiYVVVnP0jpneeKeUXNqyMBQ8f9ayz9oHdg5uPyb9IqrllD76PokwggZqMIIF # UqADAgECAhADAZoCOv9YsWvW1ermF/BmMA0GCSqGSIb3DQEBBQUAMGIxCzAJBgNV # BAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdp # Y2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IEFzc3VyZWQgSUQgQ0EtMTAeFw0x # NDEwMjIwMDAwMDBaFw0yNDEwMjIwMDAwMDBaMEcxCzAJBgNVBAYTAlVTMREwDwYD # VQQKEwhEaWdpQ2VydDElMCMGA1UEAxMcRGlnaUNlcnQgVGltZXN0YW1wIFJlc3Bv # bmRlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKNkXfx8s+CCNeDg # 9sYq5kl1O8xu4FOpnx9kWeZ8a39rjJ1V+JLjntVaY1sCSVDZg85vZu7dy4XpX6X5 # 1Id0iEQ7Gcnl9ZGfxhQ5rCTqqEsskYnMXij0ZLZQt/USs3OWCmejvmGfrvP9Enh1 # DqZbFP1FI46GRFV9GIYFjFWHeUhG98oOjafeTl/iqLYtWQJhiGFyGGi5uHzu5uc0 # LzF3gTAfuzYBje8n4/ea8EwxZI3j6/oZh6h+z+yMDDZbesF6uHjHyQYuRhDIjegE # YNu8c3T6Ttj+qkDxss5wRoPp2kChWTrZFQlXmVYwk/PJYczQCMxr7GJCkawCwO+k # 8IkRj3cCAwEAAaOCAzUwggMxMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAA # MBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMIIBvwYDVR0gBIIBtjCCAbIwggGhBglg # hkgBhv1sBwEwggGSMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5j # b20vQ1BTMIIBZAYIKwYBBQUHAgIwggFWHoIBUgBBAG4AeQAgAHUAcwBlACAAbwBm # ACAAdABoAGkAcwAgAEMAZQByAHQAaQBmAGkAYwBhAHQAZQAgAGMAbwBuAHMAdABp # AHQAdQB0AGUAcwAgAGEAYwBjAGUAcAB0AGEAbgBjAGUAIABvAGYAIAB0AGgAZQAg # AEQAaQBnAGkAQwBlAHIAdAAgAEMAUAAvAEMAUABTACAAYQBuAGQAIAB0AGgAZQAg # AFIAZQBsAHkAaQBuAGcAIABQAGEAcgB0AHkAIABBAGcAcgBlAGUAbQBlAG4AdAAg # AHcAaABpAGMAaAAgAGwAaQBtAGkAdAAgAGwAaQBhAGIAaQBsAGkAdAB5ACAAYQBu # AGQAIABhAHIAZQAgAGkAbgBjAG8AcgBwAG8AcgBhAHQAZQBkACAAaABlAHIAZQBp # AG4AIABiAHkAIAByAGUAZgBlAHIAZQBuAGMAZQAuMAsGCWCGSAGG/WwDFTAfBgNV # HSMEGDAWgBQVABIrE5iymQftHt+ivlcNK2cCzTAdBgNVHQ4EFgQUYVpNJLZJMp1K # Knkag0v0HonByn0wfQYDVR0fBHYwdDA4oDagNIYyaHR0cDovL2NybDMuZGlnaWNl # cnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEQ0EtMS5jcmwwOKA2oDSGMmh0dHA6Ly9j # cmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRENBLTEuY3JsMHcGCCsG # AQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29t # MEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNl # cnRBc3N1cmVkSURDQS0xLmNydDANBgkqhkiG9w0BAQUFAAOCAQEAnSV+GzNNsiaB # XJuGziMgD4CH5Yj//7HUaiwx7ToXGXEXzakbvFoWOQCd42yE5FpA+94GAYw3+pux # nSR+/iCkV61bt5qwYCbqaVchXTQvH3Gwg5QZBWs1kBCge5fH9j/n4hFBpr1i2fAn # PTgdKG86Ugnw7HBi02JLsOBzppLA044x2C/jbRcTBu7kA7YUq/OPQ6dxnSHdFMoV # XZJB2vkPgdGZdA0mxA5/G7X1oPHGdwYoFenYk+VVFvC7Cqsc21xIJ2bIo4sKHOWV # 2q7ELlmgYd3a822iYemKC23sEhi991VUQAOSK2vCUcIKSK+w1G7g9BQKOhvjjz3K # r2qNe9zYRDCCBs0wggW1oAMCAQICEAb9+QOWA63qAArrPye7uhswDQYJKoZIhvcN # AQEFBQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcG # A1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJl # ZCBJRCBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTIxMTExMDAwMDAwMFowYjEL # MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 # LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgQXNzdXJlZCBJRCBDQS0x # MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6IItmfnKwkKVpYBzQHDS # nlZUXKnE0kEGj8kz/E1FkVyBn+0snPgWWd+etSQVwpi5tHdJ3InECtqvy15r7a2w # cTHrzzpADEZNk+yLejYIA6sMNP4YSYL+x8cxSIB8HqIPkg5QycaH6zY/2DDD/6b3 # +6LNb3Mj/qxWBZDwMiEWicZwiPkFl32jx0PdAug7Pe2xQaPtP77blUjE7h6z8rwM # K5nQxl0SQoHhg26Ccz8mSxSQrllmCsSNvtLOBq6thG9IhJtPQLnxTPKvmPv2zkBd # XPao8S+v7Iki8msYZbHBc63X8djPHgp0XEK4aH631XcKJ1Z8D2KkPzIUYJX9BwSi # CQIDAQABo4IDejCCA3YwDgYDVR0PAQH/BAQDAgGGMDsGA1UdJQQ0MDIGCCsGAQUF # BwMBBggrBgEFBQcDAgYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCDCCAdIG # A1UdIASCAckwggHFMIIBtAYKYIZIAYb9bAABBDCCAaQwOgYIKwYBBQUHAgEWLmh0 # dHA6Ly93d3cuZGlnaWNlcnQuY29tL3NzbC1jcHMtcmVwb3NpdG9yeS5odG0wggFk # BggrBgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0AGgAaQBz # ACAAQwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1AHQAZQBz # ACAAYQBjAGMAZQBwAHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAARABpAGcAaQBD # AGUAcgB0ACAAQwBQAC8AQwBQAFMAIABhAG4AZAAgAHQAaABlACAAUgBlAGwAeQBp # AG4AZwAgAFAAYQByAHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAAdwBoAGkAYwBo # ACAAbABpAG0AaQB0ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAgAGEAcgBl # ACAAaQBuAGMAbwByAHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAgAGIAeQAg # AHIAZQBmAGUAcgBlAG4AYwBlAC4wCwYJYIZIAYb9bAMVMBIGA1UdEwEB/wQIMAYB # Af8CAQAweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5k # aWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0 # LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwgYEGA1UdHwR6MHgwOqA4 # oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJv # b3RDQS5jcmwwOqA4oDaGNGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2Vy # dEFzc3VyZWRJRFJvb3RDQS5jcmwwHQYDVR0OBBYEFBUAEisTmLKZB+0e36K+Vw0r # ZwLNMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA0GCSqGSIb3DQEB # BQUAA4IBAQBGUD7Jtygkpzgdtlspr1LPUukxR6tWXHvVDQtBs+/sdR90OPKyXGGi # nJXDUOSCuSPRujqGcq04eKx1XRcXNHJHhZRW0eu7NoR3zCSl8wQZVann4+erYs37 # iy2QwsDStZS9Xk+xBdIOPRqpFFumhjFiqKgz5Js5p8T1zh14dpQlc+Qqq8+cdkvt # X8JLFuRLcEwAiR78xXm8TBJX/l/hHrwCXaj++wc4Tw3GXZG5D2dFzdaD7eeSDY2x # aYxP+1ngIw/Sqq4AfO6cQg7PkdcntxbuD8O9fAqg7iwIVYUiuOsYGk38KiGtSTGD # R5V3cdyxG0tLHBCcdxTBnU8vWpUIKRAmMYID2TCCA9UCAQEwJTAOMQwwCgYDVQQD # EwNnb2QCEyIAAAM+vsj6Ki7YVuwAAgAAAz4wCQYFKw4DAhoFAKB4MBgGCisGAQQB # gjcCAQwxCjAIoAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYK # KwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFHon3M+L # lo31bmAJaOvcDa6tRKyRMA0GCSqGSIb3DQEBAQUABIIBABkcnpSCqCLr7Da7gp8y # KalxsXJJOyXpPcmhqGDndbjv+5Z3mSmwqnALPrF4DTNfb4ITBVKBDwp+27n0/0Wq # Mh2ssDo0yT+aNrHjivk3GRCFhtNKHNJbOIboxPByO56K4zzF22XrRWub3wrZfc7Z # wyyfXqsrFIMxdXQnM41gRH5OIlhbyzlLUzunXJDNN6ZAf693ze5RsCTOsVjlqPUF # VxiSWaPY9036XSfA4qSOPXn47dR9khqRDSNyIqXAxsyRlUbn2WuAtNV8ALC7A4t4 # S/V/SksQ7wnrYWKWA41beGN5QAwrYDOP37xFzScK4gZFaTbw4CyCaxjwlXxFy8qc # B6ihggIPMIICCwYJKoZIhvcNAQkGMYIB/DCCAfgCAQEwdjBiMQswCQYDVQQGEwJV # UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu # Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBBc3N1cmVkIElEIENBLTECEAMBmgI6/1ix # a9bV6uYX8GYwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEw # HAYJKoZIhvcNAQkFMQ8XDTIwMDYyNjE2MzEwMFowIwYJKoZIhvcNAQkEMRYEFNYD # pbgDcbusDyAO9YKqM7YJlc01MA0GCSqGSIb3DQEBAQUABIIBAG28ZH6PhmK3bSTP # lGKy6pCiuwCoqWauuSk9JfxUgCfYoqyWqXrLmgL/gqL0qT9NsRL6uIA+BXaLZFIu # gTnSrpso1jZUrNsmUZIHCRz3ZLALhrAoXD7R6lQ4SRXc4vm3T8pZLZnfuL169V/u # VLWfyzmPzpx5K6Pq+qP+f044p+btsnUuPm5qk/ZfTXeP8c0GhOktm+dARMpkIrW5 # wbb2lYZTItp9uFou2RX4jn/SLZCVCEXGomt5OglVTHYGeLCeH9RNvU5HsRHo3vvJ # wnHPBYEd6iF8IpS0zsQK/qwQiLInfvKyT95Sgk/dqmOdXMVR12buXLZn1K/UJIFJ # afNzE5c= # SIG # End signature block |