module.psm1
#requires -Modules 'Microsoft.PowerShell.Utility' -Assembly System.Security -Version 3.0 <# Functions for the Authorization against Azure Active Directory Copyright Chris Speers, Avanade 2016 No warranty implied or expressed, side effects include insomnia, runny nose, vomiting #> #Winforms Sync Context $Script:FormSyncContext=[hashtable]::Synchronized(@{}) #Discovery Key Cache $Script:DiscoveryKeyCache=@{} #Default Native Client Redirect Uri $Script:DefaultNativeRedirectUri="urn:ietf:wg:oauth:2.0:oob" $Script:DefaultAuthUrl='https://login.microsoftonline.com' $Script:DefaultTokenApiVersion="2.1" $Script:WSFedUserRealmApiVersion="1.0" #Fungible resource id for ASM and ARM $Script:DefaultAzureManagementUri='https://management.core.windows.net/' $Script:DefaultARMUri='https://management.azure.com/' $Script:DefaultAzurePortalUri='https://portal.azure.com/' $Script:DefaultAzureVaultUri='https://vault.azure.net' #Fungible resource id for Legacy and Modern Graph API $Script:DefaultAzureADGraphUri='https://graph.windows.net/' $Script:DefaultMicrosoftGraphUri='https://graph.microsoft.com/' #Native client id for ASM,ARM,graph $Script:DefaultPowershellClientId='1950a258-227b-4e31-a9cf-717495945fc2' $Script:DefaultAzurePortalClientId='c44b4083-3bb0-49c1-b47d-974e53cbdf3c' $Script:DefaultAzureActiveDirectoryClientId="00000002-0000-0000-c000-000000000000" $Script:DefaultVisualStudioClientId="872cd9fa-d31f-45e0-9eab-6e460a02d1f1" $Script:DefaultAzureCLIClientId="04b07795-8ddb-461a-bbee-02f9e1bf7b46" $Global:Azure_ActiveDirectory_WellKnownResourceIds=@{ ARM=$Script:DefaultARMUri; ASM=$Script:DefaultAzureManagementUri; Portal=$Script:DefaultAzurePortalUri; Vault=$Script:DefaultAzureVaultUri; Graph=$Script:DefaultAzureADGraphUri; MicrosoftGraph=$Script:DefaultMicrosoftGraphUri; } $Global:Azure_ActiveDirectory_WellKnownClientIds=@{ PowershellARM=$Script:DefaultPowershellClientId; Portal=$Script:DefaultAzurePortalClientId; } $Global:Azure_ActiveDirectory_WellKnownConnections=New-Object psobject -Property @{ ARM=@{Resource=$Global:Azure_ActiveDirectory_WellKnownResourceIds['ASM'];ClientId=$Global:Azure_ActiveDirectory_WellKnownClientIds['PowershellARM']} Portal=@{Resource=$Global:Azure_ActiveDirectory_WellKnownResourceIds['Portal'];ClientId=$Global:Azure_ActiveDirectory_WellKnownClientIds['Portal']} Vault=@{Resource=$Global:Azure_ActiveDirectory_WellKnownResourceIds['Vault'];ClientId=$Global:Azure_ActiveDirectory_WellKnownClientIds['PowershellARM']} Graph=@{Resource=$Global:Azure_ActiveDirectory_WellKnownResourceIds['Graph'];ClientId=$Global:Azure_ActiveDirectory_WellKnownClientIds['PowershellARM']} } $Global:Azure_ActiveDirectory_Defaults=New-Object psobject -Property @{ DefaultUri=$Script:DefaultAuthUrl; DefaultRedirectUri=$Script:DefaultNativeRedirectUri; Connections=$Global:Azure_ActiveDirectory_WellKnownConnections; } #endregion $Script:OauthClientAssertionType='urn:ietf:params:oauth:client-assertion-type:jwt-bearer' #region SAML Constants $Script:Saml1AssertionType="urn:oasis:names:tc:SAML:1.0:assertion" $Script:Saml2AssertionType="urn:oasis:names:tc:SAML:2.0:assertion" $Script:SamlBearer11TokenType="urn:ietf:params:oauth:grant-type:saml1_1-bearer" $Script:SamlBearer20TokenType = "urn:ietf:params:oauth:grant-type:saml2-bearer"; #TODO:OAuth OnBehalfOf $Script:JwtBearerTokenType = "urn:ietf:params:oauth:grant-type:jwt-bearer"; #region STS Envelope $Script:WSTrustSoapEnvelopeTemplate=@" <s:Envelope xmlns:s='http://www.w3.org/2003/05/soap-envelope' xmlns:a='http://www.w3.org/2005/08/addressing' xmlns:u='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'> <s:Header> <a:Action s:mustUnderstand='1'>http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue</a:Action> <a:messageID>urn:uuid:{2}</a:messageID> <a:ReplyTo><a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address></a:ReplyTo> <a:To s:mustUnderstand='1'>{3}</a:To> <o:Security s:mustUnderstand='1' xmlns:o='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'> <u:Timestamp u:Id='_0'> <u:Created>{4}</u:Created> <u:Expires>{5}</u:Expires> </u:Timestamp> <o:UsernameToken u:Id='uuid-{2}'> <o:Username>{0}</o:Username> <o:Password>{1}</o:Password> </o:UsernameToken> </o:Security> </s:Header> <s:Body> <trust:RequestSecurityToken xmlns:trust='http://docs.oasis-open.org/ws-sx/ws-trust/200512'> <wsp:AppliesTo xmlns:wsp='http://schemas.xmlsoap.org/ws/2004/09/policy'> <a:EndpointReference> <a:Address>urn:federation:MicrosoftOnline</a:Address> </a:EndpointReference> </wsp:AppliesTo> <trust:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Bearer</trust:KeyType> <trust:RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</trust:RequestType> </trust:RequestSecurityToken> </s:Body> </s:Envelope> "@ #endregion #region Helper methods <# .SYNOPSIS Converts a Unix Timestamp to DateTime .PARAMETER UnixTime The Unix Timestamp to be converted #> Function ConvertFromUnixTime { [OutputType([System.DateTime])] [CmdletBinding()] param ( [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true,ValueFromPipeline=$true)] [double[]]$UnixTime ) foreach ($item in $UnixTime) { $epoch = New-Object System.DateTime(1970, 1, 1, 0, 0, 0, 0) $normaltime=$epoch.AddSeconds($item) Write-Output $normaltime } } <# .SYNOPSIS Converts a DateTime to a Unix Timestamp .PARAMETER DateTime The DateTime to be converted #> Function ConvertToUnixTime { [OutputType([System.Double])] [CmdletBinding()] param ( [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true,ValueFromPipeline=$true)] [datetime[]]$DateTime ) PROCESS { foreach ($item in $DateTime) { $epoch = New-Object System.DateTime(1970, 1, 1, 0, 0, 0, 0); $delta = $item - $epoch; $unixtime=[Math]::Floor($delta.TotalSeconds) Write-Output $unixtime } } } <# .SYNOPSIS Removes Base64 Url Padding from a string .PARAMETER Data The Input String #> Function RemoveBase64UrlPaddingFromString { [CmdletBinding()] param ( [Parameter(Mandatory=$true,ValueFromPipeline=$true)] [String[]]$Data ) PROCESS { foreach ($item in $Data) { $UnpaddedData=$item.Replace('-', '+').Replace('_', '/') switch ($item.Length % 4) { 0 { break } 2 { $UnpaddedData += '==' } 3 { $UnpaddedData += '=' } default { throw New-Object ArgumentException('data') } } Write-Output $UnpaddedData } } } <# .SYNOPSIS Adds Base64 Url Padding to a string .PARAMETER Data The Input String #> Function AddBase64UrlPaddingToString { [CmdletBinding()] param ( [Parameter(Mandatory=$true,ValueFromPipeline=$true)] [String[]]$Data ) PROCESS { foreach ($item in $Data) { $CleanedInput=$item.Split('=')|Select-Object -First 1 #$CleanedInput=$CleanedInput.Replace('-','+').Replace('_','/') $CleanedInput=$CleanedInput.Replace('+','-').Replace('/','_') Write-Output $CleanedInput } } } Function GetRsaCryptoProvider { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [System.Security.Cryptography.RSACryptoServiceProvider] $RsaProvider ) if($RsaProvider.CspKeyContainerInfo.ProviderType -in 1,12) { $csp=New-Object System.Security.Cryptography.CspParameters $csp.KeyNumber=$RsaProvider.CspKeyContainerInfo.KeyNumber $csp.KeyContainerName=$RsaProvider.CspKeyContainerInfo.KeyContainerName if($RsaProvider.CspKeyContainerInfo.MachineKeyStore) { $csp.Flags=[System.Security.Cryptography.CspProviderFlags]::UseMachineKeyStore } $csp.Flags=$csp.Flags -bor [System.Security.Cryptography.CspProviderFlags]::UseExistingKey $csp.ProviderType=24 $NewRsaProvider=New-Object System.Security.Cryptography.RSACryptoServiceProvider($csp) Write-Output $NewRsaProvider } else { Write-Output $RsaProvider } } Function GetCertificateHash { [CmdletBinding()] param ( [Parameter(Mandatory=$true,ValueFromPipeline=$true)] [System.Security.Cryptography.X509Certificates.X509Certificate2[]] $Certificate ) BEGIN { } PROCESS { foreach ($Cert in $Certificate) { $Signature=[System.Convert]::ToBase64String($Cert.GetCertHash()) Write-Output $Signature } } END { } } Function NewClientAssertion { param ( [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.Uri]$Audience, [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate, [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [String]$ClientId, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [String]$JwtId=([Guid]::NewGuid().ToString()), [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [datetime]$Expires=($NotBefore.AddMinutes(60)), [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [DateTime]$NotBefore=([DateTime]::UtcNow) ) $JwtHeaders=[ordered]@{ "alg"="RS256"; "x5t"=($Certificate|GetCertificateHash|AddBase64UrlPaddingToString) } $JwtPayload=[ordered]@{ "aud"=$Audience.AbsoluteUri; "exp"= (ConvertToUnixTime -DateTime $Expires); "iss"=$ClientId; "jti"=$JwtId; "nbf"= (ConvertToUnixTime -DateTime $NotBefore); "sub"=$ClientId; } $HeaderJson=$JwtHeaders|ConvertTo-Json -Compress $PayloadJson=$JwtPayload|ConvertTo-Json -Compress $HeaderBytes=[System.Text.Encoding]::UTF8.GetBytes($HeaderJson) $HeaderString=[Convert]::ToBase64String($HeaderBytes)|AddBase64UrlPaddingToString $PayloadBytes=[System.Text.Encoding]::UTF8.GetBytes($PayloadJson) $PayloadString=[Convert]::ToBase64String($PayloadBytes)|AddBase64UrlPaddingToString $EncodedAssertion="$HeaderString.$PayloadString" Write-Output $EncodedAssertion } <# .SYNOPSIS Creates a new WinForm hosting a WebBrowser control to navigate to a URI .PARAMETER NavigateUri The Uri for the WebBrowser to navigate upon form load .PARAMETER FormTitle The default title on the form .PARAMETER FormWidth The form width .PARAMETER FormHeight The form height #> Function CreateWebForm { [OutputType([System.Windows.Forms.Form])] [CmdletBinding()] param ( [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.Uri]$NavigateUri, [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [string]$FormTitle, [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [int]$FormWidth, [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [int]$FormHeight, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.Windows.Forms.FormStartPosition]$StartupPosition=[System.Windows.Forms.FormStartPosition]::CenterParent ) $Script:FormSyncContext=[hashtable]::Synchronized(@{}) #New WinForm $FormSize = New-Object System.Drawing.Size($FormWidth,$FormHeight) $objForm = New-Object System.Windows.Forms.Form #Navigate on load $OnFormLoad={ param ( [Parameter()] [object] $sender, [Parameter()] [System.EventArgs] $e ) Write-Verbose "Loaded! Navigating to $($Script:FormSyncContext.NavigateUri)" $Script:FormSyncContext.Browser.Navigate($Script:FormSyncContext.NavigateUri,$false) } $objForm.add_Load($OnFormLoad) #Add a web browser control $webBrowser=New-Object System.Windows.Forms.WebBrowser $webBrowser.Location=(New-Object System.Drawing.Point(0,0)) $webBrowser.MinimumSize=(New-Object System.Drawing.Size(20, 20)) $webBrowser.Dock=[System.Windows.Forms.DockStyle]::Fill $webBrowser.Name="WebBrowser" #$objForm.StartPosition = "CenterScreen" $objForm.AutoScaleMode=[System.Windows.Forms.AutoScaleMode]::Font $objForm.AutoScaleDimensions=New-Object System.Drawing.SizeF(6.0,13.0) $objForm.ClientSize=$FormSize $objForm.Controls.Add($webBrowser) $objForm.Text = $FormTitle [System.Windows.Forms.Application]::EnableVisualStyles() #Put these on the sync context $Script:FormSyncContext.Form=$objForm $Script:FormSyncContext.Browser=$webBrowser $Script:FormSyncContext.NavigateUri=$NavigateUri.AbsoluteUri Write-Output $objForm } <# .SYNOPSIS Evaluates the WSFederation Metadata for the IntegratedAuth and UsernamePassword endpoints .PARAMETER MexDocument The WSFederation Metadata Document #> Function GetMexPolicies { [CmdletBinding(ConfirmImpact='None')] [OutputType([System.Collections.Hashtable])] param ( [Parameter(Mandatory=$true,ValueFromPipeline=$true)] [System.Xml.XmlDocument]$MexDocument ) $MexPolicies=@{} foreach ($policy in $MexDocument.definitions.Policy) { if($policy.ExactlyOne -eq $null) { continue } else { Write-Verbose "[GetMexPolicies] Examining Policy $($policy.Id)" $AllElement=$policy.ExactlyOne.All $NegElem=$AllElement.NegotiateAuthentication if($NegElem -ne $null) { Write-Verbose "[GetMexPolicies] IntegratedAuth policy $($policy.Id) Added." $MexPolicies.Add("#$($policy.Id)",(New-Object PSObject -Property @{Id=$policy.Id;AuthType=0})) } $SupTokenElem=$AllElement.SignedEncryptedSupportingTokens if($SupTokenElem -eq $null) { continue } $SupTokenPolicyElem=$SupTokenElem.Policy if($SupTokenPolicyElem -eq $null) { continue } $UserNameElem=$SupTokenPolicyElem.UsernameToken if($UserNameElem -eq $null -or $UserNameElem.Policy -eq $null -or $UserNameElem.Policy.WssUsernameToken10 -eq $null) { continue } $MexPolicies.Add("#$($policy.Id)",(New-Object PSObject -Property @{Id=$policy.Id;AuthType=1})) Write-Verbose "[GetMexPolicies] Username/Password Policy $($policy.Id) Added." } } Write-Output $MexPolicies } <# .SYNOPSIS Retrieves the bindings from the WSFederation metadata .PARAMETER MexDocument The WSFederation Metadata Document #> Function GetMexBindings { [CmdletBinding(ConfirmImpact='None')] [OutputType([System.Collections.Hashtable])] param ( [Parameter(Mandatory=$true,ValueFromPipeline=$true)] [System.Xml.XmlDocument]$MexDocument ) $MexBindings=@{} $MexPolicies=GetMexPolicies -MexDocument $MexDocument Write-Verbose "[GetMexBindings] Found $($MexPolicies.Count) Policies" foreach ($item in $MexDocument.definitions.binding) { Write-Verbose "[GetMexBindings] Examining Binding $($item.name)" $ItemName=$item.name if([String]::IsNullOrEmpty($ItemName)) { continue } $PolicyRefNode=$item.PolicyReference if($PolicyRefNode -eq $null) { continue } $ItemUri=$PolicyRefNode.URI Write-Verbose "[GetMexBindings] Examining Policy Reference $ItemUri" if([String]::IsNullOrEmpty($ItemUri) -or $MexBindings.ContainsKey($ItemUri)) { continue } $OperationNode=$item.operation if($OperationNode -eq $null) { continue } $OperationSubNode=$OperationNode.operation if($OperationSubNode -eq $null) { continue } if([String]::IsNullOrEmpty($OperationSubNode.soapAction)) { continue } $BindingNode=$item.binding if($BindingNode -eq $null) { continue } if([String]::IsNullOrEmpty($BindingNode.transport)) { continue } $MexPolicy=$MexPolicies[$ItemUri] if($MexPolicy -eq $null) { continue } $MexBindings.Add($ItemName,$MexPolicy) Write-Verbose "[GetMexBindings] Binding $ItemUri - $ItemName Added." } Write-Output $MexBindings } <# .SYNOPSIS Evaluates the response envelope for SAML assertion tokens .PARAMETER StsResponse The SOAP Envelope from the STS #> Function GetSecurityTokensFromEnvelope { [OutputType([PSObject[]])] [CmdletBinding()] param ( [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.Xml.XmlDocument[]]$StsResponse ) PROCESS { foreach ($item in $StsResponse) { $Tokens=@() $EnvelopeBody=$StsResponse.Envelope.Body $TokenResponseCollection=$EnvelopeBody.RequestSecurityTokenResponseCollection if($TokenResponseCollection -ne $null) { foreach ($TokenResponse in $TokenResponseCollection.RequestSecurityTokenResponse) { $TokenTypeId=$TokenResponse.TokenType $RequestedToken=$TokenResponse.RequestedSecurityToken $TokenAssertion=$RequestedToken.Assertion.OuterXml if($TokenTypeId -eq $Script:Saml1AssertionType) { $AssertionType=$Script:SamlBearer11TokenType } elseif($TokenTypeId -eq $Script:Saml2AssertionType) { $AssertionType=$Script:SamlBearer20TokenType } #We will default to 2.0 like else { $AssertionType=$Script:SamlBearer20TokenType } $Token=New-Object psobject -Property @{ AssertionType=$AssertionType; TokenType=$TokenTypeId; Token=$TokenAssertion; } $Tokens+=$Token } } Write-Output $Tokens } } } Function GetAzureADUserRealm { [OutputType([System.Management.Automation.PSCustomObject])] [CmdletBinding(ConfirmImpact='None')] param ( [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [System.String[]]$UserPrincipalName, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.Uri]$AuthorizationEndpoint=$Script:DefaultAuthUrl ) PROCESS { foreach ($item in $UserPrincipalName) { $RealmUriBuilder=New-Object System.UriBuilder($AuthorizationEndpoint) $RealmUriBuilder.Path="/common/UserRealm" $RealmUriBuilder.Query="api-version=2.1&user=$item" $RealmDetails=Invoke-RestMethod -Uri $RealmUriBuilder.Uri -ContentType "application/json" -ErrorAction Stop Write-Output $RealmDetails } } } Function GetWSFedBindings { [OutputType([System.Collections.Hashtable])] [CmdletBinding(ConfirmImpact='None')] param ( [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [System.Xml.XmlDocument[]]$MexDocument ) PROCESS { foreach ($item in $MexDocument) { $MexPolicyBindings=GetMexBindings -MexDocument $item Write-Verbose "[GetWSFedBindings] Found $($MexPolicyBindings.Count) binding(s)" foreach($port in $item.definitions.service.port) { $BindingName=$port.binding if([String]::IsNullOrEmpty($BindingName)) { continue } $uri=$BindingName.Split(':',2)|Select-Object -Last 1 Write-Debug "[GetWSFedBindings] Examining Port:$uri" if($MexPolicyBindings[$uri] -eq $null) { continue } $EndpointNode=$port.EndpointReference if($EndpointNode -eq $null) { continue } $AddressNode=$EndpointNode.Address if($AddressNode -eq $null) { continue } Write-Verbose "[GetWSFedBindings] Adding Url:$AddressNode for item $uri" $EndpointUri=New-Object System.Uri($AddressNode) $MexPolicyBindings[$uri]|Add-Member -MemberType NoteProperty -Name Url -Value $EndpointUri } Write-Verbose "Found $($MexPolicyBindings.Count) binding(s)." Write-Output $MexPolicyBindings } } } <# .SYNOPSIS Evaluates a WSFed metadata document for the specified authentication type .PARAMETER MexDocument The WSFed metadata .PARAMETER AuthType The desired authentication type #> Function GetWSFedEndpoint { [CmdletBinding(ConfirmImpact='None')] [OutputType([System.Uri])] param ( [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.Xml.XmlDocument]$MexDocument, [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [ValidateSet("IntegratedAuth","UsernamePassword")] [System.String]$AuthType ) $DesiredAuth=0 if($AuthType -eq "IntegratedAuth") { $DesiredAuth=0 } else { $DesiredAuth=1 } $MexBindings=GetWSFedBindings -MexDocument $MexDocument Write-Verbose "[GetWSFedEndpoint] Examining Metadata Bindings..." foreach ($BindingId in $MexBindings.Keys) { Write-Debug "[GetWSFedEndpoint] Examining Binding $($BindingId)" $Binding=$MexBindings[$BindingId] if($Binding.AuthType -eq $DesiredAuth) { Write-Debug "[GetWSFedEndpoint] Endpoint:$Binding.Url is a match!" Write-Output $Binding.Url } } } <# .SYNOPSIS Issues a request to the security token service and evaluates SAML assertion tokens .PARAMETER AuthUri The security token service URI .PARAMETER Credential The credential to use for authentication #> Function GetWSTrustResponse { [OutputType([PSObject])] [CmdletBinding()] param ( [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.Uri]$AuthUri, [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [pscredential]$Credential, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [String]$SoapEnvelopeTemplate=$Script:WSTrustSoapEnvelopeTemplate, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [Int32]$LengthInMinutes=10 ) $Now=[DateTime]::UtcNow $UUID=[Guid]::NewGuid() $UserName=$Credential.UserName $Password=$Credential.GetNetworkCredential().Password Write-Verbose "[GetWSTrustResponse] Executing SOAP Action against $AuthUri" $StartTime=$Now.ToString("yyyy'-'MM'-'ddTHH':'mm':'ss'Z'") $EndTime=(($Now.AddMinutes($LengthInMinutes)).ToString("yyyy'-'MM'-'ddTHH':'mm':'ss'Z'")) $AuthSoapEnvelope=($SoapEnvelopeTemplate -f $UserName,$Password,$UUID,$AuthUri.AbsoluteUri,$StartTime,$EndTime) Write-Verbose "[GetWSTrustResponse] Retrieving STS SOAP Response with Validity $StartTime to $EndTime" $Headers=@{SOAPAction=''} $result=Invoke-RestMethod -Uri $AuthUri -Headers $Headers -Body $AuthSoapEnvelope -Method Post -ContentType "application/soap+xml" -ErrorAction Stop Write-Verbose "[GetWSTrustResponse] Evaluating Response Envelope" $StsTokens=GetSecurityTokensFromEnvelope -StsResponse $result $WSFedResponse=$StsTokens|Where-Object{$_.TokenType -eq $Script:Saml2AssertionType} if ($WSFedResponse -eq $null) { $WSFedResponse=$StsTokens|Where-Object{$_.TokenType -eq $Script:Saml1AssertionType} } if ($WSFedResponse -eq $null) { throw "Unable to create a User Assertion" } Write-Output $WSFedResponse } <# .SYNOPSIS Retrieves an OAuth User Assertion token from the specified username password endpoint .PARAMETER UsernamePasswordEndpoint The WSFed UsernamePassword endpoint .PARAMETER Credential The Credential to use for authentication #> Function GetWSTrustAssertionToken { [CmdletBinding()] param ( [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.Uri]$Endpoint, [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [pscredential]$Credential ) #TODO:See if we can do integrated auth.... Write-Verbose "[GetWSTrustAssertionToken] Retrieving SAML Token from $Endpoint" $WsResult=GetWSTrustResponse -AuthUri $Endpoint -Credential $Credential Write-Verbose "[GetWSTrustAssertionToken] Retrieved SAML Token: $($WsResult.TokenType) Assertion $($WsResult.AssertionType)" #Encode the SAML assertion so we can get a token $AssertionType=$WsResult.AssertionType $TokenBytes=[System.Text.Encoding]::UTF8.GetBytes($WsResult.Token) $EncodedAssertion=[System.Convert]::ToBase64String($TokenBytes) #Go and get the token Write-Verbose "[GetWSTrustAssertionToken] Retrieving Bearer Token For User Assertion $($WsResult.AssertionType) for Resource:$Resource" $UriBuilder=New-Object System.UriBuilder($AuthorizationUri) $UriBuilder.Path="$TenantId/$TokenEndpoint" $UriBuilder.Query="api-version=$TokenApiVersion" $RequestBody=[ordered]@{ 'grant_type'=$AssertionType; 'client_id'=$ClientId; 'resource'=$Resource.OriginalString; 'scope'='openid'; 'assertion'=$EncodedAssertion; } $Response=Invoke-RestMethod -Method Post -Uri $UriBuilder.Uri -Body $RequestBody -ErrorAction Stop Write-Output $Response } <# .SYNOPSIS Retreives an OAuth 2 JWT from Azure Active Directory as a fully managed User .PARAMETER Resource The Resource Uri to obtain a token for .PARAMETER ClientId The registered Azure Active Directory application id .PARAMETER Credential The credential to use for authentication .PARAMETER TenantId The Azure Active Directory tenant id or domain name .PARAMETER AuthorizationUri The Azure Active Directory Token AuthorizationEndpoint .PARAMETER TokenEndpoint The Authorization Token Endpoint .PARAMETER TokenApiVersion The OAuth Token API Version #> Function GetAzureADUserToken { [CmdletBinding(ConfirmImpact='None')] param ( [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.Uri]$Resource, [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.String]$ClientId, [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [pscredential]$Credential, [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.String]$TenantId, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.Uri]$AuthorizationUri=$Script:DefaultAuthUrl, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.String]$TokenEndpoint='oauth2/token', [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.String]$TokenApiVersion=$Script:DefaultTokenApiVersion, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.String]$TokenScope="openid" ) $UserName=$Credential.UserName $Password=$Credential.GetNetworkCredential().Password $UriBuilder=New-Object System.UriBuilder($AuthorizationUri) $UriBuilder.Path="$TenantId/$TokenEndpoint" $UriBuilder.Query="api-version=$TokenApiVersion" Write-Verbose "[GetAzureADUserToken] Requesting User Token for User $UserName from $($UriBuilder.Uri.AbsoluteUri)" $Request=[ordered]@{ 'grant_type'='password'; 'resource'=$Resource.OriginalString; 'client_id'=$ClientId; 'username'=$UserName; 'password'=$Password; 'scope'=$TokenScope; } Write-Verbose "Acquiring Token From $($UriBuilder.Uri)" $Response=Invoke-RestMethod -Method Post -Uri $UriBuilder.Uri -Body $Request -ErrorAction Stop Write-Output $Response } <# .SYNOPSIS Retrieves an Authorization Code for an application interactively using the OAuth Consent Framework .PARAMETER AuthorizationUri The endpoint to navigate for an OAuth authorization code #> Function GetAzureADAuthorizationCode { [OutputType([String])] [CmdletBinding()] param ( [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.Uri]$AuthorizationUri ) $ConsentForm=CreateWebForm -NavigateUri $AuthorizationUri -FormTitle "Sign in to Azure Active Directory" -FormWidth 500 -FormHeight 450 try { $ConsentBrowser=$ConsentForm.Controls|Select-Object -First 1 $OnBrowserNavigated={ param ( [Parameter()] [object] $sender, [Parameter()] [System.Windows.Forms.WebBrowserNavigatedEventArgs] $e ) $TheForm=$sender.Parent Write-Verbose "[GetAzureADAuthorizationCode] Navigated $($e.Url)" $uri=New-Object System.Uri($e.Url) $QueryParams=$uri.Query.TrimStart('?').Split('&') #Make a hashtable of the query $Parameters=@{} foreach ($item in $QueryParams) { Write-Verbose "[GetAzureADAuthorizationCode] Parameter:$item" $pieces=$item.Split('=') $Parameters.Add($pieces[0],[System.Uri]::UnescapeDataString($pieces[1])) } #Look for the Authorization Code if($Parameters.ContainsKey('code')) { Write-Verbose "[GetAzureADAuthorizationCode] Authorization Code Received!" $Script:FormSyncContext.Code=$Parameters['code'] $TheForm.DialogResult=[System.Windows.Forms.DialogResult]::OK $TheForm.Close() } #Look for an error (cancel) elseif($Parameters.ContainsKey('error')) { $TheForm.DialogResult=[System.Windows.Forms.DialogResult]::Abort $TheForm.Close() $Script:FormSyncContext.Error="$($Parameters['error']):$($Parameters['error_description'].Replace('+'," "))" Write-Verbose "[GetAzureADAuthorizationCode] Error Retrieving Access Code:$($Script:FormSyncContext.Error)" } } $OnDocumentCompleted={ param ( [object]$sender, [System.Windows.Forms.WebBrowserDocumentCompletedEventArgs]$e ) $TheForm=$sender.Parent Write-Verbose "[GetAzureADAuthorizationCode] Document Completed! Size:$($sender.Document.Body.ScrollRectangle.Size)" $TheForm.Text= $sender.Document.Title } $ConsentBrowser.add_DocumentCompleted($OnDocumentCompleted) $ConsentBrowser.add_Navigated($OnBrowserNavigated) $ConsentResult=$ConsentForm.ShowDialog() if($ConsentResult -eq [System.Windows.Forms.DialogResult]::OK) { Write-Output $Script:FormSyncContext.Code } else { throw "The Operation Was Cancelled. $($Script:FormSyncContext.Error)" } } finally { $ConsentForm.Dispose() } } <# .SYNOPSIS Retrieves an Access Token for an application interactively using the OAuth Consent Framework. .DESCRIPTION Retrieves an Access Token for an application interactively using the OAuth Consent Framework. Requires the application to allow Implicit Auth .PARAMETER AuthorizationUri The endpoint to navigate for an OAuth authorization token #> Function GetAzureADTokenByWebForm { [OutputType([String])] [CmdletBinding()] param ( [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.Uri]$AuthorizationUri ) $AuthForm=CreateWebForm -NavigateUri $AuthorizationUri -FormTitle "Sign in to Azure Active Directory" -FormWidth 500 -FormHeight 450 $AuthBrowser=$AuthForm.Controls|Select-Object -First 1 $OnBrowserNavigated={ param ( [Parameter()] [object] $sender, [Parameter()] [System.Windows.Forms.WebBrowserNavigatedEventArgs] $e ) $TheForm=$sender.Parent Write-Verbose "Navigated $($e.Url)" $uri=New-Object System.Uri($e.Url) if($uri.AbsoluteUri.Contains("#") -and $uri.AbsoluteUri.Contains("access_token")) { $QueryVals=$uri.AbsoluteUri.Split('#')|Select-Object -Last 1 } else { $QueryVals=$uri.Query.TrimStart('?') } $QueryParams=$QueryVals.Split('&') #Make a hashtable of the query $Parameters=@{} foreach ($item in $QueryParams) { $pieces=$item.Split('=') $Parameters.Add($pieces[0],[System.Uri]::UnescapeDataString($pieces[1])) } #Look for the access token if($Parameters.ContainsKey('access_token')) { Write-Verbose "Access Token Received!" $AuthResult=New-Object PSObject -Property $Parameters $Script:FormSyncContext.AuthResult=$AuthResult $TheForm.DialogResult=[System.Windows.Forms.DialogResult]::OK $TheForm.Close() } #Look for an error (cancel) elseif($Parameters.ContainsKey('error')) { $TheForm.DialogResult=[System.Windows.Forms.DialogResult]::Abort $TheForm.Close() $Script:FormSyncContext.Error="$($Parameters['error']):$($Parameters['error_description'].Replace('+'," "))" Write-Verbose "Error Retrieving Access Code:$($Script:FormSyncContext.Error)" } } $OnDocumentCompleted={ param ( [object]$sender, [System.Windows.Forms.WebBrowserDocumentCompletedEventArgs]$e ) $TheForm=$sender.Parent Write-Verbose "Document Completed! Size:$($sender.Document.Body.ScrollRectangle.Size)" $TheForm.Text= $sender.Document.Title } $AuthBrowser.add_DocumentCompleted($OnDocumentCompleted) $AuthBrowser.add_Navigated($OnBrowserNavigated) $AuthResult=$AuthForm.ShowDialog() if($AuthResult -eq [System.Windows.Forms.DialogResult]::OK) { Write-Output $Script:FormSyncContext.AuthResult } else { "The Operation Was Cancelled. $($Script:FormSyncContext.Error)" } } #endregion #region User Realms <# .SYNOPSIS Retrieves the WSFederation details for a given user prinicpal name .PARAMETER UserPrincipalName The user principal name(s) to retrieve details .PARAMETER AuthorizationEndpoint The OAuth WSFed Endpoint #> Function Get-WSTrustUserRealmDetails { [OutputType([pscustomobject])] [CmdletBinding(ConfirmImpact='None')] param ( [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [System.String[]]$UserPrincipalName, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.Uri]$AuthorizationEndpoint=$Script:DefaultAuthUrl, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [String]$UserRealmApiVersion=$Script:WSFedUserRealmApiVersion ) BEGIN { $RealmUriBuilder=New-Object System.UriBuilder($AuthorizationEndpoint) $RealmUriBuilder.Query="api-version=$UserRealmApiVersion" } PROCESS { foreach ($upn in $UserPrincipalName) { try { $RealmUriBuilder.Path="/common/UserRealm/$upn" Write-Verbose "[Get-WSTrustUserRealmDetails] Retrieving User Realm Detail from $($RealmUriBuilder.Uri.AbsoluteUri) for $upn" $RealmDetails=Invoke-RestMethod -Uri $RealmUriBuilder.Uri -ContentType "application/json" -ErrorAction Stop if($RealmDetails) { Write-Output $RealmDetails } } catch { Write-Warning "[Get-WSTrustUserRealmDetails] $upn version:$UserRealmApiVersion $_" } } } } <# .SYNOPSIS Retrieves a set of objects representing User Realm details for a given User Principal NameSpaceType .PARAMETER UserPrincipalName The user principal name(s) to retrieve details .PARAMETER AuthorizationEndpoint The OAuth Endpoint .PARAMETER FederationEndpoint The WSFed Endpoint .DESCRIPTION Returns object(s) containing user realm details Managed Domains: RealmDetails Federated Domains: FederationDoc UsernamePasswordEndpoint Bindings RealmDetails WSFedRealmDetails AuthorizationUrl IntegratedAuthEndpoint #> Function Get-AzureADUserRealm { [CmdletBinding(ConfirmImpact='None')] param ( [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [System.String[]]$UserPrincipalName, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.Uri]$AuthorizationEndpoint=$Script:DefaultAuthUrl, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [String]$UserRealmApiVersion=$Script:WSFedUserRealmApiVersion ) PROCESS { foreach ($UPN in $UserPrincipalName) { $RealmDetails=GetAzureADUserRealm -UserPrincipalName $UPN -AuthorizationEndpoint $AuthorizationEndpoint Write-Verbose "[Get-AzureADUserRealm] Realm Details $($RealmDetails.DomainName) $($RealmDetails.NamespaceType)" if($RealmDetails.NamespaceType -eq "Federated") { Write-Verbose "[Get-AzureADUserRealm] User is Federated" $WsFedRealmDetails=Get-WSTrustUserRealmDetails -UserPrincipalName $UPN -AuthorizationEndpoint $AuthorizationEndpoint $MexDataUrl=$WsFedRealmDetails.federation_metadata_url Write-Verbose "[Get-AzureADUserRealm] Retrieving Federation Metadata from $MexDataUrl" $FedDoc=Invoke-RestMethod -Uri $MexDataUrl -ContentType 'application/soap+xml' -ErrorAction Stop $WsTrustBindings=GetWSFedBindings -MexDocument $FedDoc $IntegratedEndpoint=GetWSFedEndpoint -MexDocument $FedDoc -AuthType IntegratedAuth -ErrorAction SilentlyContinue $UsernameEndpoint=GetWSFedEndpoint -MexDocument $FedDoc -AuthType UsernamePassword -ErrorAction SilentlyContinue $UserRealm=New-Object PSObject -Property @{ RealmDetails=$RealmDetails; WSFedRealmDetails=$WsFedRealmDetails; FederationDoc=$FedDoc; Bindings=$WsTrustBindings; IntegratedAuthEndpoint=$IntegratedEndpoint; UsernamePasswordEndpoint=$UsernameEndpoint; AuthorizationUrl=$RealmDetails.AuthUrl; } } else { Write-Verbose "[Get-AzureADUserRealm] User is Managed" $UserRealm=New-Object PSObject -Property @{ RealmDetails=$RealmDetails; } } Write-Output $UserRealm } } } <# .SYNOPSIS Retreives the Well known OpenId Connect conifguration for the tenant .PARAMETER TenantId The tenant to retrieve the details for .PARAMETER AuthorizationUri The target endpoint #> Function Get-AzureADOpenIdConfiguration { [CmdletBinding(ConfirmImpact='None')] param ( [Parameter(Mandatory=$false,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [String[]]$TenantId='common', [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.Uri]$AuthorizationUri=$Script:DefaultAuthUrl ) PROCESS { foreach ($id in $TenantId) { $OpenIdUriBuilder=New-Object System.UriBuilder($AuthorizationUri) $OpenIdUriBuilder.Path="$id/.well-known/openid-configuration" try { $OpenIdConfig=Invoke-RestMethod -Uri $OpenIdUriBuilder.Uri -ContentType "application/json" -ErrorAction Stop Write-Output $OpenIdConfig } catch [System.Exception] { Write-Warning "[Get-AzureADOpenIdConfiguration] Tenant $id $_" } } } } #endregion #region JWT Helpers <# .SYNOPSIS Converts an encoded JSON Web Token to an object representation .PARAMETER RawToken The encoded JWT string .PARAMETER AsString Returns the decoded JWT as a string delimiting sections with a period #> Function ConvertFrom-EncodedJWT { [CmdletBinding()] param ( [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [String[]]$RawToken, [Parameter(ValueFromPipelineByPropertyName=$true)] [Switch]$AsString ) PROCESS { foreach ($JwtString in $RawToken) { Write-Debug "[ConvertFrom-EncodedJWT] Raw Token $JwtString" $TokenSections=$JwtString.Split("."); $EncodedHeaders=RemoveBase64UrlPaddingFromString -Data $TokenSections[0] $EncodedHeaderBytes=[System.Convert]::FromBase64String($EncodedHeaders) $DecodedHeaders=[System.Text.Encoding]::UTF8.GetString($EncodedHeaderBytes) $EncodedPayload=RemoveBase64UrlPaddingFromString -Data $TokenSections[1] $EncodedPayloadBytes=[System.Convert]::FromBase64String($EncodedPayload) $DecodedPayload=[System.Text.Encoding]::UTF8.GetString($EncodedPayloadBytes) #$EncodedSignature=RemoveBase64PaddingFromString -Data $TokenSections[2] #$EncodedSignatureBytes=[System.Convert]::FromBase64String($EncodedSignature) #$DecodedSignature=[System.Text.Encoding]::UTF8.GetString($EncodedSignatureBytes) $JwtProperties=@{ 'headers' = ($DecodedHeaders|ConvertFrom-Json); 'payload' = ($DecodedPayload|ConvertFrom-Json); #'signature' = ($DecodedSignature|ConvertFrom-Json); } $DecodedJwt=New-Object PSObject -Property $JwtProperties if($AsString.IsPresent) { #$OutputJwt="$DecodedHeaders`n.$DecodedPayload`n.$DecodedSignature" $OutputJwt="$DecodedHeaders`n.$DecodedPayload" Write-Output $OutputJwt } else { Write-Output $DecodedJwt } } } } <# .SYNOPSIS Test whether the current JWT is expired .PARAMETER Token The JWT as a string #> Function Test-JWTHasExpired { [CmdletBinding()] param ( [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [String[]]$Token ) PROCESS { foreach ($item in $Token) { $DecodedToken=ConvertFrom-EncodedJWT -RawToken $item $ExpireTime=ConvertFromUnixTime -UnixTime $DecodedToken.payload.exp Write-Debug "[Test-JWTHasExpired] Token Expires: $($ExpireTime)" if([System.DateTime]::UtcNow -gt $ExpireTime) { Write-Output $true } Write-Output $false } } } <# .SYNOPSIS Return the current JWT expiry as a DateTime .PARAMETER Token The JWT as a string .PARAMETER AsLocal Whether to return the time localized to the current time zone #> Function Get-JWTExpiry { [CmdletBinding()] param ( [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [String[]]$Token, [Parameter(ValueFromPipelineByPropertyName=$true)] [Switch]$AsLocal ) PROCESS { foreach ($item in $Token) { $DecodedToken=ConvertFrom-EncodedJWT -RawToken $item $ExpireTime=ConvertFromUnixTime -UnixTime $DecodedToken.payload.exp if($AsLocal.IsPresent) { $ExpireTime=$ExpireTime.ToLocalTime() } Write-Output $ExpireTime } } } #endregion #region Token/Code Request <# .SYNOPSIS Request an Azure AD OAuth2 authorization code interactively .PARAMETER ConnectionDetails An object containing all the AAD connection properties .PARAMETER Resource The Resource Uri to obtain a token for .PARAMETER ClientId The registered Azure Active Directory application id .PARAMETER AuthorizationUri The Azure Active Directory Token AuthorizationEndpoint .PARAMETER TenantId The Azure Active Directory tenant id or domain name .PARAMETER RedirectUri The approved Redirect URI request for the application .PARAMETER AuthEndpoint The OAuth2 authorization endpoint .PARAMETER TokenApiVersion The OAuth Token API Version .PARAMETER Consent Whether to grant consent during the request .PARAMETER AdminConsent Whether to grant admin consent during the request .PARAMETER Scope The oauth scopes to apply to the authorization request #> Function Get-AzureADAuthorizationCode { [CmdletBinding(ConfirmImpact='None')] param ( [ValidateNotNullOrEmpty()] [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.Uri]$Resource, [ValidateNotNullOrEmpty()] [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.String]$ClientId, [ValidateNotNull()] [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.Uri]$RedirectUri, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.String]$TenantId="common", [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.Uri]$AuthorizationUri=$Script:DefaultAuthUrl, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.String]$AuthEndpoint='oauth2/authorize', [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.String]$TokenApiVersion=$Script:DefaultTokenApiVersion, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [Switch]$Consent, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [Switch]$AdminConsent, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [String[]]$Scope=@('user_impersonation','openid') ) $TokenUriBuilder=New-Object System.UriBuilder($AuthorizationUri) $TokenUriBuilder.Path="$TenantId/$AuthEndpoint" #We will assume they got the Uri correct on entry $TokenQuery="&redirect_uri=$([Uri]::EscapeDataString($RedirectUri.AbsoluteUri))&resource=$([Uri]::EscapeDataString($Resource.OriginalString))" $TokenQuery+="&api-version=$TokenApiVersion&client_id=$($ClientId)&response_type=code" if($Consent.IsPresent) { $TokenQuery+="&prompt=consent" } elseif($AdminConsent.IsPresent) { $TokenQuery+="&prompt=admin_consent" } else { $TokenQuery+="&prompt=login" } if($Scope -ne $null) { $TokenQuery+="&scope=$([String]::Join('+',$Scope))" } $TokenUriBuilder.Query=$TokenQuery $AuthResult=GetAzureADAuthorizationCode -AuthorizationUri $TokenUriBuilder.Uri if ($AuthResult) { Write-Output $AuthResult } } <# .SYNOPSIS Approve an Azure Active Directory Application using the OAuth consent framework .PARAMETER ConnectionDetails An object containing all the AAD connection properties .PARAMETER ClientId The registered Azure Active Directory application id .PARAMETER AuthorizationCode The Authorization Code to exchange .PARAMETER AuthorizationUri The Azure Active Directory Token AuthorizationEndpoint .PARAMETER TenantId The Azure Active Directory tenant id or domain name .PARAMETER RedirectUri The approved Redirect URI request for the application .PARAMETER TokenEndpoint The Authorization Token Endpoint .PARAMETER TokenApiVersion The OAuth Token API Version .PARAMETER AdminConsent Whether to grant admin consent during the request .PARAMETER NativeClient Uses default native client redirect uri if one is not specified #> Function Approve-AzureADApplication { [CmdletBinding(ConfirmImpact='None')] param ( [ValidateNotNullOrEmpty()] [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [string]$ClientId, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.Uri]$RedirectUri, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.Uri]$Resource, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.String]$TenantId="common", [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.Uri]$AuthorizationUri=$Script:DefaultAuthUrl, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.String]$TokenEndpoint='oauth2/token', [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.String]$AuthCodeEndpoint='oauth2/authorize', [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] $TokenApiVersion=$Script:DefaultTokenApiVersion, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [Switch]$AdminConsent, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [Switch]$NativeClient ) if($NativeClient.IsPresent -and $RedirectUri -eq $null) { Write-Verbose "[Approve-AzureADApplication] Using Default Native Redirect URI:$Script:DefaultNativeRedirectUri" $RedirectUri=$Script:DefaultNativeRedirectUri } $ConsentUriBuilder=New-Object System.UriBuilder($AuthorizationUri) $ConsentUriBuilder.Path="$TenantId/$AuthCodeEndpoint" $QueryStr="api-version=$TokenApiVersion" $ConsentType="consent" if($AdminConsent.IsPresent) { Write-Verbose "[Approve-AzureADApplication] Admin Consent Present!" $ConsentType="admin_consent" } $QueryStr+="&client_id=$($ClientId)" if ($RedirectUri -ne $null) { $QueryStr+="&redirect_uri=$([Uri]::EscapeDataString($RedirectUri.AbsoluteUri))" } #We will assume they got the Uri correct on entry if ($Resource -ne $null) { $QueryStr+="&resource=$([Uri]::EscapeDataString($Resource.OriginalString))" } $QueryStr+="&response_type=code&prompt=$ConsentType" $ConsentUriBuilder.Query=$QueryStr $AuthCode=GetAzureADAuthorizationCode -AuthorizationUri $ConsentUriBuilder.Uri.AbsoluteUri Write-Output $AuthCode } <# .SYNOPSIS Exchanges an Azure Active Directory Authorization Code for a Token .PARAMETER ConnectionDetails An object containing all the AAD connection properties .PARAMETER Resource The Resource Uri to obtain a token for .PARAMETER ClientId The registered Azure Active Directory application id .PARAMETER TenantId The Azure Active Directory tenant id or domain name .PARAMETER AuthorizationCode The Authorization Code to exchange .PARAMETER AuthorizationUri The Azure Active Directory Token AuthorizationEndpoint .PARAMETER RedirectUri The approved Redirect URI request for the application .PARAMETER TokenEndpoint The Authorization Token Endpoint .PARAMETER TokenApiVersion The OAuth Token API Version #> Function Get-AzureADAccessTokenFromCode { [OutputType([pscustomobject])] [CmdletBinding(ConfirmImpact='None')] param ( [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.Uri]$Resource, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.Uri]$RedirectUri=$Script:DefaultNativeRedirectUri, [ValidateNotNullOrEmpty()] [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.String]$ClientId, [ValidateNotNullOrEmpty()] [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.String]$AuthorizationCode, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.String]$TenantId="common", [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.Uri]$AuthorizationUri=$Script:DefaultAuthUrl, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.String]$TokenEndpoint='oauth2/token', [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.String]$TokenApiVersion=$Script:DefaultTokenApiVersion ) $TokenUriBuilder=New-Object System.UriBuilder($AuthorizationUri) $TokenUriBuilder.Path="$TenantId/$TokenEndpoint" $TokenUriBuilder.Query="api-version=$TokenApiVersion" $Request=[ordered]@{ 'grant_type'='authorization_code'; 'client_id'=$ClientId; 'resource'=$Resource.OriginalString; 'scope'='openid'; 'code'=$AuthorizationCode; 'redirect_uri'=$RedirectUri.AbsoluteUri; } $Response=Invoke-RestMethod -Method Post -Uri $TokenUriBuilder.Uri -Body $Request -ErrorAction Stop Write-Output $Response } <# .SYNOPSIS Retreives an OAuth 2 JWT from Azure Active Directory as an Application .PARAMETER ConnectionDetails An object containing all the AAD connection properties .PARAMETER Resource The Resource Uri to obtain a token for .PARAMETER ClientId The registered Azure Active Directory application id .PARAMETER ClientSecret The client secret to use for authentication .PARAMETER TenantId The Azure Active Directory tenant id or domain name .PARAMETER AuthorizationUri The Azure Active Directory Token AuthorizationEndpoint .PARAMETER TokenEndpoint The Authorization Token Endpoint .PARAMETER AuthCodeEndpoint The Authorization Code Endpoint .PARAMETER TokenApiVersion The OAuth Token API Version #> Function Get-AzureADClientToken { [CmdletBinding(ConfirmImpact='None')] param ( [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.Uri]$Resource, [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.String]$ClientId, [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.String]$ClientSecret, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.String]$TenantId="common", [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.Uri]$AuthorizationUri=$Script:DefaultAuthUrl, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.String]$TokenEndpoint='oauth2/token', [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.String]$TokenApiVersion=$Script:DefaultTokenApiVersion ) $UriBuilder=New-Object System.UriBuilder($AuthorizationUri) $UriBuilder.Path="$TenantId/$TokenEndpoint" $UriBuilder.Query="api-version=$TokenApiVersion" Write-Verbose "[Get-AzureADClientToken] Retrieving token for Client:$ClientId Tenant:$TenantId with Client Secret:[REDACTED] at $($UriBuilder.Uri.AbsolutePath)" $Request=[ordered]@{ 'grant_type'='client_credentials'; 'client_id'=$ClientId; 'client_secret'=$ClientSecret; 'resource'=$Resource.OriginalString } $Response=Invoke-RestMethod -Method Post -Uri $UriBuilder.Uri -Body $Request -ErrorAction Stop Write-Verbose "[Get-AzureADClientToken] Success!" Write-Output $Response } <# .SYNOPSIS Retreives an OAuth 2 JWT from Azure Active Directory as a User .PARAMETER ConnectionDetails An object containing all the AAD connection properties .PARAMETER Resource The Resource Uri to obtain a token for .PARAMETER ClientId The registered Azure Active Directory application id .PARAMETER Credential The credential to use for authentication .PARAMETER TenantId The Azure Active Directory tenant id or domain name .PARAMETER AuthorizationUri The Azure Active Directory Token AuthorizationEndpoint .PARAMETER TokenEndpoint The Authorization Token Endpoint .PARAMETER AuthCodeEndpoint The Authorization Code Endpoint .PARAMETER TokenApiVersion The OAuth Token API Version .PARAMETER UseMicrosoftAccount Use a microsoft account interactively #> Function Get-AzureADUserToken { [OutputType([psobject])] [CmdletBinding(ConfirmImpact='None',DefaultParameterSetName='explicit')] param ( [Parameter(Mandatory=$false,ParameterSetName='usemsa',ValueFromPipelineByPropertyName=$true)] [Parameter(Mandatory=$false,ParameterSetName='explicit',ValueFromPipelineByPropertyName=$true)] [System.Uri]$Resource=$Script:DefaultAzureManagementUri, [Parameter(Mandatory=$false,ParameterSetName='usemsa',ValueFromPipelineByPropertyName=$true)] [Parameter(Mandatory=$false,ParameterSetName='explicit',ValueFromPipelineByPropertyName=$true)] [System.String]$ClientId=$Script:DefaultPowershellClientId, [Parameter(Mandatory=$true,ParameterSetName='explicit',ValueFromPipelineByPropertyName=$true)] [pscredential]$Credential, [Parameter(Mandatory=$false,ParameterSetName='explicit',ValueFromPipelineByPropertyName=$true)] [Parameter(Mandatory=$false,ParameterSetName='usemsa',ValueFromPipelineByPropertyName=$true)] [System.String]$TenantId="common", [Parameter(Mandatory=$false,ParameterSetName='usemsa',ValueFromPipelineByPropertyName=$true)] [Parameter(Mandatory=$false,ParameterSetName='explicit',ValueFromPipelineByPropertyName=$true)] [System.Uri]$AuthorizationUri=$Script:DefaultAuthUrl, [Parameter(Mandatory=$false,ParameterSetName='usemsa',ValueFromPipelineByPropertyName=$true)] [Parameter(Mandatory=$false,ParameterSetName='explicit',ValueFromPipelineByPropertyName=$true)] [System.String]$TokenEndpoint='oauth2/token', [Parameter(Mandatory=$false,ParameterSetName='usemsa',ValueFromPipelineByPropertyName=$true)] [Parameter(Mandatory=$false,ParameterSetName='explicit',ValueFromPipelineByPropertyName=$true)] [System.String]$AuthCodeEndpoint='oauth2/authorize', [Parameter(Mandatory=$false,ParameterSetName='explicit',ValueFromPipelineByPropertyName=$true)] [Parameter(Mandatory=$false,ParameterSetName='usemsa',ValueFromPipelineByPropertyName=$true)] [System.String]$TokenApiVersion=$Script:DefaultTokenApiVersion, [Parameter(Mandatory=$false,ParameterSetName='usemsa',ValueFromPipelineByPropertyName=$true)] [Switch]$UseMicrosoftAccount ) Write-Verbose "[Get-AzureADUserToken] Retrieving OAuth Token ClientId:$ClientId Resource:$Resource Tenant:$TenantId as $($Credential.UserName)" if($PSCmdlet.ParameterSetName -eq 'usemsa') { Write-Verbose "[Get-AzureADUserToken] Using Microsoft Account - Requires Interactive Login" if($TenantId -ne 'common' -and $TenantId -notmatch '^[{(]?[0-9A-F]{8}[-]?([0-9A-F]{4}[-]?){3}[0-9A-F]{12}[)}]?$') { Write-Verbose "[Get-AzureADUserToken] Retrieving OpenId openid-configuration for $TenantId" $OpenIdConfig=Get-AzureADOpenIdConfiguration -TenantId $TenantId [Uri]$AuthUri=$OpenIdConfig.authorization_endpoint $TenantId=$AuthUri.AbsolutePath.TrimStart('/').Split('/')|Select-Object -First 1 } $AuthCode=Get-AzureADAuthorizationCode -Resource $Resource -ClientId $ClientId ` -RedirectUri $Script:DefaultNativeRedirectUri -TenantId 'common' -AuthorizationUri $AuthorizationUri ` -AuthEndpoint $AuthCodeEndpoint -TokenApiVersion $TokenApiVersion $AuthToken=Get-AzureADAccessTokenFromCode -Resource $Resource -ClientId $ClientId -RedirectUri $Script:DefaultNativeRedirectUri ` -AuthorizationCode $AuthCode -TenantId 'common' -AuthorizationUri $AuthorizationUri ` -TokenEndpoint $TokenEndpoint -TokenApiVersion $TokenApiVersion if($TenantId -ne 'common') { Write-Verbose "[Get-AzureADUserToken] Retrieving Refresh token for $TenantId audience" $AuthToken=Get-AzureADRefreshToken -Resource $Resource -RefreshToken $AuthToken.refresh_token ` -ClientId $ClientId -TenantId $TenantId ` -AuthorizationUri $AuthorizationUri -TokenEndpoint $TokenEndpoint } Write-Output $AuthToken } else { $UserRealm=Get-AzureADUserRealm -UserPrincipalName $Credential.UserName -AuthorizationEndpoint $AuthorizationUri Write-Verbose "[Get-AzureADUserToken] Realm $($UserRealm.RealmDetails.DomainName) NamespaceType:$($UserRealm.RealmDetails.NameSpaceType)" if($UserRealm.FederationDoc -eq $null) { Write-Verbose "[Get-AzureADUserToken] Retrieving OAuth Token for Client:$ClientId as $($Credential.UserName)" $UserResult=GetAzureADUserToken -Resource $Resource -ClientId $ClientId -Credential $Credential -TenantId $TenantId if ($UserResult -ne $null) { Write-Verbose "[Get-AzureADUserToken] Successfully received an OAuth Token!" Write-Output $UserResult } else { throw "Failed to receive an OAuth Token!" } } else { Write-Verbose "[Get-AzureADUserToken] Retrieving WSFed User Assertion Token" #Where to we need to authenticate??? #TODO:See if we can do integrated auth.... if([String]::IsNullOrEmpty($UserRealm.UsernamePasswordEndpoint) -eq $false) { $AssertionResult=GetWSTrustAssertionToken -Endpoint $UserRealm.UsernamePasswordEndpoint -Credential $Credential if ($AssertionResult -ne $null) { Write-Verbose "[Get-AzureADUserToken] Successfully received a WSFed User Assertion Token!" } else { throw "Failed to receive a WSFed User Assertion Token!" } Write-Output $AssertionResult } else { throw "There is no Username/Password endpoint specified in the Federation Document" } } } } <# .SYNOPSIS Retrieves an OAuth2 JWT using the refresh token framework .DESCRIPTION Retrieves an OAuth2 JWT using the refresh token framework .PARAMETER ConnectionDetails An object containing all the AAD connection properties .PARAMETER RefreshToken The JWT refresh token .PARAMETER Resource The Resource Uri to obtain a token for .PARAMETER ClientId The registered Azure Active Directory application id .PARAMETER Credential The credential to use for authentication .PARAMETER TenantId The Azure Active Directory tenant id or domain name .PARAMETER AuthorizationUri The Azure Active Directory Token AuthorizationEndpoint .PARAMETER TokenEndpoint The Authorization Token Endpoint .PARAMETER AuthCodeEndpoint The Authorization Code Endpoint .PARAMETER TokenApiVersion The OAuth Token API Version #> Function Get-AzureADRefreshToken { [CmdletBinding(ConfirmImpact='None')] param ( [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.Uri]$Resource, [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [string]$RefreshToken, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [string]$ClientId=$Script:DefaultPowershellClientId, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.String]$TenantId="common", [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.Uri]$AuthorizationUri=$Script:DefaultAuthUrl, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.String]$TokenEndpoint='oauth2/token', [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.String]$AuthCodeEndpoint='oauth2/authorize', [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.String]$TokenApiVersion=$Script:DefaultTokenApiVersion ) Write-Verbose "[Get-AzureADRefreshToken] Retrieving OAuth Refresh Token for ClientId:$ClientId Resource:$Resource Tenant:$TenantId" $UriBuilder=New-Object System.UriBuilder($AuthorizationUri) $UriBuilder.Path="$TenantId/$TokenEndpoint" $UriBuilder.Query="api-version=$TokenApiVersion" Write-Verbose "[GetAzureADUserToken] Requesting User Token for User $UserName from $($UriBuilder.Uri.AbsoluteUri)" $Request=[ordered]@{ 'grant_type'='refresh_token'; 'resource'=$Resource.OriginalString; 'client_id'=$ClientId; 'refresh_token'=$RefreshToken } Write-Verbose "[Get-AzureADRefreshToken] Acquiring Token From $($UriBuilder.Uri)" $Response=Invoke-RestMethod -Method Post -Uri $UriBuilder.Uri -Body $Request -ErrorAction Stop Write-Output $Response } <# .SYNOPSIS Retrieves an OAuth access token interactively for an application allowing Implicit Flow .DESCRIPTION Retrieves an OAuth access token interactively for an application allowing Implicit Flow .PARAMETER ConnectionDetails An object containing all the AAD connection properties .PARAMETER Resource The Resource Uri to obtain a token for .PARAMETER ClientId The registered Azure Active Directory application id .PARAMETER AuthorizationUri The Azure Active Directory Token AuthorizationEndpoint .PARAMETER TenantId The Azure Active Directory tenant id or domain name .PARAMETER RedirectUri The approved Redirect URI request for the application .PARAMETER AuthEndpoint The OAuth2 authorization endpoint .PARAMETER TokenApiVersion The OAuth Token API Version .PARAMETER Consent Whether to grant consent during the request .PARAMETER AdminConsent Whether to grant admin consent during the request #> Function Get-AzureADImplicitFlowToken { [CmdletBinding(ConfirmImpact='None')] param ( [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.Uri]$Resource, [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.String]$ClientId, [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.Uri]$RedirectUri, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.String]$TenantId="common", [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.Uri]$AuthorizationUri=$Script:DefaultAuthUrl, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.String]$AuthEndpoint='oauth2/authorize', [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.String]$TokenApiVersion=$Script:DefaultTokenApiVersion, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [Switch]$Consent, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [Switch]$AdminConsent ) $TokenUriBuilder=New-Object System.UriBuilder($AuthorizationUri) $TokenUriBuilder.Path="$TenantId/$AuthEndpoint" $TokenQuery="&redirect_uri=$([Uri]::EscapeDataString($RedirectUri.AbsoluteUri))&resource=$([Uri]::EscapeDataString($Resource.OriginalString))" $TokenQuery+="&api-version=$TokenApiVersion&client_id=$($ClientId)&response_type=token" if($Consent.IsPresent) { $TokenQuery+="&prompt=consent" } elseif($AdminConsent.IsPresent) { $TokenQuery+="&prompt=admin_consent" } else { $TokenQuery+="&prompt=login" } $TokenUriBuilder.Query=$TokenQuery $AuthResult=GetAzureADTokenByWebForm -AuthorizationUri $TokenUriBuilder.Uri Write-Output $AuthResult } <# .SYNOPSIS Retrieves an OAuth access token using a certificate .DESCRIPTION Retrieves an OAuth access token using a certificate .PARAMETER ConnectionDetails An object containing all the AAD connection properties .PARAMETER Resource The Resource Uri to obtain a token for .PARAMETER Certificate The certificate to sign the token request .PARAMETER NotBefore The start of token validity .PARAMETER Expires The start of token expiration .PARAMETER ClientId The registered Azure Active Directory application id .PARAMETER AuthorizationUri The Azure Active Directory Token AuthorizationEndpoint .PARAMETER TenantId The Azure Active Directory tenant id or domain name .PARAMETER RedirectUri The approved Redirect URI request for the application .PARAMETER AuthEndpoint The OAuth2 authorization endpoint .PARAMETER TokenApiVersion The OAuth Token API Version #> Function Get-AzureADClientAssertionToken { [CmdletBinding(ConfirmImpact='None')] param ( [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.Uri]$Resource=$Script:DefaultAzureManagementUri, [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.String]$ClientId, [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.String]$TenantId="common", [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [DateTime]$NotBefore=([DateTime]::UtcNow), [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [datetime]$Expires=($NotBefore.AddMinutes(60)), [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.String]$AssertionType=$Script:OauthClientAssertionType, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.Uri]$AuthorizationUri=$Script:DefaultAuthUrl, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.String]$TokenEndpoint='oauth2/token', [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.String]$TokenApiVersion=$Script:DefaultTokenApiVersion ) Write-Verbose "[Get-AzureADClientAssertionToken] Retrieving client assertion token using certificate $($Certificate.GetCertHashString())" $TokenUriBuilder=New-Object System.UriBuilder($AuthorizationUri) $TokenUriBuilder.Path="$TenantId/$TokenEndpoint" $Sha=New-Object System.Security.Cryptography.SHA256Cng $RsaProvider=GetRsaCryptoProvider -RsaProvider $Certificate.PrivateKey try { #Get the client assertion $ClientAssertion=NewClientAssertion -Certificate $Certificate ` -ClientId $ClientId -Audience $TokenUriBuilder.Uri ` -Expires $Expires -NotBefore $NotBefore #Sign it $AssertionBytes=[System.Text.Encoding]::UTF8.GetBytes($ClientAssertion) $SignedTokenBytes=$RsaProvider.SignData($AssertionBytes,$Sha) $SignedToken=[Convert]::ToBase64String($SignedTokenBytes)|AddBase64UrlPaddingToString $EncodedAssertion="$ClientAssertion.$SignedToken" #Get the token $RequestBody=[ordered]@{ 'grant_type'='client_credentials'; 'client_id'=$ClientId; 'resource'=$Resource.OriginalString; 'client_assertion'=$EncodedAssertion; 'client_assertion_type'=$AssertionType; } Write-Verbose "[Get-AzureADClientAssertionToken] Retrieving token with gigned assertion $EncodedAssertion" $TokenResponse=Invoke-RestMethod -Uri $TokenUriBuilder.Uri -Method Post -Body $RequestBody -ErrorAction Stop Write-Output $TokenResponse } catch { throw "Error Acquiring Client Assertion Token $_" } finally { $Sha.Dispose() } } #endregion <# .SYNOPSIS Retrieves the Azure AD Token Signing Key .DESCRIPTION Retrieves the Azure AD Token Signing Key .PARAMETER TenantId The Azure AD Tenant Id .PARAMETER CertficateHash The discovery key Hash .PARAMETER DiscoveryUri The discovery key URI #> Function Get-AzureADDiscoveryKey { [CmdletBinding(ConfirmImpact='None')] param ( [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [String]$TenantId="common", [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [String]$CertificateHash, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [System.Uri]$DiscoveryUri="https://login.windows.net", [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] [String]$KeyPath="discovery/keys" ) $KeyUriBld=New-Object System.UriBuilder($DiscoveryUri) $KeyUriBld.Path="$TenantId/$($KeyPath.TrimStart('/'))" $KeyResult=Invoke-RestMethod -Uri $KeyUriBld.Uri -Method Get -ContentType 'application/json' if ($KeyResult -ne $null) { $Output=$KeyResult|Select-Object -ExpandProperty 'keys' if([String]::IsNullOrEmpty($CertificateHash) -eq $false) { $Output=$Output|Where-Object 'x5t' -eq $CertificateHash|Select-Object -First 1 } if($Output -ne $null) { Write-Output $Output } } } <# .SYNOPSIS Converts a discovery key object to an x509 Certificate .DESCRIPTION Converts a discovery key object to an x509 Certificate .PARAMETER Key The open id discovery key object #> Function ConvertFrom-AzureADDiscoveryKey { [CmdletBinding()] [OutputType([System.Security.Cryptography.X509Certificates.X509Certificate2])] param ( [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [PSObject[]]$Key ) PROCESS { foreach ($item in $Key) { if($Script:DiscoveryKeyCache.ContainsKey($item.x5t)) { Write-Verbose "[ConvertFrom-AzureADDiscoveryKey] Using cached certificate matching hash $($item.x5t)" $Cert=$Script:DiscoveryKeyCache[$item.x5t] } else { $Cert=New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(([Convert]::FromBase64String($item.x5c[0])),$item.kid) $Script:DiscoveryKeyCache.Add($item.x5t,$Cert) } Write-Output $Cert } } } |