AzureValidation/AzureADConfiguration.psm1
<#############################################################
# # # Copyright (C) Microsoft Corporation. All rights reserved. # # # #############################################################> $ErrorActionPreference = 'Stop' Import-LocalizedData LocalizedData -BaseDirectory $PSScriptRoot -Filename AzureADConfiguration.Strings.psd1 $AzurePowerShellClientID = "1950a258-227b-4e31-a9cf-717495945fc2" $AzurePowerShellReturnUri = "urn:ietf:wg:oauth:2.0:oob" function Get-Endpoints([string] $CloudARMEndpoint) { <# .Synopsis Builds graph and login endpoints for a given CloudARMEndpoint #> $fullUri = $CloudARMEndpoint.TrimEnd('/')+"/metadata/endpoints?api-version=2015-01-01" Write-AzsReadinessLog -message "Getting Azure Endpoints from $fullUri" -type Info $response = Invoke-RestMethod -Uri $fullUri -ErrorAction Stop -UseBasicParsing -TimeoutSec 30 $EndpointProperties = @{ GraphUri = $response.graphEndpoint LoginUri = $response.authentication.loginEndpoint ManagementServiceUri = $response.authentication.audiences[0] ARMUri = $CloudARMEndpoint } return $EndpointProperties } function Get-AADTenantIds { [OutputType([Hashtable])] [CmdletBinding()] param() $token = Get-AzAccessToken $cToken = ConvertFrom-JwtToken $token.Token $UserInfo = @{ FirstName = $cToken.claims.given_name LastName = $cToken.claims.family_name Account = $cToken.claims.unique_name } Write-AzsReadinessLog -message "Retrieving tenantIds..." -type Info $tenantsResponse = Invoke-RestMethod -Method Get -Uri "$($AzureURIs.ARMUri.TrimEnd('/'))/tenants?api-version=2016-02-01" -Headers @{Authorization = "Bearer $($token.Token)"} -UseBasicParsing -TimeoutSec 30 $tenantIds = [Array]$tenantsResponse.Value.tenantId return @{ UserInfo = $UserInfo TenantIds = $tenantIds } } function Invoke-Graph($method, $uri, $authorization, $body) { $params = @{ Method = $method Uri = "$($uri)?api-version=1.6" Headers = @{ Authorization = $authorization } UseBasicParsing = $true TimeoutSec = 30 ContentType = 'application/json' } if ($body) { $params += @{ Body = $body } } try { return (Invoke-WebRequest @params -ErrorAction Stop).Content | ConvertFrom-Json } catch { if ($_.Exception.Response.StatusCode -eq 'Internal Server Error') { Write-AzsReadinessLog -message ("$LocalizedData.GraphEndpointError" -f $graphUri) -type Error throw ($LocalizedData.GraphEndpointError -f $graphUri) } if ($_.Exception.Response.StatusCode -eq 'Forbidden') { Write-AzsReadinessLog -message ("{0} StatusCode: Forbidden" -f $graphUri) -type Error throw "Forbidden: Login using Connect-AzAccount and specify the target tenant." } else { Write-AzsReadinessLog -message ("{0} throw an exception: {1}" -f $graphUri, $_) -type Error throw $_ } } } function Get-AADTenantDetail { [OutputType([Hashtable])] [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string] $TenantId, [Parameter(Mandatory=$true)] [string] $UserId, [Parameter(Mandatory=$true)] [Hashtable] $AzureURIs ) try { $token = Get-AzAccessToken -resource (Get-AzContext).Environment.GraphEndpointResourceId -TenantId $TenantId } catch { throw $_ } # Validating if the user has required permission $authorization = "Bearer $($token.Token)" $accessTokenObj = ConvertFrom-JwtToken -JwtToken $token.Token $postUri = $azureURIs.GraphUri.TrimEnd('/')+"/$($TenantId)/directoryObjects/$($accessTokenObj.Claims.oid)/getMemberObjects" Write-AzsReadinessLog -message "Retrieving member objects for user." -type Info $roles = Invoke-Graph -method Post -uri $postUri -authorization $authorization -body ( ConvertTo-Json -Depth 1 @{ securityEnabledOnly = $false } ) | Select-Object -ExpandProperty value if( $roles -eq $null ){ return $null } $getUri = $azureURIs.GraphUri.TrimEnd('/')+"/$($TenantId)/directoryRoles" Write-AzsReadinessLog -message "Retrieving administrator role in directory." -type Info $roleOid = Invoke-Graph -method Get -uri $getUri -authorization $authorization | Select-Object -ExpandProperty Value | Where { ($_.displayName -In 'Company Administrator', 'Global Administrator') -or ($_.roleTemplateId -eq '62e90394-69f5-4237-9190-012177145e10') } | Select-Object -ExpandProperty objectId if ($roleOid -notin $roles) { Write-AzsReadinessLog -message "Administrator role not present in user roles." -type Error return $null } Write-AzsReadinessLog -message "Success. Administrator role present in user roles." -type Info $tenantResponse = Invoke-RestMethod -Method Get -Uri "$($AzureURIs.GraphUri.TrimEnd('/'))/myOrganization/tenantDetails?api-version=1.5" -Headers @{Authorization = "Bearer $($token.Token)"} -UseBasicParsing -TimeoutSec 30 return @{ Id = $tenantResponse.Value.objectId DisplayName = $tenantResponse.Value.DisplayName DomainName = ($tenantResponse.Value.VerifiedDomains | Where {$_.default}).name Token = $token.Token } } function Get-AADTenantDetails { <# .SYNOPSIS Resolve Azure Tenant Details .DESCRIPTION Resolve Azure Tenant Details .EXAMPLE Get-AADTenantDetails -AADDirectoryTenantName azurestack.contoso.com .OUTPUTS Hashtable of tenant details .NOTES General notes #> [OutputType([Hashtable])] [CmdletBinding()] param( [Parameter(Mandatory=$false)] [string] $AADDirectoryTenantName ) Write-Progress -Activity $LocalizedData.InfoSigningIn $azureURIs = Get-Endpoints (Get-AzContext).Environment.ResourceManagerUrl $TenantsInfo = Get-AADTenantIds $TenantIds = [Array] $TenantsInfo.TenantIds $tenantDetails = @() # Workaround to add the target tenant Id if specified to the list of "resolved" tenant memberships if ($AADDirectoryTenantName) { Write-AzsReadinessLog -message "Retrieving tenant detail from target tenant $AADDirectoryTenantName" -type Info $targetTenantId = Get-TenantIdFromName -tenantName $AADDirectoryTenantName $tenantDetail = Get-AADTenantDetail -TenantId $targetTenantId -UserId $TenantsInfo.UserInfo.Account -AzureURIs $azureURIs if ($tenantDetail -ne $null) { Write-AzsReadinessLog -message ("Success. Target directory ({0}...{1}) returned result." -f $targetTenantId.split('-')[0],$targetTenantId.split('-')[-1]) -type Info $tenantDetails = $tenantDetail } else { for ($i = 1; $i -le $TenantIds.Count; $i++) { Write-Progress -Activity $LocalizedData.InfoSigningIn -PercentComplete ($i * 100 / ($TenantIds.Count + 1)) $tid = $TenantIds[$i-1] Write-AzsReadinessLog -message ("Retrieving tenant detail from tenant {0}...{1}" -f $tid.split('-')[0],$tid.split('-')[-1]) -type Info $tenantDetail = Get-AADTenantDetail -TenantId $tid -UserId $TenantsInfo.UserInfo.Account -AzureURIs $azureURIs if ($tenantDetail -ne $null) { Write-AzsReadinessLog -message ("Success. Directory ({0}...{1}) returned result." -f $tid.split('-')[0],$tid.split('-')[-1]) -type Info $tenantDetails += $tenantDetail } } } } Write-Progress -Activity $LocalizedData.InfoSigningIn -Completed return @{ UserInfo = $TenantsInfo.UserInfo TenantDetails = $tenantDetails } } <# .Synopsis Decodes a base64-encoded string. #> function ConvertFrom-Base64String { [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateNotNull()] [string] $Base64String ) process { $data = $Base64String.Replace('-','+').Replace('_','/') switch ($data.Length % 4) { 0 { break } 2 { $data += '==' } 3 { $data += '=' } default { Write-Error "Invalid data: '$data'" } } $bytes = [System.Convert]::FromBase64String($data) Write-Output $bytes } } <# .Synopsis Converts a Jwt Token into an object. #> function ConvertFrom-JwtToken { [CmdletBinding()] Param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string] $JwtToken ) function ConvertFrom-RawData([String]$Data) { [System.Text.Encoding]::UTF8.GetString((ConvertFrom-Base64String $Data)) | ConvertFrom-Json | Write-Output } $parts = $JwtToken.Split('.') [pscustomobject]@{ Headers = ConvertFrom-RawData $parts[0] Claims = ConvertFrom-RawData $parts[1] Signature = $parts[2] } | Write-Output } function Get-AzureADTenantDetails { <# .SYNOPSIS Resolve Azure AD Tenant Details .DESCRIPTION Resolve Azure AD Tenant Details .EXAMPLE Get-AzureADTenantDetails -AADDirectoryTenantName $AADDirectoryTenantName .INPUTS AzureEnvironment - string - should be AzureCloud, AzureChinaCloud, AzureGermanCloud or CustomCloud AADAdminCredential - PSCredential - AAD Admin Credential AADDirectoryTenantName - string - AAD tenant Name .OUTPUTS Hashtable of Azure AD tenant details .NOTES General notes #> [OutputType([Hashtable])] [CmdletBinding()] param( [Parameter(Mandatory=$false)] [string] $AADDirectoryTenantName ) $claimsProvider = (Get-AzContext).Environment.Name $azureResult = Get-AADTenantDetails -AADDirectoryTenantName $AADDirectoryTenantName $azureToken = $null #$azureRefreshToken = $null $tenantId = $null $tenantName = $null if ($azureResult.TenantDetails -eq $null -or @($azureResult.TenantDetails).Count -eq 0) { Write-AzsReadinessLog -message ($LocalizedData.AADAccountNotAdmin -f $($azureResult.UserInfo.Account)) -type Error -toScreen } elseif (@($azureResult.TenantDetails).Count -gt 1 -and -not $AADDirectoryTenantName) { Write-AzsReadinessLog -message ($LocalizedData.MoreThanOneTenant -f @($($azureResult.UserInfo.Account), $($azureResult.TenantDetails.DomainName -join ', '))) -type Error -toScreen } elseif (@($azureResult.TenantDetails).Count -ige 1 -and $AADDirectoryTenantName) { $tenantDetail = $azureResult.TenantDetails | Where-Object DomainName -eq $AADDirectoryTenantName if ($tenantDetail) { $azureToken = $tenantDetail.Token #$azureRefreshToken = $tenantDetail.RefreshToken $tenantName = $tenantDetail.DomainName $tenantID = $tenantDetail.ID } else { Write-AzsReadinessLog -message ($LocalizedData.NotAdminOfTenant -f @($($azureResult.UserInfo.Account), $AADDirectoryTenantName, $($azureResult.TenantDetails.DomainName -join ', '))) -type Error -toScreen } } else { $azureToken = $azureResult.TenantDetails.Token #$azureRefreshToken = $azureResult.TenantDetails.RefreshToken $tenantName = $azureResult.TenantDetails.DomainName $tenantID = $azureResult.TenantDetails.ID } $adminSubscriptionOwner = (ConvertFrom-JwtToken $azureToken).Claims.unique_name return @{ UserName = $azureResult.UserInfo.Account Token = $azureToken #RefreshToken = $azureRefreshToken AdminSubscriptionOwner = $adminSubscriptionOwner TenantDirectoryName = $tenantName TenantDirectoryID = $tenantID ClaimsProvider = $claimsProvider } } <# .SYNOPSIS Returns Azure AD directory tenant ID given the login endpoint and the directory tenant name .DESCRIPTION Makes an unauthenticated REST call to the given Azure environment's login endpoint to retrieve directory tenant id .EXAMPLE $tenantId = Get-TenantIdFromName -azureEnvironment "Public Azure" -tenantName "msazurestack.onmicrosoft.com" #> function Get-TenantIdFromName { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [ValidateNotNull()] [string] $tenantName ) $azureURIs = Get-Endpoints (Get-AzContext).Environment.ResourceManagerUrl $uri = "{0}/{1}/.well-known/openid-configuration" -f ($azureURIs.LoginUri).TrimEnd('/'), $tenantName $response = Invoke-RestMethod -Uri $uri -Method Get -UseBasicParsing -TimeoutSec 30 Write-AzsReadinessLog -message "using token_endpoint $($response.token_endpoint) to parse tenant id" -type Info $tenantId = $response.token_endpoint.Split('/')[3] $tenantIdGuid = [guid]::NewGuid() $result = [guid]::TryParse($tenantId, [ref] $tenantIdGuid) if(-not $result) { Write-AzsReadinessLog -message "Error obtaining tenant id from tenant name" -type Error } else { Write-AzsReadinessLog -message "Success. Tenant Name: $tenantName Tenant id: $tenantId" -type Info return $tenantId } } Export-ModuleMember -Function Get-AzureADTenantDetails Export-ModuleMember -Function Get-Endpoints Export-ModuleMember -Function Get-TenantIdFromName Export-ModuleMember -Function ConvertFrom-JwtToken # SIG # Begin signature block # MIIoLQYJKoZIhvcNAQcCoIIoHjCCKBoCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCQHjZZNP6n8+Nn # W6GJs/XSoIb0Nb7TQtXoWxVGvSm0m6CCDXYwggX0MIID3KADAgECAhMzAAADrzBA # DkyjTQVBAAAAAAOvMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjMxMTE2MTkwOTAwWhcNMjQxMTE0MTkwOTAwWjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDOS8s1ra6f0YGtg0OhEaQa/t3Q+q1MEHhWJhqQVuO5amYXQpy8MDPNoJYk+FWA # hePP5LxwcSge5aen+f5Q6WNPd6EDxGzotvVpNi5ve0H97S3F7C/axDfKxyNh21MG # 0W8Sb0vxi/vorcLHOL9i+t2D6yvvDzLlEefUCbQV/zGCBjXGlYJcUj6RAzXyeNAN # xSpKXAGd7Fh+ocGHPPphcD9LQTOJgG7Y7aYztHqBLJiQQ4eAgZNU4ac6+8LnEGAL # go1ydC5BJEuJQjYKbNTy959HrKSu7LO3Ws0w8jw6pYdC1IMpdTkk2puTgY2PDNzB # tLM4evG7FYer3WX+8t1UMYNTAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQURxxxNPIEPGSO8kqz+bgCAQWGXsEw # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwMTgyNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAISxFt/zR2frTFPB45Yd # mhZpB2nNJoOoi+qlgcTlnO4QwlYN1w/vYwbDy/oFJolD5r6FMJd0RGcgEM8q9TgQ # 2OC7gQEmhweVJ7yuKJlQBH7P7Pg5RiqgV3cSonJ+OM4kFHbP3gPLiyzssSQdRuPY # 1mIWoGg9i7Y4ZC8ST7WhpSyc0pns2XsUe1XsIjaUcGu7zd7gg97eCUiLRdVklPmp # XobH9CEAWakRUGNICYN2AgjhRTC4j3KJfqMkU04R6Toyh4/Toswm1uoDcGr5laYn # TfcX3u5WnJqJLhuPe8Uj9kGAOcyo0O1mNwDa+LhFEzB6CB32+wfJMumfr6degvLT # e8x55urQLeTjimBQgS49BSUkhFN7ois3cZyNpnrMca5AZaC7pLI72vuqSsSlLalG # OcZmPHZGYJqZ0BacN274OZ80Q8B11iNokns9Od348bMb5Z4fihxaBWebl8kWEi2O # PvQImOAeq3nt7UWJBzJYLAGEpfasaA3ZQgIcEXdD+uwo6ymMzDY6UamFOfYqYWXk # ntxDGu7ngD2ugKUuccYKJJRiiz+LAUcj90BVcSHRLQop9N8zoALr/1sJuwPrVAtx # HNEgSW+AKBqIxYWM4Ev32l6agSUAezLMbq5f3d8x9qzT031jMDT+sUAoCw0M5wVt # CUQcqINPuYjbS1WgJyZIiEkBMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 # IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg # Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC # CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 # a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr # rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg # OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy # 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 # sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh # dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k # A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB # w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn # Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 # lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w # ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o # ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD # VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa # BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny # bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG # AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV # HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG # AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl # AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb # C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l # hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 # I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 # wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 # STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam # ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa # J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah # XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA # 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt # Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr # /Xmfwb1tbWrJUnMTDXpQzTGCGg0wghoJAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB # BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIHOTO2F8hhF5C4740ulOGRDb # TEHRMKuJ8RZJDNjifeX7MEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # BQAEggEAhb1kiRY7qhj9tk10wmNdeUZbRILIBFlnjVY0iNqBtv4tbZVygKONJcfE # vMK1ibc0+hnjzwSY0Tgzhk3jz+7g9SK7ImP8OkIqWuGPViqtQBmj2KOpSWaYHO42 # Q9vfT1YC++zXSpGluCMexOtvT8JwhVVzy9BMdt9PnU0NpS6N3hqKDqVgUFxZwx59 # n1RMiUtOFFTgIq4UL8eP+o1KxJPCUZtoXUHc+J41cKgln8681n0WX+1aTS48+QkC # zLvvSucujNEb5PT0YoL4zIfLZw49/YVgvTI6fnxIVGcZ9GDqg4v67PQhgsZZxAkC # P84mwe6zzoMYRCNStrj+Wv8pW/igdqGCF5cwgheTBgorBgEEAYI3AwMBMYIXgzCC # F38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq # hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AwQCAQUABCB2wfL9TpeWWtXHbDY0v31k7PEN/A/V8/j3J5K8N98tBgIGZfLgoh8b # GBMyMDI0MDMyNTA3MjY0OS43MzZaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l # cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTQwMC0w # NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg # ghHtMIIHIDCCBQigAwIBAgITMwAAAezgK6SC0JFSgAABAAAB7DANBgkqhkiG9w0B # AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzEyMDYxODQ1 # MzhaFw0yNTAzMDUxODQ1MzhaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z # MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTQwMC0wNUUwLUQ5NDcxJTAjBgNV # BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQCwR/RuCTbgxUWVm/Vdul22uwdEZm0IoAFs6oIr39VK # /ItP80cn+8TmtP67iabB4DmAKJ9GH6dJGhEPJpY4vTKRSOwrRNxVIKoPPeUF3f4V # yHEco/u1QUadlwD132NuZCxbnh6Mi2lLG7pDvszZqMG7S3MCi2bk2nvtGKdeAIL+ # H77gL4r01TSWb7rsE2Jb1P/N6Y/W1CqDi1/Ib3/zRqWXt4zxvdIGcPjS4ZKyQEF3 # SEZAq4XIjiyowPHaqNbZxdf2kWO/ajdfTU85t934CXAinb0o+uQ9KtaKNLVVcNf5 # QpS4f6/MsXOvIFuCYMRdKDjpmvowAeL+1j27bCxCBpDQHrWkfPzZp/X+bt9C7E5h # PP6HVRoqBYR7u1gUf5GEq+5r1HA0jajn0Q6OvfYckE0HdOv6KWa+sAmJG7PDvTZa # e77homzx6IPqggVpNZuCk79SfVmnKu9F58UAnU58TqDHEzGsQnMUQKstS3zjn6SU # 0NLEFNCetluaKkqWDRVLEWbu329IEh3tqXPXfy6Rh/wCbwe9SCJIoqtBexBrPyQY # A2Xaz1fK9ysTsx0kA9V1JwVV44Ia9c+MwtAR6sqKdAgRo/bs/Xu8gua8LDe6KWyu # 974e9mGW7ZO8narDFrAT1EXGHDueygSKvv2K7wB8lAgMGJj73CQvr+jqoWwx6Xdy # eQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFPRa0Edk/iv1whYQsV8UgEf4TIWGMB8G # A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG # Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy # MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w # XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy # dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG # A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD # AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQCSvMSkMSrvjlDPag8ARb0OFrAQtSLMDpN0 # UY3FjvPhwGKDrrixmnuMfjrmVjRq1u8IhkDvGF/bffbFTr+IAnDSeg8TB9zfG/4y # bknuopklbeGjbt7MLxpfholCERyEc20PMZKJz9SvzfuO1n5xrrLOL8m0nmv5kBcv # +y1AXJ5QcLicmhe2Ip3/D67Ed6oPqQI03mDjYaS1NQhBNtu57wPKXZ1EoNToBk8b # A6839w119b+a9WToqIskdRGoP5xjDIv+mc0vBHhZGkJVvfIhm4Ap8zptC7xVAly0 # jeOv5dUGMCYgZjvoTmgd45bqAwundmPlGur7eleWYedLQf7s3L5+qfaY/xEh/9uo # 17SnM/gHVSGAzvnreGhOrB2LtdKoVSe5LbYpihXctDe76iYtL+mhxXPEpzda3bJl # hPTOQ3KOEZApVERBo5yltWjPCWlXxyCpl5jj9nY0nfd071bemnou8A3rUZrdgKIa # utsH7SHOiOebZGqNu+622vJta3eAYsCAaxAcB9BiJPla7Xad9qrTYdT45VlCYTtB # SY4oVRsedSADv99jv/iYIAGy1bCytua0o/Qqv9erKmzQCTVMXaDc25DTLcMGJrRu # a3K0xivdtnoBexzVJr6yXqM+Ba2whIVRvGcriBkKX0FJFeW7r29XX+k0e4DnG6iB # HKQjec6VNzCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI # hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy # MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC # AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg # M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF # dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6 # GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp # Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu # yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E # XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0 # lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q # GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ # +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA # PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw # EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG # NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV # MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj # cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK # BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC # AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX # zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v # cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI # KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG # 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x # M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC # VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449 # xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM # nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS # PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d # Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn # GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs # QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL # jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL # 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNQ # MIICOAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn # MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOkE0MDAtMDVFMC1EOTQ3MSUwIwYDVQQD # ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQCO # HPtgVdz9EW0iPNL/BXqJoqVMf6CBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6as0/jAiGA8yMDI0MDMyNDIzMjU1 # MFoYDzIwMjQwMzI1MjMyNTUwWjB3MD0GCisGAQQBhFkKBAExLzAtMAoCBQDpqzT+ # AgEAMAoCAQACAgN5AgH/MAcCAQACAhJcMAoCBQDprIZ+AgEAMDYGCisGAQQBhFkK # BAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJ # KoZIhvcNAQELBQADggEBAJsGQMJHkyP2uHuaHwL6IHw0ydwSq22zMqbO8LNqbE9n # 3tTY1dBBUeJJRCfxR+p6tX9Ge13wPbd0qT2kBTLvqHMYl2RSl5cwMXlInNy7ov7z # aQLZ+fCNS6eEFQRnpIUyZbtHn+SgSQVYsgTRa00UIt95ZIx/5Fw4IDZ1nIyomqBC # VaDMkbdhXai7r4xPlDyNXfZ6Uq7tT6/2IlD9Pnb/HesemtcXkAwgq3weA3Cng4wX # 2DiwVZ0LELhkITDn5zbaOKDC8N91TwS5MHcf8EcBkhH2RwLTiS79Lpt0/7DaMtGj # CcJq2yJUY507Tg9xx+goxUrm0817W/I8kUEVnk7qXQsxggQNMIIECQIBATCBkzB8 # MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk # bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N # aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAezgK6SC0JFSgAABAAAB # 7DANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEE # MC8GCSqGSIb3DQEJBDEiBCA9/22TJdV/c9EpiLFHYtN1ifrjm99ZhI6AAEmE/1Jm # czCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EICcJ5vVqfTfIhx21QBBbKyo/ # xciQIXaoMWULejAE1QqDMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT # Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB # IDIwMTACEzMAAAHs4CukgtCRUoAAAQAAAewwIgQgmK9yhkdQfpZvi0vyiGhp7uuf # ssnNdiaLmpmjiekIWVIwDQYJKoZIhvcNAQELBQAEggIAXBOo1ybkpdzy/dv8UUE4 # Tjc67jkxkZanF7j+LtTtKlLjIR2eOnpfBjK4zVGRL/GTg4P3B4fb76hmgFPedZPa # aZ8fgxSoWegMxxuNjjWxOmWsL3Yg3jNLVU3xd/VR0Rv319wBQV1Y6cpC5YrIVllg # eCalVg43EisaQKbBGdaEa2gqQwnYSnP1JDqgaVVJN/AIPzJMHKN0e7l2EOB6FIjb # 8S6ZfXhabx0srMfr+xxR20dWbPWfuI3v4DDJvir7W2I5HFc+/AdCuwJ4DCPZ0lxo # oo6sJCgmHbs6GLRh04RS2qJxHpVX9OFOu3o87eNA2nW8m98fyX9Jx99ONoIheFNY # 0AcLCWiUtnafgD52TtO4zjntcw/Xewxo5q1wHvytuxsxdpMdcUJKalq8DonN28Yj # /piWTat14f8gKNDWaKbAXnmY0KOe3rHx/vFfobZz1d7VI+tovOHOJei3tlMjAV9H # DXUSO9QPGt10vv5zJZvYppreF4e5yeQ7oW7hnAYKNzX9HsI7AzVxX4XF8NsxFUEd # 4TMjJ+5WsaFUXWSeFIDDDtrDUKlMlkZftvOtQF7+dyzaOlJbRgwbS+FDxceWvpNL # 0er7HdHY41fqOpvf4UOwO2AwhiGp1nRwqdLRZ5gVux4NcGL2DDOOHE1+/DLC7HtI # 4Wj0wxrsFBnjyCcxLPdqeFI= # SIG # End signature block |