core/modules/monkeymsal/public/Get-MonkeyMSALToken.ps1
# Monkey365 - the PowerShell Cloud Security Tool for Azure and Microsoft 365 (copyright 2022) by Juan Garrido # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. Function Get-MonkeyMSALToken{ <# .SYNOPSIS .DESCRIPTION .INPUTS .OUTPUTS .EXAMPLE .NOTES Author : Juan Garrido Twitter : @tr1ana File Name : Get-MonkeyMSALToken Version : 1.0 .LINK https://github.com/silverhack/monkey365 #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "", Scope="Function")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidDefaultValueForMandatoryParameter", "", Scope="Function")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("InjectionRisk.StaticPropertyInjection", "", Scope="Function")] [CmdletBinding(DefaultParameterSetName = 'Implicit')] [OutputType([Microsoft.Identity.Client.AuthenticationResult])] Param ( [Parameter(Mandatory = $false, ParameterSetName = 'Implicit', HelpMessage = 'Application Id')] [Parameter(Mandatory = $true, ParameterSetName = 'ClientSecret-App', HelpMessage = 'Application Id')] [Parameter(Mandatory = $true, ParameterSetName = 'ClientAssertionCertificate', HelpMessage = 'Application Id')] [Parameter(Mandatory = $true, ParameterSetName = 'ClientAssertionCertificate-File', HelpMessage = 'Application Id')] [Parameter(Mandatory = $false, ParameterSetName = 'Implicit-IntegratedWindowsAuth', HelpMessage = 'Application Id')] [Parameter(Mandatory = $false, ParameterSetName = 'Implicit-InputObject', HelpMessage = 'Application Id')] [String]$ClientId = "1950a258-227b-4e31-a9cf-717495945fc2", [parameter(Mandatory= $false, ParameterSetName = 'Implicit', HelpMessage= "User for access to the O365 services")] [String]$UserPrincipalName, [Parameter(Mandatory = $false, ParameterSetName = 'Implicit')] [Parameter(Mandatory = $false, ParameterSetName = 'Implicit-InputObject')] [Parameter(Mandatory = $false, ParameterSetName = 'Implicit-PublicApplication')] [System.Management.Automation.PSCredential]$UserCredentials, [Parameter(Mandatory = $true, ParameterSetName = 'ClientSecret-App', HelpMessage = 'Client Secret')] [Security.SecureString]$ClientSecret = [Security.SecureString]::new(), [Parameter(Mandatory = $true, ParameterSetName = 'ClientSecret-InputObject', HelpMessage = 'PsCredential')] [Alias('client_credentials')] [ValidateNotNull()] [System.Management.Automation.PSCredential]$ClientCredentials, [Parameter(Mandatory = $true, ParameterSetName = 'ClientAssertionCertificate-File', HelpMessage = 'Certificate file path')] [ValidateScript( { if( -Not ($_ | Test-Path) ){ throw ("The cert file does not exist in {0}" -f (Split-Path -Path $_)) } if(-Not ($_ | Test-Path -PathType Leaf) ){ throw "The argument must be a PFX file. Folder paths are not allowed." } if($_ -notmatch "(\.pfx)"){ throw "The certificate specified argument must be of type pfx" } return $true })] [System.IO.FileInfo]$Certificate, [Parameter(Mandatory = $false,ParameterSetName = 'ClientAssertionCertificate-File', HelpMessage = 'Certificate password')] [Security.SecureString]$CertFilePassword, [Parameter(Mandatory = $true, ParameterSetName = 'ClientAssertionCertificate', HelpMessage = 'Client assertion certificate')] [System.Security.Cryptography.X509Certificates.X509Certificate2]$ClientAssertionCertificate, [parameter(Mandatory=$false, HelpMessage = 'Redirect URI')] [System.Uri]$RedirectUri, [Parameter(Mandatory = $false, ParameterSetName = 'Implicit')] [Parameter(Mandatory = $false, ParameterSetName = 'Implicit-PublicApplication')] [Parameter(Mandatory = $true, ParameterSetName = 'ClientSecret-App')] [Parameter(Mandatory = $true, ParameterSetName = 'ClientSecret-InputObject')] [Parameter(Mandatory = $true, ParameterSetName = 'ClientAssertionCertificate')] [Parameter(Mandatory = $true, ParameterSetName = 'ClientAssertionCertificate-File')] [Parameter(Mandatory = $false, ParameterSetName = 'ClientSecret-ConfidentialApp')] [String]$TenantId, [parameter(Mandatory=$false, HelpMessage = 'Environment')] [Microsoft.Identity.Client.AzureCloudInstance]$Environment = [Microsoft.Identity.Client.AzureCloudInstance]::AzurePublic, [parameter(Mandatory=$false, HelpMessage = 'Instance')] [String]$Instance, [parameter(Mandatory=$false, HelpMessage = 'Authority')] [System.Uri]$Authority, [Parameter(Mandatory = $true, ParameterSetName = 'Implicit-PublicApplication')] [Microsoft.Identity.Client.IPublicClientApplication] $PublicApp, [Parameter(Mandatory = $true, ParameterSetName = 'ClientSecret-ConfidentialApp')] [Microsoft.Identity.Client.IConfidentialClientApplication] $ConfidentialApp, [Parameter(Mandatory = $false, ParameterSetName = 'ClientSecret-InputObject')] [Parameter(Mandatory = $true, ParameterSetName = 'ConfidentialClientSecret-AuthorizationCode')] [Parameter(Mandatory = $true, ParameterSetName = 'ConfidentialClientCertificate-AuthorizationCode')] [String] $AuthorizationCode, [parameter(Mandatory= $true, HelpMessage= "Resource to connect")] [String]$Resource, [Parameter(Mandatory = $false, ParameterSetName = 'Implicit')] [Parameter(Mandatory = $false, ParameterSetName = 'Implicit-PublicApplication')] [ValidateSet("SelectAccount", "NoPrompt", "Never", "ForceLogin")] [String] $PromptBehavior = 'SelectAccount', # Ignore any access token in the user token cache and attempt to acquire new access token using the refresh token for the account if one is available. [Parameter(Mandatory = $false, ParameterSetName = 'Implicit')] [Parameter(Mandatory = $false, ParameterSetName = 'Implicit-PublicApplication')] [Parameter(Mandatory = $false, ParameterSetName = 'Implicit-InputObject')] [Parameter(Mandatory = $false, ParameterSetName = 'ClientSecret-ConfidentialApp')] [Parameter(Mandatory = $false, ParameterSetName = 'ClientSecret-App')] [Parameter(Mandatory = $false, ParameterSetName = 'ClientSecret-InputObject')] [Parameter(Mandatory = $false, ParameterSetName = 'ClientAssertionCertificate')] [Parameter(Mandatory = $false, ParameterSetName = 'ClientAssertionCertificate-File')] [Switch]$ForceRefresh, [Parameter(Mandatory=$false, HelpMessage="scopes")] [Array]$Scopes, [Parameter(Mandatory = $false, ParameterSetName = 'Implicit')] [Parameter(Mandatory = $false, ParameterSetName = 'Implicit-PublicApplication')] [Parameter(Mandatory = $false, ParameterSetName = 'Implicit-InputObject')] [String[]] $ExtraScopesToConsent, [Parameter(Mandatory = $false, ParameterSetName = 'Implicit')] [Parameter(Mandatory = $false, ParameterSetName = 'Implicit-IntegratedWindowsAuth')] [Parameter(Mandatory = $false, ParameterSetName = 'Implicit-InputObject')] [Parameter(Mandatory = $false, ParameterSetName = 'Implicit-PublicApplication')] [Switch] $IntegratedWindowsAuth, [Parameter(Mandatory = $false, ParameterSetName = 'Implicit')] [Parameter(Mandatory = $false, ParameterSetName = 'Implicit-IntegratedWindowsAuth')] [Parameter(Mandatory = $false, ParameterSetName = 'Implicit-PublicApplication')] [Parameter(Mandatory = $false, ParameterSetName = 'Implicit-InputObject')] [String] $LoginHint, [Parameter(Mandatory = $false, ParameterSetName = 'Implicit')] [Parameter(Mandatory = $false, ParameterSetName = 'Implicit-PublicApplication', HelpMessage="Device code authentication")] [Parameter(Mandatory = $false, ParameterSetName = 'Implicit-InputObject', HelpMessage="Device code authentication")] [Switch]$DeviceCode, [Parameter(Mandatory=$false, HelpMessage="Force silent authentication")] [Switch]$Silent, [Parameter(Mandatory=$false, ParameterSetName = 'Implicit', HelpMessage="Force Authentication Context. Only valid for user&password auth method")] [Parameter(Mandatory = $false, ParameterSetName = 'Implicit-PublicApplication')] [Switch]$ForceAuth ) Begin{ #Set authType $AuthType = 'Interactive' $application = $authContext = $null; $Verbose = $Debug = $False; $InformationAction = 'SilentlyContinue' if($PSBoundParameters.ContainsKey('Verbose') -and $PSBoundParameters.Verbose){ $Verbose = $True } if($PSBoundParameters.ContainsKey('Debug') -and $PSBoundParameters.Debug){ $DebugPreference = 'Continue' $Debug = $True } if($PSBoundParameters.ContainsKey('InformationAction') -and $PSBoundParameters['InformationAction']){ $InformationAction = $PSBoundParameters['InformationAction'] } #Setting scopes if ($PSBoundParameters.ContainsKey('Scopes')){ if($Resource -match '/$'){ foreach($scp in $Scopes){ [string[]]$scope += ("{0}{1}" -f $Resource,$scp) } } else{ foreach($scp in $Scopes){ [string[]]$scope += ("{0}/{1}" -f $Resource,$scp) } } } else{ if($Resource -match '/$'){ [string[]]$scope = ("{0}.default" -f $Resource) } else{ [string[]]$scope = ("{0}/.default" -f $Resource) } } } Process{ if($PSCmdlet.ParameterSetName -eq 'ClientSecret-ConfidentialApp'){ $application = $PSBoundParameters['ConfidentialApp'] } Elseif($PSCmdlet.ParameterSetName -eq 'Implicit-PublicApplication'){ $application = $PSBoundParameters['PublicApp'] } else{ #Get command metadata $AppOptions = New-Object -TypeName "System.Management.Automation.CommandMetaData" (Get-Command -Name "New-MonkeyMSALApplication") #Set new dict $newPsboundParams = [ordered]@{} $param = $AppOptions.Parameters.Keys foreach($p in $param.GetEnumerator()){ if($PSBoundParameters.ContainsKey($p)){ $newPsboundParams.Add($p,$PSBoundParameters[$p]) } } #Add verbose, debug, etc.. [void]$newPsboundParams.Add('InformationAction',$InformationAction) [void]$newPsboundParams.Add('Verbose',$Verbose) [void]$newPsboundParams.Add('Debug',$Debug) #Get application $application = New-MonkeyMsalApplication @newPsboundParams } } End{ if($null -ne $application){ try{ if($application -is [Microsoft.Identity.Client.PublicClientApplication]){ If($PSBoundParameters.ContainsKey("ForceAuth") -and $PSBoundParameters['ForceAuth'].IsPresent){ $PromptBehavior = "ForceLogin" } If ($PSBoundParameters.ContainsKey("UserCredentials") -and $PSBoundParameters['UserCredentials']) { $authContext = $application.AcquireTokenByUsernamePassword($scope, $UserCredentials.UserName, $UserCredentials.Password) } ElseIf ($PSBoundParameters.ContainsKey("DeviceCode") -and $PSBoundParameters['DeviceCode']) { $AuthType = 'Device_Code' $authContext = $application.AcquireTokenWithDeviceCode($scope, [DeviceCodeHelper]::GetDeviceCodeResultCallback()) } ElseIf ($PSBoundParameters.ContainsKey("Silent") -and $PSBoundParameters['Silent'].IsPresent) { If ($PSBoundParameters.ContainsKey("UserPrincipalName") -and $PSBoundParameters['UserPrincipalName']){ $p = @{ MessageData = ($script:messages.UsingLoginHint -f $UserPrincipalName); Verbose = $verbose; } Write-Verbose @p $authContext = $application.AcquireTokenSilent($scope, $UserPrincipalName) } Else { [Microsoft.Identity.Client.IAccount] $Account = $application.GetAccountsAsync().GetAwaiter().GetResult() | Select-Object -First 1 if($Account){ $authContext = $application.AcquireTokenSilent($scope, $Account) } Else{ Write-Verbose ($script:messages.AccountWasNotFound); [ref]$null = $PSBoundParameters.Remove('Silent') Get-MonkeyMSALToken @PSBoundParameters } } If($null -ne $authContext -and $PSBoundParameters.ContainsKey('ForceRefresh') -and $PSBoundParameters['ForceRefresh'].IsPresent){ $p = @{ Message = ($script:messages.RefreshingToken); Verbose = $verbose; } Write-Verbose @p #Force refresh [void]$authContext.WithForceRefresh($ForceRefresh) } } Else{ $authContext = $application.AcquireTokenInteractive($scope) [IntPtr] $ParentWindow = [System.Diagnostics.Process]::GetCurrentProcess().MainWindowHandle if ($ParentWindow) { [void] $authContext.WithParentActivityOrWindow($ParentWindow) } if ($PromptBehavior){ [void] $authContext.WithPrompt([Microsoft.Identity.Client.Prompt]::$PromptBehavior) } Else{ [void] $authContext.WithPrompt([Microsoft.Identity.Client.Prompt]::SelectAccount) } If($PSBoundParameters.ContainsKey('UseEmbeddedWebView')){ [void]$authContext.WithUseEmbeddedWebView($UseEmbeddedWebView) } } } else{ #Get authentication type if($null -ne $application.AppConfig.ClientCredentialCertificate){ $AuthType = 'Certificate_Credentials' } else{ $AuthType = 'Client_Credentials' } if($PSBoundParameters.ContainsKey('AuthorizationCode') -and $PSBoundParameters['AuthorizationCode']){ $authContext = $application.AcquireTokenByAuthorizationCode($scope, $PSBoundParameters['AuthorizationCode']) } else{ $authContext = $application.AcquireTokenForClient($scope) if ($PSBoundParameters.ContainsKey('ForceRefresh') -and $PSBoundParameters['ForceRefresh'].IsPresent){ [void]$authContext.WithForceRefresh($ForceRefresh) } } } if($PSBoundParameters.ContainsKey('TenantId') -and $PSBoundParameters['TenantId']){ [void]$authContext.WithAuthority($PSBoundParameters['Environment'],$PSBoundParameters['TenantId']) } if($PSBoundParameters.ContainsKey('Authority') -and $PSBoundParameters['Authority']){ [void]$authContext.WithAuthority($PSBoundParameters['Authority'].AbsoluteUri) } if($null -ne $authContext){ #Create cancellationtoken $cancelationToken = [System.Threading.CancellationTokenSource]::new() try{ $token_result = $authContext.ExecuteAsync($cancelationToken.Token); while ($token_result.IsCompleted -ne $true){ Start-Sleep -Milliseconds 500; } } Finally{ if (!$token_result.IsCompleted) { $cancelationToken.Cancel() } $cancelationToken.Dispose() } if($token_result.IsFaulted){ Write-Warning ($script:messages.AcquireTokenFailed -f $token_result.Exception.InnerException.message) $ErrorCode = $token_result.Exception.InnerException | Select-Object -ExpandProperty ErrorCode -ErrorAction Ignore Write-Verbose ($script:messages.AcquireTokenFailed -f $ErrorCode) #Detailed error Write-Debug $token_result.Exception.InnerException #Retrying authentication without silent parameter if($application -is [Microsoft.Identity.Client.PublicClientApplication] -and $PSBoundParameters.ContainsKey('Silent')){ Write-Warning $script:messages.RemoveSilentParameter; [ref]$null = $PSBoundParameters.Remove('Silent') Get-MonkeyMSALToken @PSBoundParameters return } } if($null -ne $token_result.Result){ #add elements to auth object $new_token = $token_result.Result $new_token | Add-Member -type NoteProperty -name AuthType -value $AuthType -Force $new_token | Add-Member -type NoteProperty -name resource -value $Resource -Force $new_token | Add-Member -type NoteProperty -name clientId -value $application.ClientId -Force $new_token | Add-Member -type NoteProperty -name renewable -value $true -Force #Add TenantId if($null -ne $new_token.psobject.properties.Item('TenantId') -and $null -eq $new_token.TenantId){ if($TenantId){ $tid = $TenantId } elseif($application.AppConfig.TenantId){ $tid = $application.AppConfig.TenantId } else{ $tid = $null } $new_token | Add-Member -type NoteProperty -name TenantId -value $tid -Force } #Add function to check for near expiration $new_token | Add-Member -Type ScriptMethod -Name IsNearExpiry -Value { return ([System.Datetime]::UtcNow -gt $this.ExpiresOn.UtcDateTime.AddMinutes(-15)) } #Add function to disable token renewal $new_token | Add-Member -Type ScriptMethod -Name DisableRenew -Value { $this.renewable = $false } #return new token return $new_token } else{ #Write message Write-Debug -Message ($script:messages.AccessTokenErrorMessage -f $Resource) return $null } } } Catch{ Write-Error $_ } } } } |