Microsoft.PowerApps.AuthModule.psm1
$local:ErrorActionPreference = "Stop" <# If(Get-Module -ListAvailable -Name (Join-Path (Split-Path $script:MyInvocation.MyCommand.Path) "Microsoft.PowerApps.RestClientModule.psm1")) { Write-Host "Module loaded" } else { Import-Module (Join-Path (Split-Path $script:MyInvocation.MyCommand.Path) "Microsoft.PowerApps.RestClientModule.psm1") -NoClobber #-Force } #> #[Reflection.Assembly]::LoadFile("$(Split-Path $script:MyInvocation.MyCommand.Path)\Microsoft.IdentityModel.Clients.ActiveDirectory.dll") | Out-Null #[Reflection.Assembly]::LoadFile("$(Split-Path $script:MyInvocation.MyCommand.Path)\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll") | Out-Null function Get-JwtTokenClaims { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string]$JwtToken ) $tokenSplit = $JwtToken.Split(".") $claimsSegment = $tokenSplit[1].Replace(" ", "+").Replace("-", "+"); $mod = $claimsSegment.Length % 4 if ($mod -gt 0) { $paddingCount = 4 - $mod; for ($i = 0; $i -lt $paddingCount; $i++) { $claimsSegment += "=" } } $decodedClaimsSegment = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($claimsSegment)) return ConvertFrom-Json $decodedClaimsSegment } function Add-PowerAppsAccount { [CmdletBinding()] param ( [string] $Audience = "https://management.azure.com/", [Parameter(Mandatory = $false)] [ValidateSet("prod","preview","tip1", "tip2", "usgov", "usgovhigh")] [string]$Endpoint = "prod", [string]$Username = $null, [SecureString]$Password = $null ) $authContext = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext("https://login.windows.net/common"); $redirectUri = New-Object System.Uri("urn:ietf:wg:oauth:2.0:oob"); if ($Username -ne $null -and $Password -ne $null) { $credential = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.UserCredential($Username, $Password) $authResult = $authContext.AcquireToken($Audience, "1950a258-227b-4e31-a9cf-717495945fc2", $credential); } else { $authResult = $authContext.AcquireToken($Audience, "1950a258-227b-4e31-a9cf-717495945fc2", $redirectUri, 1); } $claims = Get-JwtTokenClaims -JwtToken $authResult.IdToken $global:currentSession = @{ loggedIn = $true; idToken = $authResult.IdToken; upn = $claims.upn; tenantId = $claims.tid; userId = $claims.oid; refreshToken = $authResult.RefreshToken; expiresOn = (Get-Date).AddHours(8); resourceTokens = @{ $Audience = @{ accessToken = $authResult.AccessToken; expiresOn = $authResult.ExpiresOn; } }; selectedEnvironment = "~default"; flowEndpoint = switch ($Endpoint) { "prod" { "api.flow.microsoft.com" } "usgov" { "gov.api.flow.microsoft.us" } "usgovhigh" { "high.api.flow.microsoft.us" } "preview" { "preview.api.flow.microsoft.com" } "tip1" { "tip1.api.flow.microsoft.com"} "tip2" { "tip2.api.flow.microsoft.com" } default { throw "Unsupported endpoint '$Endpoint'"} }; powerAppsEndpoint = switch ($Endpoint) { "prod" { "api.powerapps.com" } "usgov" { "gov.api.powerapps.us" } "usgovhigh" { "high.api.powerapps.us" } "preview" { "preview.api.powerapps.com" } "tip1" { "tip1.api.powerapps.com"} "tip2" { "tip2.api.powerapps.com" } default { throw "Unsupported endpoint '$Endpoint'"} }; bapEndpoint = switch ($Endpoint) { "prod" { "api.bap.microsoft.com" } "usgov" { "gov.api.bap.microsoft.us" } "usgovhigh" { "high.api.bap.microsoft.us" } "preview" { "preview.api.bap.microsoft.com" } "tip1" { "tip1.api.bap.microsoft.com"} "tip2" { "tip2.api.bap.microsoft.com" } default { throw "Unsupported endpoint '$Endpoint'"} }; graphEndpoint = switch ($Endpoint) { "prod" { "graph.windows.net" } "usgov" { "graph.windows.net" } "usgovhigh" { "graph.windows.net" } "preview" { "graph.windows.net" } "tip1" { "graph.windows.net"} "tip2" { "graph.windows.net" } default { throw "Unsupported endpoint '$Endpoint'"} }; cdsOneEndpoint = switch ($Endpoint) { "prod" { "api.cds.microsoft.com" } "usgov" { "gov.api.cds.microsoft.us" } "usgovhigh" { "high.api.cds.microsoft.us" } "preview" { "preview.api.cds.microsoft.com" } "tip1" { "tip1.api.cds.microsoft.com"} "tip2" { "tip2.api.cds.microsoft.com" } default { throw "Unsupported endpoint '$Endpoint'"} }; }; } function Test-PowerAppsAccount { [CmdletBinding()] param ( ) if (-not $global:currentSession) { Add-PowerAppsAccount } } function Remove-PowerAppsAccount { [CmdletBinding()] param ( ) if ($global:currentSession -ne $null -and $global:currentSession.upn -ne $null) { Write-Verbose "Logging out $($global:currentSession.upn)" } else { Write-Verbose "No user logged in" } $global:currentSession = @{ loggedIn = $false; }; } function Get-JwtToken { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string] $Audience ) if ($global:currentSession -eq $null) { $global:currentSession = @{ loggedIn = $false; }; } if ($global:currentSession.loggedIn -eq $false -or $global:currentSession.expiresOn -lt (Get-Date)) { Write-Verbose "No user logged in. Signing the user in before acquiring token." Add-PowerAppsAccount -Audience $Audience } if ($global:currentSession.resourceTokens[$Audience] -eq $null -or ` $global:currentSession.resourceTokens[$Audience].accessToken -eq $null -or ` $global:currentSession.resourceTokens[$Audience].expiresOn -eq $null -or ` $global:currentSession.resourceTokens[$Audience].expiresOn -lt (Get-Date)) { Write-Verbose "Token for $Audience is either missing or expired. Acquiring a new one." $tenantId = $global:currentSession.tenantId $authContext = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext("https://login.windows.net/$tenantId"); $refreshTokenResult = $authContext.AcquireTokenByRefreshToken($global:currentSession.refreshToken, "1950a258-227b-4e31-a9cf-717495945fc2", $Audience) $global:currentSession.resourceTokens[$Audience] = @{ accessToken = $refreshTokenResult.AccessToken; expiresOn = $refreshTokenResult.ExpiresOn; } } return $global:currentSession.resourceTokens[$Audience].accessToken; } function Invoke-OAuthDialog { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string] $ConsentLinkUri ) Add-Type -AssemblyName System.Windows.Forms $form = New-Object -TypeName System.Windows.Forms.Form -Property @{ Width=440; Height=640 } $web = New-Object -TypeName System.Windows.Forms.WebBrowser -Property @{ Width=420; Height=600; Url=$ConsentLinkUri } $DocComp = { $Global:uri = $web.Url.AbsoluteUri if ($Global:uri -match "error=[^&]*|code=[^&]*") { $form.Close() } } $web.ScriptErrorsSuppressed = $true $web.Add_DocumentCompleted($DocComp) $form.Controls.Add($web) $form.Add_Shown({$form.Activate()}) $form.ShowDialog() | Out-Null $queryOutput = [System.Web.HttpUtility]::ParseQueryString($web.Url.Query) $output = @{} foreach($key in $queryOutput.Keys) { $output["$key"] = $queryOutput[$key] } return $output } function Get-TenantDetailsFromGraph { <# .SYNOPSIS . .DESCRIPTION The Get-TenantDetailsFromGraph function . Use Get-Help Get-TenantDetailsFromGraph -Examples for more detail. .EXAMPLE Get-TenantDetailsFromGraph . #> param ( [string]$GraphApiVersion = "1.6" ) process { $TenantIdentifier = "myorganization" $route = "https://{graphEndpoint}/{tenantIdentifier}/tenantDetails`?api-version={graphApiVersion}" ` | ReplaceMacro -Macro "{tenantIdentifier}" -Value $TenantIdentifier ` | ReplaceMacro -Macro "{graphApiVersion}" -Value $GraphApiVersion; $graphResponse = InvokeApi -Method GET -Route $route CreateTenantObject -TenantObj $graphResponse.value } } #Returns users or groups from Graph #wrapper on top of https://msdn.microsoft.com/en-us/library/azure/ad/graph/api/users-operations & https://msdn.microsoft.com/en-us/library/azure/ad/graph/api/groups-operations function Get-UsersOrGroupsFromGraph( ) { [CmdletBinding(DefaultParameterSetName="Id")] param ( [Parameter(Mandatory = $true, ParameterSetName = "Id")] [string]$ObjectId, [Parameter(Mandatory = $true, ParameterSetName = "Search")] [string]$SearchString, [Parameter(Mandatory = $false, ParameterSetName = "Search")] [Parameter(Mandatory = $false, ParameterSetName = "Id")] [string]$GraphApiVersion = "1.6" ) Process { if (-not [string]::IsNullOrWhiteSpace($ObjectId)) { $userGraphUri = "https://graph.windows.net/myorganization/users/{userId}`?&api-version={graphApiVersion}" ` | ReplaceMacro -Macro "{userId}" -Value $ObjectId ` | ReplaceMacro -Macro "{graphApiVersion}" -Value $GraphApiVersion; $userGraphResponse = InvokeApi -Route $userGraphUri -Method GET If($userGraphResponse.StatusCode -eq $null) { CreateUserObject -UserObj $userGraphResponse } $groupsGraphUri = "https://graph.windows.net/myorganization/groups/{groupId}`?api-version={graphApiVersion}" ` | ReplaceMacro -Macro "{groupId}" -Value $ObjectId ` | ReplaceMacro -Macro "{graphApiVersion}" -Value $GraphApiVersion; $groupGraphResponse = InvokeApi -Route $groupsGraphUri -Method GET If($groupGraphResponse.StatusCode -eq $null) { CreateGroupObject -GroupObj $groupGraphResponse } } else { $userFilter = "startswith(userPrincipalName,'$SearchString') or startswith(displayName,'$SearchString')" $userGraphUri = "https://graph.windows.net/myorganization/users`?`$filter={filter}&api-version={graphApiVersion}" ` | ReplaceMacro -Macro "{filter}" -Value $userFilter ` | ReplaceMacro -Macro "{graphApiVersion}" -Value $GraphApiVersion; $userGraphResponse = InvokeApi -Route $userGraphUri -Method GET foreach($user in $userGraphResponse.value) { CreateUserObject -UserObj $user } $groupFilter = "startswith(displayName,'$SearchString')" $groupsGraphUri = "https://graph.windows.net/myorganization/groups`?`$filter={filter}&api-version={graphApiVersion}" ` | ReplaceMacro -Macro "{filter}" -Value $groupFilter ` | ReplaceMacro -Macro "{graphApiVersion}" -Value $GraphApiVersion; $groupsGraphResponse = Invoke-Request -Uri $groupsGraphUri -Method GET -ParseContent -ThrowOnFailure foreach($group in $groupsGraphResponse.value) { CreateGroupObject -GroupObj $group } } } } function CreateUserObject { param ( [Parameter(Mandatory = $true)] [object]$UserObj ) return New-Object -TypeName PSObject ` | Add-Member -PassThru -MemberType NoteProperty -Name ObjectType -Value $UserObj.objectType ` | Add-Member -PassThru -MemberType NoteProperty -Name ObjectId -Value $UserObj.objectId ` | Add-Member -PassThru -MemberType NoteProperty -Name UserPrincipalName -Value $UserObj.userPrincipalName ` | Add-Member -PassThru -MemberType NoteProperty -Name Mail -Value $UserObj.mail ` | Add-Member -PassThru -MemberType NoteProperty -Name DisplayName -Value $UserObj.displayName ` | Add-Member -PassThru -MemberType NoteProperty -Name AssignedLicenses -Value $UserObj.assignedLicenses ` | Add-Member -PassThru -MemberType NoteProperty -Name AssignedPlans -Value $UserObj.assignedLicenses ` | Add-Member -PassThru -MemberType NoteProperty -Name Internal -Value $UserObj; } function CreateGroupObject { param ( [Parameter(Mandatory = $true)] [object]$GroupObj ) return New-Object -TypeName PSObject ` | Add-Member -PassThru -MemberType NoteProperty -Name ObjectType -Value $GroupObj.objectType ` | Add-Member -PassThru -MemberType NoteProperty -Name Objectd -Value $GroupObj.objectId ` | Add-Member -PassThru -MemberType NoteProperty -Name Mail -Value $GroupObj.mail ` | Add-Member -PassThru -MemberType NoteProperty -Name DisplayName -Value $GroupObj.displayName ` | Add-Member -PassThru -MemberType NoteProperty -Name Internal -Value $GroupObj; } function CreateTenantObject { param ( [Parameter(Mandatory = $true)] [object]$TenantObj ) return New-Object -TypeName PSObject ` | Add-Member -PassThru -MemberType NoteProperty -Name ObjectType -Value $TenantObj.objectType ` | Add-Member -PassThru -MemberType NoteProperty -Name TenantId -Value $TenantObj.objectId ` | Add-Member -PassThru -MemberType NoteProperty -Name Country -Value $TenantObj.countryLetterCode ` | Add-Member -PassThru -MemberType NoteProperty -Name Language -Value $TenantObj.preferredLanguage ` | Add-Member -PassThru -MemberType NoteProperty -Name DisplayName -Value $TenantObj.displayName ` | Add-Member -PassThru -MemberType NoteProperty -Name Domains -Value $TenantObj.verifiedDomains ` | Add-Member -PassThru -MemberType NoteProperty -Name Internal -Value $TenantObj; } # SIG # Begin signature block # MIIdhAYJKoZIhvcNAQcCoIIddTCCHXECAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUa3+EPu8V910cfer9dzWEKtyx # FoCgghhuMIIE2jCCA8KgAwIBAgITMwAAAUBR3eGS/3LZigAAAAABQDANBgkqhkiG # 9w0BAQUFADB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G # A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSEw # HwYDVQQDExhNaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EwHhcNMTkxMDIzMjMxNzE2 # WhcNMjEwMTIxMjMxNzE2WjCByjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAldBMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # LTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEm # MCQGA1UECxMdVGhhbGVzIFRTUyBFU046RDA4Mi00QkZELUVFQkExJTAjBgNVBAMT # HE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggEiMA0GCSqGSIb3DQEBAQUA # A4IBDwAwggEKAoIBAQCscF2UvgYrttZCacjwuM0P8dG9w4e+HCcHSFQeeI/wOm+3 # 0Ri2hS4uNzAoK9MWplth6P1HxnhOqGdfDC1USuaRZFwbqqQTQSy8qEFucsaE4yoq # LEEFX6q5UArGE7FEc0N4S6tX/Y2nBs/7FKbSL7ISFjSNPORi9njTkDLAmePe5CG7 # xd8NBPxMrERlhPi7rdWFJhaa4oNdM3cd/dHmGgSQrNtyT4tDbXdKbbNCsglGllek # aW/+zdmgHOGVRPaqyHRvAGZ08ffUdzPuHu4cZedUaD3u8ZL8m1KLxuxaYPHtcNIM # SwMi1c3Fxn/+K7HuA5aK9dduVMPbFa0fkosXlpJ7AgMBAAGjggEJMIIBBTAdBgNV # HQ4EFgQUtTUyQ/FL75p3SN/rUv0DML5GBJIwHwYDVR0jBBgwFoAUIzT42VJGcArt # QPt2+7MrsMM1sw8wVAYDVR0fBE0wSzBJoEegRYZDaHR0cDovL2NybC5taWNyb3Nv # ZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljcm9zb2Z0VGltZVN0YW1wUENBLmNy # bDBYBggrBgEFBQcBAQRMMEowSAYIKwYBBQUHMAKGPGh0dHA6Ly93d3cubWljcm9z # b2Z0LmNvbS9wa2kvY2VydHMvTWljcm9zb2Z0VGltZVN0YW1wUENBLmNydDATBgNV # HSUEDDAKBggrBgEFBQcDCDANBgkqhkiG9w0BAQUFAAOCAQEAd+ebPjg/kVtGHhxO # laE+b6X05eeGPrFcFEl1h6Mom1v6DMZ0yIPvteBBLtFW7c5+RHIccvH/XnUZjclH # aWccA0Wh91N7zIXvYQj8axX98htwcBnkITCfmXvFOhGy5HrhB//bDXA0MryCIo0P # Qe2Aar3myXtrHlwJUCsYDNtvGoexAjnIH1ibRDCkswHIuH781uUx2ZXdJelV0lsM # CnxOdGRtOmEqM/8G8+dFopOdnFtzjGE6raf38oZo7vL0Er5A3Dvmjll8EzvD/lpN # 7TFZ5/4C5IR+FZfL5AKxvN6atbX9KPYsfCQyGzNsP9WWfBRcTaqneJCe19RS7y+q # 1wIilzCCBgMwggProAMCAQICEzMAAAFSm0CfUFaZdYgAAAAAAVIwDQYJKoZIhvcN # AQELBQAwfjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV # BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYG # A1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMTAeFw0xOTA1MDIy # MTM3NDZaFw0yMDA1MDIyMTM3NDZaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX # YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg # Q29ycG9yYXRpb24xHjAcBgNVBAMTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjCCASIw # DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALGnidP2p+707XSInJ7BhceU35YS # Hv02iiv2eEzp6dQ1sJuFf29L7xz/d4hVrNJidUFOovNbvFY3VsJwi0NuwTMoQTYG # zBK6fsn3EJovBwYcoWv6pZSXPuGH1FyaNhKQ4Y3Js5+uCPeybQNK2gryWPATJRV5 # F8wfH0T/sJr84SrZxcFPcvR9WeUSR9qXfXQQUIsOjYGsTfk0ZGMb7+edmKoqoSHm # VY2TfclXz8jR8hxQqssSZQau/QKALvDZyOZsGEEgn7QrNKdJaKeBeiX/eJR0EHLh # BA0fvruRga6cl5jTxGMcwiCMkJ0CgQz7aZe/WmFpXuP4zd03Nn9x2zPegN8CAwEA # AaOCAYIwggF+MB8GA1UdJQQYMBYGCisGAQQBgjdMCAEGCCsGAQUFBwMDMB0GA1Ud # DgQWBBTd/wDDWxbvZZwnZuj9BJgbNWtFhzBUBgNVHREETTBLpEkwRzEtMCsGA1UE # CxMkTWljcm9zb2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMRYwFAYDVQQF # Ew0yMzAwMTIrNDU0MTM2MB8GA1UdIwQYMBaAFEhuZOVQBdOCqhc3NyK1bajKdQKV # MFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lv # cHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0wNy0wOC5jcmwwYQYIKwYBBQUH # AQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtp # b3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0wNy0wOC5jcnQwDAYDVR0T # AQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAlPBE5oe+iBeCLFaPKO8t+JGCojZA # rwagbbd6oCA2NftX9Z1RYFuzRogLeTj1x3TN2r4kkiaLFxfpQ5OnPYtk8VKHbeXT # 8yjfnAbsldTGA7RT7l2ttCG3nGgyXWfv9NDiIpyYBhIA/FOrmUWehXb58B6WNUDs # 7jezOOzstHT0PTAUfDNlyj+ITweVqSXbdlPsWcHkB9TaHB+/zvLerdrmoWK6BLKQ # gukrT++qeURUHQoB1BXNhQtD9Th4USOeKzcmz1SsC+0iEizbrjjlGPdQ/pTgaA6O # BCieVED6YOWyHvzAVIZsBIi8r5+Q41SG+PwHxkc2fhMV+dy35rRm55jh/ppE/Gvx # t41JQqftBb8VCafjbZsTsp+epadywfu9s2Eb3b2mtUc+xprsnbaL3DIePubSgBNc # n5iN/KgQC13n83IhhoThS7SPUSG5hSjlVokmcxpMRHSpfz79hlFasU3+F6mzjVc1 # WOIBrClsBrR9RrdH1E0GM+IGczSC80+iszh2xXZUnwaW4hA6smU6+4Ks5gMaKsad # p06ZDbXA8GgYUJakrno/HOWIqzLk02YBdwTHtBv29SNjVaVi2t3A5dOnE3iyXMiG # 3r0FpmUsiMtVbCoh+42uEco0Mz0r6/u+Csht/uXn/rrmDSCNAIMI3pDmzG621MAQ # q0l5L+mSkj4Ntn8wggYHMIID76ADAgECAgphFmg0AAAAAAAcMA0GCSqGSIb3DQEB # BQUAMF8xEzARBgoJkiaJk/IsZAEZFgNjb20xGTAXBgoJkiaJk/IsZAEZFgltaWNy # b3NvZnQxLTArBgNVBAMTJE1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhv # cml0eTAeFw0wNzA0MDMxMjUzMDlaFw0yMTA0MDMxMzAzMDlaMHcxCzAJBgNVBAYT # AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xITAfBgNVBAMTGE1pY3Jvc29mdCBU # aW1lLVN0YW1wIFBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ+h # bLHf20iSKnxrLhnhveLjxZlRI1Ctzt0YTiQP7tGn0UytdDAgEesH1VSVFUmUG0KS # rphcMCbaAGvoe73siQcP9w4EmPCJzB/LMySHnfL0Zxws/HvniB3q506jocEjU8qN # +kXPCdBer9CwQgSi+aZsk2fXKNxGU7CG0OUoRi4nrIZPVVIM5AMs+2qQkDBuh/NZ # MJ36ftaXs+ghl3740hPzCLdTbVK0RZCfSABKR2YRJylmqJfk0waBSqL5hKcRRxQJ # gp+E7VV4/gGaHVAIhQAQMEbtt94jRrvELVSfrx54QTF3zJvfO4OToWECtR0Nsfz3 # m7IBziJLVP/5BcPCIAsCAwEAAaOCAaswggGnMA8GA1UdEwEB/wQFMAMBAf8wHQYD # VR0OBBYEFCM0+NlSRnAK7UD7dvuzK7DDNbMPMAsGA1UdDwQEAwIBhjAQBgkrBgEE # AYI3FQEEAwIBADCBmAYDVR0jBIGQMIGNgBQOrIJgQFYnl+UlE/wq4QpTlVnkpKFj # pGEwXzETMBEGCgmSJomT8ixkARkWA2NvbTEZMBcGCgmSJomT8ixkARkWCW1pY3Jv # c29mdDEtMCsGA1UEAxMkTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9y # aXR5ghB5rRahSqClrUxzWPQHEy5lMFAGA1UdHwRJMEcwRaBDoEGGP2h0dHA6Ly9j # cmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL21pY3Jvc29mdHJvb3Rj # ZXJ0LmNybDBUBggrBgEFBQcBAQRIMEYwRAYIKwYBBQUHMAKGOGh0dHA6Ly93d3cu # bWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljcm9zb2Z0Um9vdENlcnQuY3J0MBMG # A1UdJQQMMAoGCCsGAQUFBwMIMA0GCSqGSIb3DQEBBQUAA4ICAQAQl4rDXANENt3p # tK132855UU0BsS50cVttDBOrzr57j7gu1BKijG1iuFcCy04gE1CZ3XpA4le7r1ia # HOEdAYasu3jyi9DsOwHu4r6PCgXIjUji8FMV3U+rkuTnjWrVgMHmlPIGL4UD6ZEq # JCJw+/b85HiZLg33B+JwvBhOnY5rCnKVuKE5nGctxVEO6mJcPxaYiyA/4gcaMvnM # MUp2MT0rcgvI6nA9/4UKE9/CCmGO8Ne4F+tOi3/FNSteo7/rvH0LQnvUU3Ih7jDK # u3hlXFsBFwoUDtLaFJj1PLlmWLMtL+f5hYbMUVbonXCUbKw5TNT2eb+qGHpiKe+i # myk0BncaYsk9Hm0fgvALxyy7z0Oz5fnsfbXjpKh0NbhOxXEjEiZ2CzxSjHFaRkMU # vLOzsE1nyJ9C/4B5IYCeFTBm6EISXhrIniIh0EPpK+m79EjMLNTYMoBMJipIJF9a # 6lbvpt6Znco6b72BJ3QGEe52Ib+bgsEnVLaxaj2JoXZhtG6hE6a/qkfwEm/9ijJs # sv7fUciMI8lmvZ0dhxJkAj0tr1mPuOQh5bWwymO0eFQF1EEuUKyUsKV4q7OglnUa # 2ZKHE3UiLzKoCG6gW4wlv6DvhMoh1useT8ma7kng9wFlb4kLfchpyOZu6qeXzjEp # /w7FW1zYTRuh2Povnj8uVRZryROj/TCCB3owggVioAMCAQICCmEOkNIAAAAAAAMw # DQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n # dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9y # YXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhv # cml0eSAyMDExMB4XDTExMDcwODIwNTkwOVoXDTI2MDcwODIxMDkwOVowfjELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z # b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMTCCAiIwDQYJKoZIhvcNAQEBBQADggIP # ADCCAgoCggIBAKvw+nIQHC6t2G6qghBNNLrytlghn0IbKmvpWlCquAY4GgRJun/D # DB7dN2vGEtgL8DjCmQawyDnVARQxQtOJDXlkh36UYCRsr55JnOloXtLfm1OyCizD # r9mpK656Ca/XllnKYBoF6WZ26DJSJhIv56sIUM+zRLdd2MQuA3WraPPLbfM6XKEW # 9Ea64DhkrG5kNXimoGMPLdNAk/jj3gcN1Vx5pUkp5w2+oBN3vpQ97/vjK1oQH01W # KKJ6cuASOrdJXtjt7UORg9l7snuGG9k+sYxd6IlPhBryoS9Z5JA7La4zWMW3Pv4y # 07MDPbGyr5I4ftKdgCz1TlaRITUlwzluZH9TupwPrRkjhMv0ugOGjfdf8NBSv4yU # h7zAIXQlXxgotswnKDglmDlKNs98sZKuHCOnqWbsYR9q4ShJnV+I4iVd0yFLPlLE # tVc/JAPw0XpbL9Uj43BdD1FGd7P4AOG8rAKCX9vAFbO9G9RVS+c5oQ/pI0m8GLhE # fEXkwcNyeuBy5yTfv0aZxe/CHFfbg43sTUkwp6uO3+xbn6/83bBm4sGXgXvt1u1L # 50kppxMopqd9Z4DmimJ4X7IvhNdXnFy/dygo8e1twyiPLI9AN0/B4YVEicQJTMXU # pUMvdJX3bvh4IFgsE11glZo+TzOE2rCIF96eTvSWsLxGoGyY0uDWiIwLAgMBAAGj # ggHtMIIB6TAQBgkrBgEEAYI3FQEEAwIBADAdBgNVHQ4EFgQUSG5k5VAF04KqFzc3 # IrVtqMp1ApUwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGG # MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUci06AjGQQ7kUBU7h6qfHMdEj # iTQwWgYDVR0fBFMwUTBPoE2gS4ZJaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3Br # aS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0MjAxMV8yMDExXzAzXzIyLmNybDBe # BggrBgEFBQcBAQRSMFAwTgYIKwYBBQUHMAKGQmh0dHA6Ly93d3cubWljcm9zb2Z0 # LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0MjAxMV8yMDExXzAzXzIyLmNydDCB # nwYDVR0gBIGXMIGUMIGRBgkrBgEEAYI3LgMwgYMwPwYIKwYBBQUHAgEWM2h0dHA6 # Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvZG9jcy9wcmltYXJ5Y3BzLmh0bTBA # BggrBgEFBQcCAjA0HjIgHQBMAGUAZwBhAGwAXwBwAG8AbABpAGMAeQBfAHMAdABh # AHQAZQBtAGUAbgB0AC4gHTANBgkqhkiG9w0BAQsFAAOCAgEAZ/KGpZjgVHkaLtPY # dGcimwuWEeFjkplCln3SeQyQwWVfLiw++MNy0W2D/r4/6ArKO79HqaPzadtjvyI1 # pZddZYSQfYtGUFXYDJJ80hpLHPM8QotS0LD9a+M+By4pm+Y9G6XUtR13lDni6WTJ # RD14eiPzE32mkHSDjfTLJgJGKsKKELukqQUMm+1o+mgulaAqPyprWEljHwlpblqY # luSD9MCP80Yr3vw70L01724lruWvJ+3Q3fMOr5kol5hNDj0L8giJ1h/DMhji8MUt # zluetEk5CsYKwsatruWy2dsViFFFWDgycScaf7H0J/jeLDogaZiyWYlobm+nt3TD # QAUGpgEqKD6CPxNNZgvAs0314Y9/HG8VfUWnduVAKmWjw11SYobDHWM2l4bf2vP4 # 8hahmifhzaWX0O5dY0HjWwechz4GdwbRBrF1HxS+YWG18NzGGwS+30HHDiju3mUv # 7Jf2oVyW2ADWoUa9WfOXpQlLSBCZgB/QACnFsZulP0V3HjXG0qKin3p6IvpIlR+r # +0cjgPWe+L9rt0uX4ut1eBrs6jeZeRhL/9azI2h15q/6/IvrC4DqaTuv/DDtBEyO # 3991bWORPdGdVk5Pv4BXIqF4ETIheu9BCrE/+6jMpF3BoYibV3FWTkhFwELJm3Zb # CoBIa/15n8G9bW1qyVJzEw16UM0xggSAMIIEfAIBATCBlTB+MQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQgQ29k # ZSBTaWduaW5nIFBDQSAyMDExAhMzAAABUptAn1BWmXWIAAAAAAFSMAkGBSsOAwIa # BQCggZQwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFHsKDsyWr/pJ/F9KcsJWiQH+ # F2UoMDQGCisGAQQBgjcCAQwxJjAkoBKAEABUAGUAcwB0AFMAaQBnAG6hDoAMaHR0 # cDovL3Rlc3QgMA0GCSqGSIb3DQEBAQUABIIBAF9fBglbfLw4XDP/A4G8BkVt09jd # xT1c9uqsxp3WhN939X49SrU+dOYlHrugySmE3qUlg91KWG4+reL+5CuODXoHAuQd # 1+j+tUCwNWaLPI63GGmMvgGl9rZtPRwcm9t3ZRFsWxg21e1ErXhvsJpprvJWjgb9 # OIsNetQ9sItDSLjaeXYjlaYRNkvoAADvdEyj1y2tUXLL4MGE8sWYnlF3qeHGLXai # rKErqZwC3r4pl81GHKzY6H0AyDzQfajDWB59D8MYYc+5FBO6J02/PgtTSVIWAfxl # BbB8nXI45J+oSbjSkbYIXeJXDYpUUyYUlPDdOWdX497AqLLKn9QQ+JLQq8mhggIo # MIICJAYJKoZIhvcNAQkGMYICFTCCAhECAQEwgY4wdzELMAkGA1UEBhMCVVMxEzAR # BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p # Y3Jvc29mdCBDb3Jwb3JhdGlvbjEhMB8GA1UEAxMYTWljcm9zb2Z0IFRpbWUtU3Rh # bXAgUENBAhMzAAABQFHd4ZL/ctmKAAAAAAFAMAkGBSsOAwIaBQCgXTAYBgkqhkiG # 9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0yMDAxMTQwMTA4MTha # MCMGCSqGSIb3DQEJBDEWBBTYps79wkF2mx8sxx8tQx02xwaWGDANBgkqhkiG9w0B # AQUFAASCAQCmJUupwB+4siGuwWrbMvXYZWUTrghni1MigBn8ciRxxPJTGNXEEuac # u+Cxc0uPMu3CTRHLJqknR2A9u+/YRfnnOh3g/q6nxwyLikFDtnnPb6DXkJkcdSBE # cX/D1jYkJ2p+bw82i8zoEKNDtWuo0c/QqSboP7nXV9gCiTVMuMP1RAX1Q3a76bt/ # QINTc6aphe1bVw4apq3c2akCKQBOfTWj5Lkc/pXS/utIGb2WbbGmklGRPSdZbsMJ # Q2Ap+f3LH8kOXvr2WywN1E1cyvuZ3VQiBPiwtCSNGukuLm22WrDbqdks8DStmxZi # M55uAsmfx6JeGJH2EXizon5H2jUbM5T3 # SIG # End signature block |